Skip to content

feat: Add docker image build and publish pipeline - #109

Open
Lantum-Brendan wants to merge 1 commit into
mainfrom
feat/docker-image-pr2
Open

feat: Add docker image build and publish pipeline#109
Lantum-Brendan wants to merge 1 commit into
mainfrom
feat/docker-image-pr2

Conversation

@Lantum-Brendan

Copy link
Copy Markdown
Collaborator

What

Adds a multi-stage Dockerfile, entrypoint script, CI workflow, and .dockerignore so the webui can be published as ghcr.io/trakli/webui — the image every downstream deployment template needs.

This is PR 2 of Issue #308 — Distribution, Helm, Cloud Templates.

Why

The webui has no published Docker image today. Self-hosting, FlatRun, Helm, and every cloud template in #308 depend on pulling this image. Without it, Phase 2–4 templates can't reference a webui image.

How

Dockerfile (multi-stage):

  • Stage 1 (node:lts): npm ci + npm run build with a placeholder API URL baked into the output
  • Stage 2 (node:lts-alpine): copies only .output/ — small final image

Runtime config fix: Nuxt 3 bakes runtimeConfig.public values into JS at build time. Build with http://__NUXT_PUBLIC_API_BASE_URL__ as placeholder. At container start, docker/entrypoint.sh sed-replaces it with the actual env var, then starts the Node server.

CI workflow (.github/workflows/build-and-publish.yml):

  • SHA tag on dev push, latest + release semver on GitHub Release publish
  • Uses docker/build-push-action with GITHUB_TOKEN auth

Healthcheck: wget -qO- http://localhost:3000/ on port 3000.

Checklist

  • docker build succeeds
  • CI publishes to ghcr.io/trakli/webui with correct tag scheme
  • Image is publicly accessible (not private like the hosted image)

The webui has no published Docker image today. Every downstream
deployment template (compose stack, Helm, cloud providers) needs
ghcr.io/trakli/webui to exist.

Dockerfile (multi-stage):
- Stage 1 (node:lts): npm ci + npm run build with a placeholder API URL
  baked into the output. Nuxt 3 runtimeConfig.public values are embedded
  at build time, so we cannot set the real URL here.
- Stage 2 (node:lts-alpine): copies only .output/ from the build stage.
  Alpine-based for a small final image.

docker/entrypoint.sh:
- At container start, sed-replaces the build-time placeholder
  (http://__NUXT_PUBLIC_API_BASE_URL__) in all .output/ JS files with
  the actual NUXT_PUBLIC_API_BASE_URL env var, then starts the Node
  server. Standard Nuxt 3 pattern for a single image across deployments.

.github/workflows/build-and-publish.yml:
- Mirrors the webservice workflow: SHA tag on dev push, latest +
  release semver tag on GitHub Release publish. Pushes to ghcr.io.

.dockerignore:
- Excludes node_modules, .nuxt, .output, .git, .env*, tests.

Issue: trakli/webservice#308
Copilot AI lite review requested due to automatic review settings August 11, 2026 12:41
@sourceant

sourceant Bot commented Aug 11, 2026

Copy link
Copy Markdown

Code Review Summary

This PR successfully introduces a Dockerized deployment path for the Trakli WebUI. It includes a multi-stage Dockerfile optimized for production and a CI/CD pipeline for automated publishing to GHCR.

🚀 Key Improvements

  • Implemented a multi-stage Docker build to reduce final image footprint.
  • Added a runtime configuration injection mechanism to allow a single image to be used across different environments.
  • Integrated GitHub Actions for automated building and tagging on both dev pushes and releases.

💡 Minor Suggestions

  • Consolidate the GitHub Action steps using docker/metadata-action for cleaner YAML.
  • Add .github to .dockerignore to keep the build context minimal.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread Dockerfile
Comment on lines +29 to +30
WORKDIR /app

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For security best practices, the container should run as a non-root user. The node-alpine image provides a node user (UID 1000) that can be utilized. Ensure the files are owned by this user so that the entrypoint script (which uses sed -i) has permission to modify them at runtime.

Suggested change
WORKDIR /app
+WORKDIR /app
+
+COPY --from=builder --chown=node:node /app/.output .output
+COPY --from=builder --chown=node:node /app/docker/entrypoint.sh /entrypoint.sh
+RUN chmod +x /entrypoint.sh
+
+USER node

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Docker packaging and a GitHub Actions workflow so the Nuxt web UI can be built and published to GHCR as ghcr.io/trakli/webui, enabling downstream deployment templates to consume a prebuilt image.

Changes:

  • Introduces a multi-stage Dockerfile that builds the Nuxt app and ships only .output/ in a slim runtime image.
  • Adds a container docker/entrypoint.sh that patches the baked Nuxt public runtime config placeholder at startup.
  • Adds a GitHub Actions workflow to build/push images on dev pushes and release publishes, plus a .dockerignore.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
Dockerfile Multi-stage build and runtime image setup (healthcheck + entrypoint).
docker/entrypoint.sh Startup-time placeholder replacement for Nuxt public runtime config.
.github/workflows/build-and-publish.yml CI build/push pipeline to GHCR for dev commits and releases.
.dockerignore Reduces Docker build context size by excluding local/dev artifacts.
Suppressed comments (1)

Dockerfile:22

  • To keep runtime behavior aligned with CI (which runs on Node.js 20), consider pinning the final image to Node 20 as well instead of node:lts-alpine (which can jump major versions over time).
FROM node:lts-alpine

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread docker/entrypoint.sh
Comment on lines +8 to +11
if [ -n "$NUXT_PUBLIC_API_BASE_URL" ]; then
find /app/.output -type f \( -name '*.mjs' -o -name '*.js' -o -name '*.cjs' \) \
-exec sed -i "s|http://__NUXT_PUBLIC_API_BASE_URL__|${NUXT_PUBLIC_API_BASE_URL}|g" {} +
fi
Comment thread Dockerfile
Comment on lines +29 to +33
WORKDIR /app

COPY --from=builder /app/.output .output
COPY --from=builder /app/docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
Comment thread Dockerfile
# so a single image works across deployments with different API URLs.

# ── Stage 1: Build ──────────────────────────────────────────────────────────
FROM node:lts AS builder
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants