Docker Cheatsheet

Best Practices

Use this Docker reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Image Size

  • Use minimal base images: alpine, slim, distroless, or scratch.
  • Multi-stage builds — never ship compilers, test deps, or source to production.
  • Combine RUN commands with && and clean up in the same layer:
RUN apt-get update && apt-get install -y --no-install-recommends \
      curl ca-certificates \
    && rm -rf /var/lib/apt/lists/*
  • Use .dockerignore to exclude everything not needed in the build context.

Layer Cache Efficiency

# Copy dependency manifests BEFORE source code
COPY package*.json ./
RUN npm ci --omit=dev

# Source changes only bust layers from here
COPY . .
  • Order instructions from least to most frequently changed.
  • Use --mount=type=cache (BuildKit) for package manager caches:
# syntax=docker/dockerfile:1
RUN --mount=type=cache,target=/root/.npm \
    npm ci --omit=dev

Security Hardening

# Never run as root
RUN addgroup -S app && adduser -S -G app app
USER app

# Read-only root filesystem at runtime
# docker run --read-only --tmpfs /tmp myapp

# Drop all Linux capabilities and add only what's needed
# docker run --cap-drop ALL --cap-add NET_BIND_SERVICE myapp
PracticeCommand / Config
Non-root userUSER app in Dockerfile
Read-only rootfsdocker run --read-only
No new privilegesdocker run --security-opt no-new-privileges
Drop capabilitiesdocker run --cap-drop ALL
Pin image digestsFROM nginx@sha256:abc...
Scan imagesdocker scout cves, trivy image

Reproducibility

# Pin base image tags (never use bare "latest" in production Dockerfiles)
FROM node:22.14-alpine3.21

# Or pin by digest for true immutability
FROM node:22@sha256:abc123...
  • Pin dependency versions in package-lock.json, requirements.txt, go.sum.
  • Pass GIT_COMMIT and BUILD_DATE as ARG/LABEL for traceability.

Health Checks

HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
  CMD wget -qO- http://localhost:3000/health || exit 1
  • Use /health or /healthz endpoint — check DB connectivity too.
  • depends_on: condition: service_healthy in Compose waits for this.

Secrets

  • Never put secrets in ENV instructions or ARG — they appear in docker history.
  • Inject at runtime: -e SECRET=..., --env-file .env, Docker secrets, or secret mounts.
# BuildKit secret mount (never stored in layer)
# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm ci
docker build --secret id=npmrc,src=$HOME/.npmrc .

Signals and Graceful Shutdown

# Exec form ensures PID 1 = your process, receives signals directly
CMD ["node", "server.js"]

# If you need a shell wrapper, use tini
FROM node:22-alpine
RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "server.js"]

Logging

  • Write logs to stdout/stderr — let Docker (and the orchestrator) collect them.
  • Never write logs to files inside the container; the filesystem is ephemeral.
  • Set --log-opt max-size=10m --log-opt max-file=3 to cap disk usage.

Compose for Local Dev

services:
  app:
    build:
      context: .
      target: dev           # dev stage includes hot-reload tooling
    volumes:
      - .:/app
      - /app/node_modules   # anonymous volume hides host node_modules
    environment:
      - NODE_ENV=development

General Rules

DoAvoid
Use multi-stage buildsSingle-stage images with build tools
Pin image tags/digestsFROM ubuntu:latest in production
Run as non-rootUSER root
One process per containerBundling app + database in one image
Use named volumes for stateStoring DB data in container layer
Validate with docker scout / trivyShipping unscanned images
Set WORKDIR explicitlyRelying on / as working directory
Label images with OCI metadataNo traceability
Use --init or tiniPID 1 zombie reaping problems

Useful One-liners

# Remove all stopped containers, unused networks, dangling images, build cache
docker system prune -af --volumes

# Show which processes are using the most disk
docker system df -v

# Get a shell in a distroless/scratch container via a debug image
docker run -it --pid=container:myapp --network=container:myapp \
  busybox sh

# Check open ports inside a container without exec
docker inspect -f '{{.NetworkSettings.Ports}}' mycontainer

# Follow logs for multiple services in Compose
docker compose logs -f web api