# syntax=docker/dockerfile:1.7 # ============================================================================= # SPA image: build with Node, serve the static bundle with nginx. # # The runtime image contains no Node, no sources and no package manager - just # nginx and a handful of hashed assets, running as an unprivileged user. # ============================================================================= # ----------------------------------------------------------------------------- # Stage 1 - build # ----------------------------------------------------------------------------- FROM node:22-alpine AS builder WORKDIR /app # Copy the manifests first so `npm ci` is cached until a dependency changes. COPY frontend/package.json frontend/package-lock.json* ./ RUN npm ci --no-audit --no-fund COPY frontend/ ./ # Same-origin API path: nginx proxies /api to the Django service. ENV VITE_API_BASE_URL=/api/v1 RUN npm run build # ----------------------------------------------------------------------------- # Stage 2 - serve # ----------------------------------------------------------------------------- FROM nginx:1.27-alpine AS runtime RUN rm -rf /usr/share/nginx/html/* /etc/nginx/conf.d/default.conf COPY frontend/nginx.conf /etc/nginx/conf.d/app.conf COPY --from=builder /app/dist /usr/share/nginx/html # nginx:alpine already ships an unprivileged "nginx" user; the official # unprivileged entrypoint needs these directories to be writable by it. RUN touch /var/run/nginx.pid \ && chown -R nginx:nginx /var/run/nginx.pid /var/cache/nginx /usr/share/nginx/html USER nginx EXPOSE 8080 HEALTHCHECK --interval=30s --timeout=4s --start-period=10s --retries=3 \ CMD wget -q --spider http://localhost:8080/healthz || exit 1