diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..476d808 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +node_modules +.nuxt +.output +.git +.env +.env.* +!.env.example +*.log +.DS_Store +tests diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml new file mode 100644 index 0000000..5a6b95e --- /dev/null +++ b/.github/workflows/build-and-publish.yml @@ -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 }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..59529a5 --- /dev/null +++ b/Dockerfile @@ -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 + +COPY --from=builder /app/.output .output +COPY --from=builder /app/docker/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +EXPOSE 3000 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \ + CMD wget -qO- http://localhost:3000/ || exit 1 + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000..8d50d57 --- /dev/null +++ b/docker/entrypoint.sh @@ -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 + +exec node .output/server/index.mjs