| name | docker |
| description | Production-grade Docker and container best practices. Use when containerizing apps with Docker,
writing Dockerfiles, or managing Docker Compose environments.
"multi-stage build", "image size", "docker-compose". Use ONLY when a Dockerfile or container registry is the explicit target for deployment. NOT for serverless or Cloudflare Workers. NOT for generic app deployment without containers. NOT for orchestrating CI/CD pipelines natively (use github-actions instead). Do NOT trigger for generic "deploy my app" requests without clarifying the target platform.
|
Docker & Container Engineering Standards
This skill codifies 2026 Docker best practices — secure, minimal, reproducible container images using BuildKit, multi-stage builds, and Compose v2.
1. Dockerfile Fundamentals
Always Start With BuildKit Syntax
# syntax=docker/dockerfile:1
Place this as the first line of every Dockerfile. It enables:
- Parallel stage execution
- Cache mounts (
--mount=type=cache)
- Secret mounts (
--mount=type=secret)
- Reproducible builds across Docker versions
Base Image Selection
| Use Case | Recommended Base | Why |
|---|
| Node.js | node:22-slim | Debian Slim — small, has essential libs |
| Python | python:3.13-slim | Minimal Debian, no build tools |
| Go | scratch or distroless | Static binary needs nothing |
| General | debian:bookworm-slim | Stable, well-patched, small |
- NEVER use
:latest — always pin to a specific version tag
- Prefer
-slim variants over full images to reduce attack surface
- Consider
distroless for production — no shell, no package manager = minimal attack surface
2. Multi-Stage Builds (Required for Production)
Multi-stage builds are mandatory for any production image. They separate build-time dependencies from runtime.
See templates.md for the full annotated Multi-Stage Build template.
Key Rules
- Name every stage:
FROM ... AS builder — makes builds readable and targetable
- Copy only artifacts: Use
COPY --from=builder to cherry-pick built files
- Never install dev dependencies in the runtime stage
- The runtime stage should have ZERO build tools (no compilers, no git, no curl)
3. Security Hardening
Non-Root Execution (Mandatory)
# Create a system user with no home directory, no login shell
RUN groupadd -r appgroup && useradd -r -g appgroup -s /usr/sbin/nologin appuser
# Switch to the non-root user BEFORE CMD
USER appuser
- NEVER run containers as root in production
- NEVER use
--privileged flag unless absolutely required
- Set
USER as late as possible — after all RUN commands that need root
Secret Handling
# ✅ Good: BuildKit secret mount (never stored in layers)
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) \
npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN
# ❌ Bad: ARG/ENV secrets (visible in image history)
ARG NPM_TOKEN
ENV NPM_TOKEN=$NPM_TOKEN
- NEVER use
ARG or ENV for secrets — they are baked into image layers
- NEVER
COPY .env files into the image
- Use
--mount=type=secret (BuildKit) for build-time secrets
- Use Docker Compose
secrets: or orchestrator secrets for runtime
Vulnerability Scanning
docker scout cves <image>
trivy image <image>
grype <image>
- Run scanning in CI — hard-fail on HIGH/CRITICAL vulnerabilities
- Never use
continue-on-error: true for security gates
4. Layer Optimization
Instruction Ordering
Docker caches each layer. Order instructions from least-changing to most-changing:
# 1. Base image (rarely changes)
FROM node:22-slim
# 2. System deps (changes infrequently)
RUN apt-get update && apt-get install -y --no-install-recommends \
dumb-init \
&& rm -rf /var/lib/apt/lists/*
# 3. Application deps (changes when lock file changes)
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
# 4. Application code (changes most frequently)
COPY . .
RUN pnpm build
Cache Mounts (BuildKit)
# Cache package manager downloads between builds
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
.dockerignore (Required)
Create a .dockerignore in every project with a Dockerfile:
.git
.github
node_modules
dist
*.md
.env*
.vscode
.idea
tmp/
coverage/
- ALWAYS exclude
.git — it can be hundreds of MB
- ALWAYS exclude
node_modules — reinstall inside the container
- ALWAYS exclude
.env* — prevents secret leaks
5. Advanced Examples & CI/CD
For advanced Docker Compose configurations and GitHub Actions CI/CD workflows, see the reference file:
Read: references/advanced-examples.md
7. Anti-Patterns (Never Do These)
| Anti-Pattern | Why It's Wrong | Do This Instead |
|---|
FROM ubuntu:latest | Unpinned, large, unpredictable | Pin version, use -slim |
RUN apt-get update alone | Cache goes stale across builds | Combine with install in one RUN |
ADD for local files | Unpredictable (auto-extracts) | Use COPY explicitly |
Multiple RUN apt-get | Creates unnecessary layers | Chain with && in one RUN |
COPY . . before deps | Breaks layer cache on every code change | Copy lock file first, install, then copy source |
| Running as root | Security vulnerability | Create and switch to appuser |
Secrets in ENV/ARG | Visible in image history | Use --mount=type=secret |
No .dockerignore | Bloated context, potential secret leaks | Always create one |