xtablo-source/backend/Dockerfile
Arthur Belleville f29bf0c765
feat(07-02): multi-stage Dockerfile for web + worker binaries
- Stage 1 (assets): downloads Tailwind v4.0.0 CLI, HTMX@2, Sortable.js 1.15.7; compiles minified CSS
- Stage 2 (builder): runs templ generate @v0.3.1020; CGO_ENABLED=0 go build for /app/web and /app/worker
- Stage 3 (runtime): gcr.io/distroless/static-debian12:nonroot; no CMD per D-08
- No .env files COPY'd into any layer (T-07-05 mitigated)
2026-05-15 18:19:32 +02:00

93 lines
3.9 KiB
Docker

# Multi-stage Dockerfile for the Xtablo Go backend.
#
# Three stages:
# assets — downloads Tailwind CLI, HTMX, and Sortable.js then compiles the
# Tailwind output CSS from tailwind.input.css + templates/. (D-09)
# builder — copies assets, runs `templ generate`, and compiles both binaries
# with CGO_ENABLED=0 so they are fully static. (D-07)
# runtime — copies only the two binaries into a minimal distroless image.
# No CMD is set — docker-compose overrides command: per service. (D-08)
#
# Build context: backend/ directory (sibling to go.mod).
# From the repo root: docker build -f backend/Dockerfile backend/
# From inside backend/: docker build .
#
# Security: no .env files are ever COPY'd into any layer (T-07-05).
# The :nonroot distroless tag runs as uid 65532, preventing filesystem writes (T-07-07).
# ---------------------------------------------------------------------------
# Stage 1: assets
# Downloads pinned versions of Tailwind CLI, HTMX, and Sortable.js,
# then compiles tailwind.input.css against templates/ for the minified CSS.
# ---------------------------------------------------------------------------
FROM node:20-alpine AS assets
WORKDIR /build
RUN apk add --no-cache curl
RUN mkdir -p static
# Tailwind standalone CLI — pinned at v4.0.0 (matches justfile tailwind_version)
RUN curl -sSL -o /usr/local/bin/tailwindcss \
"https://github.com/tailwindlabs/tailwindcss/releases/download/v4.0.0/tailwindcss-linux-x64" \
&& chmod +x /usr/local/bin/tailwindcss
# HTMX — pinned at major version 2 (matches justfile htmx_version)
RUN curl -sSL -o static/htmx.min.js \
"https://unpkg.com/htmx.org@2/dist/htmx.min.js"
# Sortable.js — pinned at v1.15.7 (matches justfile sortable_version)
RUN curl -sSL -o static/sortable.min.js \
"https://cdn.jsdelivr.net/npm/sortablejs@1.15.7/Sortable.min.js"
# Copy Tailwind input and templates for content-scanning
COPY tailwind.input.css .
COPY templates/ templates/
# Compile and minify Tailwind CSS
RUN tailwindcss -i tailwind.input.css -o static/tailwind.css --minify
# ---------------------------------------------------------------------------
# Stage 2: builder
# Compiles both Go binaries (cmd/web and cmd/worker) with CGO_ENABLED=0.
# Runs `templ generate` first since *_templ.go files are gitignored. (D-07)
# ---------------------------------------------------------------------------
FROM golang:1.26-alpine AS builder
WORKDIR /app
# Download dependencies first (layer-cached until go.mod/go.sum change)
COPY go.mod go.sum ./
RUN go mod download
# Copy the entire backend context
COPY . .
# Overwrite static/ with the freshly built assets from the assets stage
COPY --from=assets /build/static ./static
# Install templ at the pinned version and run template generation
# (gitignored *_templ.go files must be generated at build time)
RUN go install github.com/a-h/templ/cmd/templ@v0.3.1020 && templ generate
# Compile the web server binary — CGO_ENABLED=0 required for distroless/static
RUN --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -trimpath -o /app/web ./cmd/web
# Compile the background worker binary — CGO_ENABLED=0 required for distroless/static
RUN --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -trimpath -o /app/worker ./cmd/worker
# ---------------------------------------------------------------------------
# Stage 3: runtime
# Minimal distroless image containing only the two compiled binaries.
# Runs as nonroot (uid 65532) — no filesystem write access. (T-07-07)
# No CMD set — docker-compose.prod.yaml provides `command:` per service. (D-08)
# ---------------------------------------------------------------------------
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /app/web /app/web
COPY --from=builder /app/worker /app/worker
EXPOSE 8080