# Web build stage - the API serves this directory itself (see # registerWebUI in cmd/api/main.go); there's no separate nginx/web image. FROM node:22-alpine AS build_web WORKDIR /app/web COPY web/package.json web/package-lock.json ./ RUN npm ci COPY web/ ./ RUN npm run build # Output: /app/web/dist/web/browser # API build stage FROM golang:1.25-alpine AS build_go WORKDIR /app # Install git and build dependencies RUN apk add --no-cache git # Copy go mod and sum files COPY go.mod go.sum ./ # Download dependencies RUN go mod download # Copy source code COPY . . # Build the application # CGO_ENABLED=0 ensures a static binary ARG TARGETOS TARGETARCH RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o api-server ./cmd/api # Runtime Stage FROM alpine:latest WORKDIR /app # Install ca-certificates for database connections (TLS) RUN apk add --no-cache ca-certificates # Copy the binary from the builder stage. Migrations are embedded in the # binary (see db/migrations/embed.go), so there's nothing else to copy for # the API itself. COPY --from=build_go /app/api-server . # The web app's build output. registerWebUI (cmd/api/main.go) serves this # directory as a single-page app, with unknown paths falling back to # index.html for Angular's router; it no-ops if WEB_DIST_DIR is missing, so # this layer is the only thing that turns that serving on. COPY --from=build_web /app/web/dist/web/browser /frontend # Expose the application port EXPOSE 8080 # Environment variables with defaults (can be overridden) ENV PORT=8080 ENV DATABASE_URL="" ENV WEB_DIST_DIR=/frontend # Run the binary CMD ["./api-server"]