# syntax=docker/dockerfile:1 # # Two build stages that do not know about each other — the Go binary and the # Vite bundle — collapsed into one small runtime image. The server serves both # the API and the built frontend, so there is a single process to run and a # single port to expose. # ------------------------------------------------------------------ web ---- FROM node:24-alpine AS web RUN corepack enable WORKDIR /web COPY web/package.json web/pnpm-lock.yaml web/pnpm-workspace.yaml ./ RUN pnpm install --frozen-lockfile COPY web/ ./ # The manifest this produces is what the Go server reads to find the hashed # entry files, so a build without it would boot to an error. RUN pnpm build # ------------------------------------------------------------------ api ---- FROM golang:1.25-alpine AS api WORKDIR /src COPY api/go.mod api/go.sum ./ RUN go mod download COPY api/ ./ # Static binary: the runtime image has no libc to link against. RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/server ./cmd/server # -------------------------------------------------------------- runtime ---- FROM alpine:3.22 AS runner # curl is here for the healthcheck; the binary itself needs nothing but certs. RUN apk add --no-cache ca-certificates curl \ && addgroup -S app -g 1001 && adduser -S app -u 1001 WORKDIR /app COPY --from=api /out/server /usr/local/bin/ COPY --from=web --chown=app:app /web/dist ./web ENV ADDR=:3000 WEB_DIR=/app/web USER app EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD curl -fsS http://127.0.0.1:3000/api/health || exit 1 CMD ["server"] # ---------------------------------------------------------------- seeder ---- # The fixture loader, for development and CI only. Its first act is to DELETE # every campaign, organisation and user, so it must not be reachable from the # image that runs in production: `--target runner` never builds these two # stages, and nothing in compose references them. FROM api AS seedbuild RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/seed ./test/seed FROM runner AS seeder COPY --from=seedbuild /out/seed /usr/local/bin/ USER app CMD ["seed"]