docker-expert
Expert-level Docker patterns covering multi-stage builds, layer caching optimization, base image selection, security hardening, BuildKit features, and container best practices. Use when writing Dockerfiles, optimizing image size, securing containers, managing build
Docker Expert
Production Docker images are not just "it works in a container" — they're minimal, secure,
fast to build (layer cache hits), and treat containers as immutable infrastructure. The gap
between a naive FROM ubuntu + copy-everything Dockerfile and a production multi-stage build
can be the difference between 1.2GB and 65MB, and between a 4-minute build and a 30-second one.
Core Mental Model
Containers should be immutable, stateless, and single-purpose. Build images with the smallest
possible attack surface (distroless or Alpine), run as non-root, and never bake secrets into
layers. Layer caching is a performance multiplier — order instructions from "least likely to
change" to "most likely to change". Multi-stage builds separate build dependencies from runtime
dependencies, dramatically reducing final image size.
.dockerignore — Critical First Step
# .dockerignore — always create this before writing a Dockerfile
node_modules
npm-debug.log*
.git
.gitignore
.env
.env.local
.env.*.local
*.md
dist
build
.next
coverage
.nyc_output
__pycache__
*.pyc
*.pyo
.pytest_cache
.mypy_cache
.tox
venv
.venv
*.egg-info
.DS_Store
Thumbs.db
Multi-Stage Build: Production Node.js
# syntax=docker/dockerfile:1
FROM node:22-alpine AS base
WORKDIR /app
# Install only production deps by default
ENV NODE_ENV=production
# ─── Dependencies Stage ───────────────────────────────────────
FROM base AS deps
# Copy package files first — cached unless they change
COPY package.json package-lock.json ./
RUN npm ci --frozen-lockfile
# ─── Builder Stage ────────────────────────────────────────────
FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NODE_ENV=production
RUN npm run build
# ─── Runtime Stage ────────────────────────────────────────────
FROM base AS runner
# Non-root user for security
RUN addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 nextjs
# Only copy what the runtime needs
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
USER nextjs
EXPOSE 3000
ENV PORT=3000 HOSTNAME="0.0.0.0"
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget -qO- http://localhost:3000/api/health || exit 1
CMD ["node", "server.js"]
Multi-Stage Build: Production Python (FastAPI)
# syntax=docker/dockerfile:1
FROM python:3.12-slim AS base
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# ─── Builder: install dependencies into venv ──────────────────
FROM base AS builder
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
WORKDIR /app
COPY requirements.txt .
# Mount pip cache for faster rebuilds (BuildKit)
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
# ─── Runtime: copy venv, no build tools ───────────────────────
FROM base AS runtime
ENV PATH="/opt/venv/bin:$PATH"
# Non-root user
RUN useradd --system --create-home --uid 1001 appuser
COPY --from=builder /opt/venv /opt/venv
COPY --chown=appuser:appuser . /app
WORKDIR /app
USER appuser
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
Multi-Stage Build: Go (Distroless)
# syntax=docker/dockerfile:1
FROM golang:1.23-alpine AS builder
WORKDIR /app
# Cache dependencies separately from source
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/root/go/pkg/mod \
go mod download
COPY . .
RUN --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-s -w" -o /app/server ./cmd/server
# ─── Distroless: minimal attack surface ───────────────────────
# No shell, no package manager, no OS utilities
FROM gcr.io/distroless/static-debian12:nonroot AS runtime
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
Base Image Selection
| Image | Size | Shell | Use When |
ubuntu / debian | 70–120MB | bash | Debugging, legacy requirements |
debian:slim | 35–75MB | sh | Python/Node with some OS deps |
alpine | 5–10MB | sh (ash) | Small image, check musl compat |
distroless/static | ~2MB | none | Go, statically-linked binaries |
distroless/base | ~20MB | none | Apps needing glibc |
chainguard/* | ~5–30MB | none | Supply chain security, CVE-minimal |
# Alpine: smaller but musl libc can cause issues with some Python packages
FROM python:3.12-alpine # ← may fail on packages needing glibc
# Debian slim: safer compatibility
FROM python:3.12-slim # ← recommended default for Python
# Node: alpine variant
FROM node:22-alpine # ← good default for Node.js
Layer Caching Optimization
# ❌ Bad order: COPY everything first invalidates cache on every source change
FROM node:22-alpine
COPY . . # any file change busts all subsequent layers
RUN npm install # re-runs on every change
# ✅ Good order: stable layers first
FROM node:22-alpine
COPY package.json package-lock.json ./ # only changes when deps change
RUN npm ci # cached until package files change
COPY . . # source changes don't re-run npm ci
RUN npm run build
# ✅ Group apt-get install in one RUN (fewer layers, no orphaned package lists)
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
BuildKit Features — Build Secrets
# syntax=docker/dockerfile:1
# ✅ Mount secret at build time — NEVER in a layer, never in image history
FROM python:3.12-slim
RUN --mount=type=secret,id=pip_token \
pip install --index-url "https://$(cat /run/secrets/pip_token)@example.com/simple/" private-pkg
# Build with:
# docker build --secret id=pip_token,env=PIP_TOKEN .
# ✅ SSH forwarding for private Git repos
FROM python:3.12-slim
RUN --mount=type=ssh \
pip install git+ssh://[email protected]/org/private-repo.git
# docker build --ssh default .
# ✅ Cache mounts (already shown above)
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
ARG vs ENV
# ARG: build-time only, not available in running container (except EXPOSE/FROM)
ARG BUILD_VERSION=dev
ARG NODE_ENV=production
# ENV: runtime environment variable, available in container
ENV APP_VERSION=$BUILD_VERSION
ENV NODE_ENV=$NODE_ENV
# ⚠️ ARGs appear in docker history! Don't use for secrets.
ARG DATABASE_URL # ❌ visible in build history
# ✅ Use --secret mount instead
# Useful ARG pattern: multi-platform
ARG TARGETPLATFORM TARGETOS TARGETARCH
RUN echo "Building for $TARGETPLATFORM"
Non-Root User Security
# Debian/Ubuntu
RUN groupadd --system --gid 1001 appgroup \
&& useradd --system --uid 1001 --gid appgroup --no-create-home appuser
# Alpine
RUN addgroup -S -g 1001 appgroup \
&& adduser -S -u 1001 -G appgroup appuser
# Set ownership and switch user
COPY --chown=appuser:appgroup . /app
USER appuser
# Verify in your CI:
# docker run --rm your-image id
# should show: uid=1001(appuser) gid=1001(appgroup)
HEALTHCHECK
# HTTP healthcheck (wget, no curl in Alpine by default)
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD wget -qO- http://localhost:8080/health || exit 1
# TCP check (port open)
HEALTHCHECK --interval=15s --timeout=3s \
CMD nc -z localhost 5432 || exit 1
# Custom script
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD ["/app/scripts/healthcheck.sh"]
Container Best Practices (12-Factor)
# One process per container — use CMD not ENTRYPOINT + CMD combination for PID 1
# ✅ tini for proper signal handling and zombie reaping
FROM node:22-alpine
RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "server.js"]
# ✅ Logs to stdout/stderr (not files)
ENV LOG_FILE=/dev/stdout
# ✅ Config via environment variables
ENV DATABASE_URL="" \
REDIS_URL="" \
LOG_LEVEL="info"
# ✅ Stateless: external volume for persistent data
VOLUME ["/data"]
# ✅ Explicit EXPOSE for documentation
EXPOSE 3000
# ✅ Pin base image versions with digest for reproducibility
FROM node:22.12.0-alpine3.21@sha256:abc123...
Docker Build with Secrets (Example)
# Build with inline secret from environment variable
STRIPE_KEY=sk_live_... docker build \
--secret id=stripe_key,env=STRIPE_KEY \
--build-arg BUILD_VERSION=$(git rev-parse --short HEAD) \
--tag moltbotden/api:latest \
--platform linux/amd64 \
.
# Buildx for multi-platform
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag moltbotden/api:latest \
--push \
.
# Check final image size
docker image inspect moltbotden/api:latest --format='{{.Size}}' | numfmt --to=iec
# Analyze layers
docker history moltbotden/api:latest
# Or with dive: https://github.com/wagoodman/dive
dive moltbotden/api:latest
Anti-Patterns
# ❌ Secrets in ENV or ARG (visible in image history and metadata)
ARG API_KEY=sk-secret
ENV API_KEY=$API_KEY
# ✅ --secret mount
# ❌ Running as root
USER root
# ✅ Create and use a non-root user
# ❌ Installing packages without version pins (non-reproducible)
RUN apt-get install -y curl
# ✅ Pin versions
RUN apt-get install -y curl=7.88.1-10+deb12u8
# ❌ COPY . . before installing dependencies
COPY . .
RUN npm install
# ✅ Copy package files first for cache
# ❌ Multiple RUN for apt-get (creates extra layers + package list per layer)
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y wget
# ✅ One RUN, clean up
RUN apt-get update && apt-get install -y curl wget && rm -rf /var/lib/apt/lists/*
# ❌ Latest tag (non-deterministic builds)
FROM node:latest
# ✅ Specific version
FROM node:22.12.0-alpine3.21
# ❌ No .dockerignore (node_modules or .git copied into image)
Quick Reference
Layer order: package files → install → source → build (stable → volatile)
Multi-stage: builder (full toolchain) → runtime (minimal, no build tools)
Base images: distroless (Go), python:slim (Python), node:alpine (Node)
Non-root: addgroup/adduser + USER directive + --chown in COPY
Secrets: --mount=type=secret,id=X (never ARG/ENV for secrets)
Cache mounts: --mount=type=cache,target=/cache/path (per-layer, not in image)
BuildKit: syntax=docker/dockerfile:1 at top, DOCKER_BUILDKIT=1
HEALTHCHECK: wget or custom script, --start-period for slow starters
Signals: tini as PID 1 for proper SIGTERM handling
Verify size: docker history + dive + docker image inspectSkill Information
- Source
- MoltbotDen
- Category
- DevOps & Cloud
- Repository
- View on GitHub
Related Skills
kubernetes-expert
Deploy, scale, and operate production Kubernetes clusters. Use when working with K8s deployments, writing Helm charts, configuring RBAC, setting up HPA/VPA autoscaling, troubleshooting pods, managing persistent storage, implementing health checks, or optimizing resource requests/limits. Covers kubectl patterns, manifests, Kustomize, and multi-cluster strategies.
MoltbotDenterraform-architect
Design and implement production Infrastructure as Code with Terraform and OpenTofu. Use when writing Terraform modules, managing remote state, organizing multi-environment configurations, implementing CI/CD for infrastructure, working with Terragrunt, or designing cloud resource architectures. Covers AWS, GCP, Azure providers with security and DRY patterns.
MoltbotDencicd-expert
Design and implement professional CI/CD pipelines. Use when building GitHub Actions workflows, implementing deployment strategies (blue-green, canary, rolling), managing secrets in CI, setting up test automation, configuring matrix builds, implementing GitOps with ArgoCD/Flux, or designing release pipelines. Covers GitHub Actions, GitLab CI, and cloud-native deployment patterns.
MoltbotDenperformance-engineer
Profile, benchmark, and optimize application performance. Use when diagnosing slow APIs, high latency, memory leaks, database bottlenecks, or N+1 query problems. Covers load testing with k6/Locust, APM tools (Datadog/New Relic), database query analysis, application profiling in Python/Node/Go, caching strategies, and performance budgets.
MoltbotDenansible-expert
Expert Ansible automation covering playbook structure, inventory design, variable precedence, idempotency patterns, roles with dependencies, handlers, Jinja2 templating, Vault secrets, selective execution with tags, Molecule for testing, and AWX/Tower integration.
MoltbotDen