
A well-written Dockerfile produces small, secure, fast-building images. Multi-stage builds separate build dependencies from runtime. Layer order matters — put rarely-changing layers first to maximize cache reuse.
| Item | Syntax | Description |
|---|---|---|
| FROM | FROM ubuntu:22.04 / FROM python:3.12-slim | Base image — use official slim/alpine variants |
| FROM multi-stage | FROM golang:1.22 AS builder ... FROM scratch | Build in one stage, copy binary to minimal image |
| AS builder | FROM node:20 AS builder | Name a build stage for reference |
| WORKDIR | WORKDIR /app | Set working directory — creates if not exists |
| COPY | COPY src/ /app/src/ | Copy files from build context to image |
| COPY --from | COPY --from=builder /app/binary /usr/local/bin/ | Copy from another build stage |
| RUN | RUN apt-get update && apt-get install -y curl | Execute command — creates new layer |
| RUN chain | RUN cmd1 && cmd2 && cmd3 | Chain commands to reduce layer count |
| ENV | ENV APP_ENV=production PORT=8080 | Set environment variables in image |
| ARG | ARG VERSION=1.0 | Build-time variable (not in final image) |
| EXPOSE | EXPOSE 8080 | Document which port the app listens on |
| USER | USER nobody / USER 1000:1000 | Run as non-root for security |
| CMD | CMD ["python", "app.py"] | Default command to run (can be overridden) |
| ENTRYPOINT | ENTRYPOINT ["/docker-entrypoint.sh"] | Fixed command — CMD becomes arguments |
| HEALTHCHECK | HEALTHCHECK CMD curl -f http://localhost/health || exit 1 | Container health check |
| LABEL | LABEL maintainer='team@mywebuniversity.com' | Add metadata to image |
| .dockerignore | .git node_modules __pycache__ *.pyc | Exclude files from build context |
| Layer caching | Order: rarely-changed → often-changed | COPY requirements.txt before COPY . to cache pip install |
| Alpine | FROM python:3.12-alpine | Smallest base — 5MB, but uses musl libc |
| slim | FROM python:3.12-slim | Debian minimal — good balance of size and compatibility |
| distroless | FROM gcr.io/distroless/python3 | No shell, no package manager — most secure |
| Pinned versions | FROM ubuntu:22.04 not FROM ubuntu:latest | Pin exact versions for reproducibility |
| Non-root | USER nobody after installing packages | Never run containers as root in production |
| Secrets | Use BuildKit secrets: --secret id=mysecret,src=.secret | Never COPY secret files into image |
# ── Production-ready Dockerfile (Python API) ─────────────────
# Dockerfile
FROM python:3.12-slim AS base
# Metadata
LABEL maintainer="team@mywebuniversity.com" \
version="2.0" \
description="MyWebUniversity API"
# Build-time variables
ARG ENVIRONMENT=production
# System dependencies (rarely change — cache first)
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Python environment
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# App directory
WORKDIR /app
# Install Python deps FIRST (separate layer — cached unless requirements changes)
COPY requirements.txt .
RUN pip install -r requirements.txt
# Copy application code (changes most often — last layer)
COPY src/ ./src/
COPY config/ ./config/
# Create non-root user AFTER installing packages
RUN addgroup --system app && adduser --system --ingroup app app
USER app
# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
EXPOSE 8080
CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8080"]
# ── Multi-stage: Go binary ────────────────────────────────────
# Stage 1: Build
FROM golang:1.22-alpine AS builder
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o server ./cmd/server
# Stage 2: Minimal runtime
FROM scratch
COPY --from=builder /build/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
ENTRYPOINT ["/server"]
# Final image: just the binary — typically <10MB
# ── Multi-stage: Node.js ──────────────────────────────────────
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:20-alpine AS builder
WORKDIR /app
COPY . .
COPY --from=deps /app/node_modules ./node_modules
RUN npm run build
FROM node:20-alpine AS runtime
WORKDIR /app
RUN addgroup -g 1001 nodejs && adduser -u 1001 -G nodejs nextjs
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
USER nextjs
EXPOSE 3000
CMD ["npm", "start"]
# ── .dockerignore ─────────────────────────────────────────────
cat > .dockerignore << 'EOF'
.git
.gitignore
.github
node_modules
__pycache__
*.pyc
*.pyo
*.egg-info
.env
.env.*
*.md
tests/
docs/
Dockerfile*
docker-compose*
.dockerignore
EOF
# Build commands
docker build -t mywebuniversity/api:latest .
docker build --target builder -t myapp-builder . # build only one stage
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest . --push