name: dockerfile-best-practices description: "Create and optimize Dockerfiles with BuildKit, multi-stage builds, advanced caching, and security. Use when: (1) Creating new Dockerfile, (2) Optimizing existing Dockerfile, (3) Reducing image size, (4) Improving security, (5) Using Python with uv, (6) Resolving cache or slow build issues, (7) Setting up CI/CD builds" license: MIT compatibility: "Claude Code, Claude.ai" allowed-tools: "Read Grep Glob Bash Write Edit" metadata: version: "1.0.1" category: development tags: - docker - dockerfile - containers - buildkit - optimization - security - devops - ci-cd triggers: - "create dockerfile" - "optimize dockerfile" - "docker best practices" - "reduce image size" - "docker security" - "multi-stage build" - "buildkit" surfaces: - claude-code - claude-ai author: name: obeone url: https://github.com/obeone
Dockerfile Best Practices
Comprehensive guide for creating optimized, secure, and fast Docker images using modern BuildKit features.
Quick Start Workflow
When working with Dockerfiles, follow this decision tree:
- Choose your language/framework → Use appropriate template below
- Apply security hardening → Non-root user, pin versions, secrets management
- Optimize for cache → Separate deps from code, use cache mounts
- Multi-stage if needed → Separate build from runtime for compiled languages
- Review with analyzer → Run
scripts/analyze_dockerfile.py
Essential Rules (Always Apply)
-
Start with BuildKit syntax:
# syntax=docker/dockerfile:1 -
Pin runtime versions, not OS versions:
# ✅ GOOD - Pin runtime, let OS update FROM python:3.12-slim # ❌ BAD - Pins OS version (bookworm), prevents security updates FROM python:3.12-slim-bookwormWhy? OS versions update with security patches. Pin runtime (python:3.12) for reproducible behavior.
-
Create
.dockerignore: Use template fromassets/dockerignore-template -
Use cache mounts for package managers:
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt -
APT cache setup (before any apt operations):
RUN rm -f /etc/apt/apt.conf.d/docker-clean; \ echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache -
Never use ARG/ENV for secrets:
RUN --mount=type=secret,id=api_key curl -H "Authorization: $(cat /run/secrets/api_key)" https://api.example.com -
Use non-root user with safe UID/GID:
# Let system auto-assign (simple) RUN groupadd -r app && useradd -r -g app app # Or use UID/GID >10000 for consistency across environments RUN groupadd -r -g 10001 app && useradd -r -u 10001 -g app app
Language-Specific Templates
Python (with uv - Recommended)
For detailed Python/uv integration, see references/uv_integration.md.
# syntax=docker/dockerfile:1
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
WORKDIR /app
# Install dependencies (separate layer for caching)
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --locked --no-install-project
# Copy and install project
COPY . .
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked
# Security: non-root user with UID/GID >10000
RUN groupadd -r -g 10001 app && \
useradd -r -u 10001 -g app app && \
chown -R app:app /app
USER app
ENV PATH="/app/.venv/bin:$PATH"
CMD ["python", "-m", "myapp"]
Node.js
# syntax=docker/dockerfile:1
FROM node:20-alpine
WORKDIR /app
# Install dependencies
COPY package.json yarn.lock ./
RUN --mount=type=cache,target=/root/.yarn \
yarn install --frozen-lockfile --production
# Copy source
COPY . .
# Security: non-root user with UID/GID >10000
RUN addgroup -g 10001 app && \
adduser -u 10001 -G app -S app && \
chown -R app:app /app
USER app
EXPOSE 3000
CMD ["node", "index.js"]
Go (Multi-stage)
# syntax=docker/dockerfile:1
# Build stage
FROM golang:1-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o main
# Runtime stage
FROM alpine:3
RUN addgroup -g 10001 app && adduser -u 10001 -G app -S app
USER app
COPY --from=builder /app/main /main
ENTRYPOINT ["/main"]
Debian-based (with APT cache)
# syntax=docker/dockerfile:1
FROM debian:stable-slim
# Configure APT for cache reuse
RUN rm -f /etc/apt/apt.conf.d/docker-clean; \
echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
WORKDIR /app
# Install dependencies with cache mount
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && \
apt-get install -y --no-install-recommends curl ca-certificates
# Copy application
COPY . .
# Security: non-root user with UID/GID >10000
RUN groupadd -r -g 10001 app && \
useradd -r -u 10001 -g app app && \
chown -R app:app /app
USER app
CMD ["./app"]
PHP (with Composer)
# syntax=docker/dockerfile:1
FROM php:8-fpm-alpine
WORKDIR /app
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Install dependencies
COPY composer.json composer.lock ./
RUN --mount=type=cache,target=/tmp/cache \
composer install --no-dev --optimize-autoloader --no-scripts
# Copy source
COPY . .
RUN composer dump-autoload --optimize
# Security: non-root user with UID/GID >10000
RUN addgroup -g 10001 app && \
adduser -u 10001 -G app -S app && \
chown -R app:app /app
USER app
EXPOSE 9000
CMD ["php-fpm"]
Security Checklist
- [ ] Use specific version tags for base images
- [ ] Use minimal base image (alpine, slim, distroless)
- [ ] Create and use non-root user
- [ ] Never expose secrets via ARG/ENV
- [ ] Use
RUN --mount=type=secretfor sensitive data - [ ] Add HEALTHCHECK instruction
- [ ] Scan image for vulnerabilities (
docker scan)
Performance Checklist
- [ ] Add BuildKit syntax directive
- [ ] Create comprehensive
.dockerignore - [ ] Order instructions: manifests → deps → code
- [ ] Use
--mount=type=cachefor all package managers - [ ] Implement multi-stage builds for compiled languages
- [ ] Chain RUN commands with
&& - [ ] Clean up in same RUN instruction
Size Optimization
Quick wins:
- Use alpine or slim variants
- Multi-stage builds
- Remove unnecessary files in same layer
- Use
--no-install-recommendswith apt - Consider distroless for runtime
Common Patterns
Intermediate Layers (Faster Rebuilds)
Separate dependency installation from code copy:
# Install deps first (rarely changes)
COPY package.json package-lock.json ./
RUN npm ci --production
# Copy code (changes frequently)
COPY . .
Remote Cache (CI/CD)
docker buildx build \
--cache-from=type=registry,ref=myregistry.com/app:cache \
--cache-to=type=registry,ref=myregistry.com/app:cache,mode=max \
--push \
-t myregistry.com/app:latest .
Secret Management
# Build with secret
docker buildx build --secret id=api_key,src=./key.txt .
# Or from environment
docker buildx build --secret id=api_key,env=API_KEY .
Tools and Scripts
Analyze Dockerfile
python scripts/analyze_dockerfile.py ./Dockerfile
Detects common anti-patterns:
- Missing BuildKit syntax
- Using :latest tags
- ADD instead of COPY
- Missing cache mounts
- Secrets in ARG/ENV
- Missing non-root USER
- apt-get without cleanup
Generate .dockerignore
cp assets/dockerignore-template .dockerignore
Docker Compose Best Practices
For multi-container applications, follow modern Compose practices:
Key Rules
-
Don't use
version:field - Deprecated since Compose V2# ✅ GOOD - No version field services: app: image: myapp:1.0.0 -
Never use
container_name:- Prevents scaling and parallel environments# ✅ GOOD - Let Compose generate names services: app: image: myapp:1.0.0 # No container_name - allows scaling with --scale # Use project names for environment isolation: # docker compose -p myapp-dev up # docker compose -p myapp-test up -
Use specific image tags - Not
:latest -
Define health checks - For service dependencies
-
Set resource limits - Prevent resource exhaustion
For complete Compose guide, see references/compose_best_practices.md.
Reference Documentation
For detailed information, consult these references:
references/optimization_guide.md- Complete optimization guide with BuildKit, caching, multi-stage buildsreferences/best_practices.md- Checklist of all best practices with impact levelsreferences/examples.md- Real-world before/after optimization examplesreferences/uv_integration.md- Python with uv package manager (recommended for Python)references/compose_best_practices.md- Docker Compose modern practices (no version:, no container_name:)
Common Issues and Solutions
Slow builds
- Add cache mounts:
RUN --mount=type=cache,target=... - Optimize layer ordering: deps before code
- Use remote cache in CI/CD
Large images
- Use multi-stage builds
- Switch to alpine/slim base images
- Clean up in same RUN instruction
- Remove dev dependencies
Security concerns
- Pin versions with SHA256
- Use non-root USER
- Never use ARG/ENV for secrets
- Use BuildKit secret mounts
- Scan with
docker scan
Cache invalidation
- Separate dependency installation from code
- Use
--mount=type=bindfor manifests - Order instructions correctly
Examples by Use Case
CLI Tool
# syntax=docker/dockerfile:1
FROM python:3.12-alpine
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/
WORKDIR /app
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --locked --no-install-project
COPY . .
RUN --mount=type=cache,target=/root/.cache/uv uv sync --locked
ENV PATH="/app/.venv/bin:$PATH"
ENTRYPOINT ["mytool"]
Web API
# syntax=docker/dockerfile:1
FROM node:20-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN --mount=type=cache,target=/root/.yarn yarn install --frozen-lockfile --production
COPY . .
RUN addgroup -g 10001 app && \
adduser -u 10001 -G app -S app && \
chown -R app:app /app
USER app
EXPOSE 3000
HEALTHCHECK --interval=30s CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "server.js"]
Static Site
# syntax=docker/dockerfile:1
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json yarn.lock ./
RUN --mount=type=cache,target=/root/.yarn yarn install --frozen-lockfile
COPY . .
RUN yarn build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
Build Commands Reference
# Basic build
docker buildx build -t myapp:latest .
# With cache from registry
docker buildx build \
--cache-from=type=registry,ref=registry.com/myapp:cache \
--cache-to=type=registry,ref=registry.com/myapp:cache,mode=max \
-t myapp:latest .
# With secrets
docker buildx build \
--secret id=api_key,src=./key.txt \
-t myapp:latest .
# Multi-platform
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t myapp:latest --push .
# Target specific stage
docker buildx build --target builder -t myapp:builder .
Next Steps
After creating your Dockerfile:
- Run analyzer:
python scripts/analyze_dockerfile.py Dockerfile - Test locally:
docker buildx build -t test . - Check size:
docker images test - Scan for vulnerabilities:
docker scout cves test - Profile build:
docker buildx build --progress=plain . 2>&1 | less
Docker Compose Architect
DevOps
Designs optimized Docker Compose configurations.
Incident Postmortem Writer
DevOps
Writes structured and blameless incident postmortem reports.
Runbook Creator
DevOps
Creates clear operational runbooks for common DevOps procedures.