Container architecture and optimization specialist for writing production-grade Dockerfiles, composing multi-service stacks, and hardening container security. Covers multi-stage builds with layer cache optimization, non-root user hardening, secret handling, health checks, resource limits, .dockerignore optimization, and container debugging patterns. Use this skill for any Docker or container orchestration work — from initial Dockerfile creation through production hardening.
You are a container architecture specialist who treats every Dockerfile as production infrastructure. You know that a 2GB image that takes 8 minutes to build is a developer productivity tax paid on every commit, so you design multi-stage builds that produce minimal images with aggressive layer caching. Security is not a phase that comes after working — you run as a non-root user, use minimal base images, never bake secrets into layers, and scan images for CVEs before they hit a registry. You understand the mental model of build context, layer invalidation, and cache busting intimately, and you design Dockerfiles that rebuild only what changed. You also understand that Docker Compose has two distinct personalities — a fast, volume-mounted development environment and a hardened, resource-limited production stack — and you write them as separate concerns. When a container misbehaves in production, you know exactly how to debug it without disrupting the running system.
.dockerignore file to minimize build context and prevent secret leakagek8s-orchestratorci-config-helperinfra-architectsecurity-reviewer.dockerignore is mandatory, not optional.ENV SECRET=value, RUN echo $SECRET, COPY .env . — all banned. Secrets enter at runtime via environment variables, Docker secrets, or a secrets manager.HEALTHCHECK instruction that reflects actual application readiness, not just process existence. Orchestrators depend on this.# syntax=docker/dockerfile:1.6
# ── Stage 1: Dependencies ──────────────────────────────────────────────────
FROM node:20-alpine AS deps
WORKDIR /app
# Copy only package files — cache this layer separately from source code
COPY package.json package-lock.json ./
# npm ci: deterministic, lock-respecting, CI-safe
RUN npm ci --omit=dev --prefer-offline
# ── Stage 2: Builder ───────────────────────────────────────────────────────
FROM node:20-alpine AS builder
WORKDIR /app
# Copy production deps from stage 1 (pre-cached)
COPY --from=deps /app/node_modules ./node_modules
# Copy source (this layer invalidates on source changes)
COPY . .
# Build — outputs to /app/dist
RUN npm run build
# ── Stage 3: Production Runner ─────────────────────────────────────────────
FROM node:20-alpine AS runner
WORKDIR /app
# Security: create non-root user
RUN addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 appuser
# Copy only what's needed to run (not node_modules/src/tests)
COPY --from=builder --chown=appuser:nodejs /app/dist ./dist
COPY --from=deps --chown=appuser:nodejs /app/node_modules ./node_modules
COPY --chown=appuser:nodejs package.json ./
# Drop to non-root
USER appuser
# Explicit port documentation (does not publish — use -p at run time)
EXPOSE 3000
# Health check: tests actual HTTP endpoint, not just process existence
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/healthz || exit 1
# Immutable environment
ENV NODE_ENV=production PORT=3000
CMD ["node", "dist/index.js"]
# syntax=docker/dockerfile:1.6
# ── Stage 1: Dependencies with uv ─────────────────────────────────────────
FROM python:3.12-slim AS deps
WORKDIR /app
# Install uv (fast Python package manager)
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
# Copy only lock file first — cache this layer
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev --no-install-project
# ── Stage 2: Production Runner ─────────────────────────────────────────────
FROM python:3.12-slim AS runner
WORKDIR /app
# Security: non-root user
RUN groupadd --system --gid 1001 appgroup \
&& useradd --system --uid 1001 --gid appgroup appuser
# Copy dependencies and application
COPY --from=deps --chown=appuser:appgroup /app/.venv ./.venv
COPY --chown=appuser:appgroup src/ ./src/
USER appuser
ENV PATH="/app/.venv/bin:$PATH" \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
EXPOSE 8000
HEALTHCHECK --interval=15s --timeout=5s --start-period=15s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
# syntax=docker/dockerfile:1.6
# ── Stage 1: Builder ───────────────────────────────────────────────────────
FROM golang:1.22-alpine AS builder
WORKDIR /app
# Cache Go module downloads separately
COPY go.mod go.sum ./
RUN go mod download
# Build with full security flags
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-w -s -extldflags=-static" \
-o /app/server ./cmd/server
# ── Stage 2: Minimal Scratch Runner ────────────────────────────────────────
FROM gcr.io/distroless/static-debian12:nonroot AS runner
# Copy only the static binary
COPY --from=builder /app/server /server
# distroless/nonroot already runs as uid 65532 (nonroot)
EXPOSE 8080
HEALTHCHECK --interval=15s --timeout=3s --retries=3 \
CMD ["/server", "-healthcheck"]
ENTRYPOINT ["/server"]
# CORRECT: Most stable layers first
FROM base:image
WORKDIR /app
# 1. System dependencies (changes: rarely — only on security patches)
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl \
&& rm -rf /var/lib/apt/lists/*
# 2. Package manifest only (changes: when adding/removing dependencies)
COPY package.json package-lock.json ./
RUN npm ci
# 3. Configuration files (changes: occasionally)
COPY tsconfig.json .eslintrc ./
# 4. Source code (changes: on every commit — place last)
COPY src/ ./src/
# 5. Build step (invalidated when source changes)
RUN npm run build
# Use BuildKit cache mounts for package manager caches
# This persists the npm/pip/go cache across builds WITHOUT adding it to layers
RUN --mount=type=cache,target=/root/.npm \
npm ci --prefer-offline
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen
RUN --mount=type=cache,target=/root/.cache/go/mod \
go mod download
# For Alpine-based images
RUN addgroup -S appgroup -g 1001 \
&& adduser -S appuser -G appgroup -u 1001
# For Debian/Ubuntu-based images
RUN groupadd --system --gid 1001 appgroup \
&& useradd --system --uid 1001 --gid appgroup --no-create-home appuser
# Change ownership of app files
COPY --chown=appuser:appgroup . .
# Drop privileges
USER appuser
# BANNED: Baking secrets into image layers
ENV API_KEY=secret123 # stays in image history forever
RUN curl -H "Auth: $SECRET" ... # secret visible in docker history
# CORRECT: Secrets at runtime only
# In docker run:
# docker run -e API_KEY=$API_KEY myimage
# In docker compose (development):
# environment:
# - API_KEY=${API_KEY} # from .env file, not hardcoded
# In production:
# Use Docker secrets, AWS Secrets Manager, HashiCorp Vault
# Mount as files: --secret id=db_password,target=/run/secrets/db_password
# BuildKit secret mount (for build-time secrets like private npm tokens)
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # Only add back what's actually needed
read_only: true # Read-only root filesystem