Docker and container best practices for production workloads. Covers multi-stage Dockerfiles, base image selection, security hardening, BuildKit caching, docker-compose patterns, health checks, multi-arch builds, size optimization, and CI/CD integration. Use when writing Dockerfiles, setting up docker-compose, optimizing container images, or building CI/CD pipelines with containers.
<essential_principles>
Core Philosophy
Decision Matrix: Base Image Selection
| Use Case | Base Image | Size | Notes |
|---|---|---|---|
| Go / Rust (static binary) | scratch or gcr.io/distroless/static-debian12 | ~2-5 MB | No shell, no package manager -- most secure |
| Go / Rust (needs CA certs, tzdata) | gcr.io/distroless/base-debian12 | ~20 MB | Includes glibc, CA certs, tzdata |
| Node.js / Python | node:22-slim / python:3.13-slim | ~150-200 MB | Slim variants strip dev tools |
| .NET | mcr.microsoft.com/dotnet/aspnet:9.0 | ~220 MB | Runtime-only image |
| Need a shell for debugging | alpine:3.21 | ~7 MB | Use only when shell access required |
| General purpose with tooling | debian:bookworm-slim | ~80 MB | When you need apt-get |
Never use latest tag in production. Always pin to specific version + digest for reproducibility.
</essential_principles>
<multi_stage_builds>
Every production Dockerfile should use multi-stage builds. Separate build dependencies from runtime.
# syntax=docker/dockerfile:1
# ── Build stage ──────────────────────────────────────────
FROM golang:1.24-bookworm AS builder
WORKDIR /src
# Cache dependencies separately from source code
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
# Copy source and build
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-s -w -X main.version=${VERSION}" \
-o /bin/app ./cmd/app/
# ── Runtime stage ────────────────────────────────────────
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /bin/app /app
EXPOSE 8080
ENTRYPOINT ["/app"]
# syntax=docker/dockerfile:1
# ── Dependencies stage ───────────────────────────────────
FROM node:22-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --production=false
# ── Build stage ──────────────────────────────────────────
FROM deps AS builder
COPY . .
RUN npm run build
# ── Production dependencies ──────────────────────────────
FROM node:22-slim AS prod-deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci --omit=dev
# ── Runtime stage ────────────────────────────────────────
FROM node:22-slim
RUN groupadd -r appuser && useradd -r -g appuser -d /app appuser
WORKDIR /app
COPY --from=prod-deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package.json ./
USER appuser
EXPOSE 3000
CMD ["node", "dist/index.js"]
# syntax=docker/dockerfile:1
# ── Build stage ──────────────────────────────────────────
FROM node:22-slim AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci
COPY . .
RUN npm run build
# ── Runtime stage (static files served by nginx) ────────
FROM nginx:1.27-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
Key Principles:
# syntax=docker/dockerfile:1 to enable BuildKit features--mount=type=cache for package manager caches-ldflags="-s -w"CGO_ENABLED=0 for Go when targeting scratch/distroless</multi_stage_builds>
Non-Negotiable Security Practices:
# For distroless: use the nonroot tag
FROM gcr.io/distroless/static-debian12:nonroot
# For other images: create a dedicated user
RUN groupadd -r appuser && useradd -r -g appuser -s /usr/sbin/nologin appuser
USER appuser
# WRONG -- secret baked into layer history
COPY .env /app/.env
ENV DATABASE_PASSWORD=hunter2
# RIGHT -- use build secrets (never persisted in image)
RUN --mount=type=secret,id=db_password \
cat /run/secrets/db_password > /dev/null
# RIGHT -- pass at runtime via environment or mounted secrets
# docker run -e DATABASE_PASSWORD=... app
# docker run -v /secrets/db_password:/run/secrets/db_password:ro app
# docker-compose.yml