Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
node_modules
.nuxt
.output
.git
.env
.env.*
!.env.example
*.log
.DS_Store
tests
52 changes: 52 additions & 0 deletions .github/workflows/build-and-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Build and Publish Docker Image

on:
push:
branches:
- dev
release:
types: [published]

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Resolve short commit SHA
id: meta
if: ${{ github.event_name == 'push' }}
run: echo "sha=${GITHUB_SHA:0:7}" >> "$GITHUB_OUTPUT"

- name: Build and push commit image
if: ${{ github.event_name == 'push' }}
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/trakli/webui:${{ steps.meta.outputs.sha }}

- name: Build and push latest
if: ${{ github.event_name == 'release' }}
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
ghcr.io/trakli/webui:latest
ghcr.io/trakli/webui:${{ github.event.release.tag_name }}
40 changes: 40 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Trakli web UI — multi-stage build for production
#
# Nuxt 3 bakes runtimeConfig.public values into the .output/ at build time.
# We build with a placeholder and replace it at container start via entrypoint,
# so a single image works across deployments with different API URLs.

# ── Stage 1: Build ──────────────────────────────────────────────────────────
FROM node:lts AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .

# Placeholder — replaced at runtime by docker/entrypoint.sh
ENV NUXT_PUBLIC_API_BASE_URL=http://__NUXT_PUBLIC_API_BASE_URL__
RUN npm run build

# ── Stage 2: Runtime ────────────────────────────────────────────────────────
FROM node:lts-alpine

LABEL org.opencontainers.image.source=https://github.com/trakli/webui
LABEL org.opencontainers.image.description="Trakli web dashboard"
LABEL org.opencontainers.image.vendor="WhileSmart LLC"
LABEL org.opencontainers.image.licenses=MIT

WORKDIR /app

Comment on lines +29 to +30

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

COPY --from=builder /app/.output .output
COPY --from=builder /app/docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
Comment on lines +29 to +33

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \
CMD wget -qO- http://localhost:3000/ || exit 1

ENTRYPOINT ["/entrypoint.sh"]
13 changes: 13 additions & 0 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/sh
set -e

# Replace the build-time placeholder with the actual runtime value.
# This is how a single Docker image serves deployments with different API URLs
# without rebuilding — Nuxt 3 bakes runtimeConfig.public into .output/ at
# build time, so we patch the built files before starting the server.
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 on lines +8 to +11

exec node .output/server/index.mjs
Loading