| 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 |
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:1Pin 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-templateUse cache mounts for package managers:
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txtAPT 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-cacheNever use ARG/ENV for secrets:
RUN --mount=type=secret,id=api_key curl -H "Authorization: $(cat /run/secrets/api_key)" https://api.example.comUse 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.0Never 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 upUse specific image tags - Not
:latestDefine 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