Skip to content

Docker

Docker builds, distributes, and runs OCI-compatible container images. Learn the image and process model first; Docker commands are controls over that model, not a replacement for it.

Essential Workflow

docker build -t example/app:dev .
docker run --rm -p 8080:8080 example/app:dev
docker ps
docker logs <container>
docker inspect <container>
docker exec -it <container> sh
docker stop <container>

Use docker exec for temporary diagnosis, not to repair production state manually. Rebuild and redeploy the image so the fix is reproducible.

A Small Production Dockerfile

# syntax=docker/dockerfile:1
FROM python:3.13-slim AS build
WORKDIR /app
COPY requirements.txt .
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt

FROM python:3.13-slim
RUN useradd --create-home --uid 10001 app
WORKDIR /app
COPY --from=build /wheels /wheels
COPY requirements.txt .
RUN pip install --no-cache-dir --no-index --find-links=/wheels -r requirements.txt \
    && rm -rf /wheels
COPY --chown=app:app . .
USER app
EXPOSE 8080
CMD ["python", "-m", "app"]

Adapt the base and package installation to the application. Pin dependencies through the ecosystem's lock or hash mechanism and regularly rebuild from a maintained base.

Build Rules

  • Keep .dockerignore narrow enough to exclude .git, local environments, caches, secrets, and build output.
  • Copy dependency manifests before frequently changing source to preserve useful cache layers.
  • Combine commands only when they form one installation transaction and cleanup belongs with it.
  • Use multi-stage builds to keep compilers and source-only tooling out of runtime images.
  • Use BuildKit secret or SSH mounts for build credentials; ARG, ENV, copied files, and later deletion can leave secrets in image history.
  • Build once and promote the same digest through environments.

The maintained Docker build guidance is the source for current builder behavior.

Dockerfile Semantics

  • FROM selects a base and begins a stage.
  • RUN executes during image construction and creates a layer.
  • COPY adds build-context files.
  • WORKDIR sets the working directory for later instructions.
  • ENV persists runtime environment defaults; do not store secrets there.
  • USER sets the runtime user.
  • ENTRYPOINT defines the executable; CMD provides its default command or arguments.
  • EXPOSE documents a port but does not publish it.

Prefer JSON/exec form for the main process so it receives signals directly:

CMD ["server", "--port", "8080"]

Storage

  • writable container layer: ephemeral, tied to the container;
  • named volume: engine-managed persistent storage;
  • bind mount: a specific host path made visible in the container;
  • tmpfs: memory-backed temporary data where supported.

Bind mounts couple workloads to host paths and permissions. Volumes do not create backups by themselves. Never assume deleting a container deletes or preserves its data—inspect the mount type and lifecycle.

Networking

Create a user-defined network for name-based discovery between containers:

docker network create app-net
docker run -d --name db --network app-net postgres:17
docker run --rm --network app-net example/app:dev

Inside the application container, localhost means that container, not the database or host. Publish only required ports, and bind to a specific host interface when exposure should be limited.

Compose for Local Multi-Container Work

services:
  app:
    build: .
    ports: ["8080:8080"]
    depends_on: [db]
  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets: [db_password]
    volumes: [db-data:/var/lib/postgresql/data]

secrets:
  db_password:
    file: ./local-db-password.txt
volumes:
  db-data:

depends_on controls startup order, not application readiness. The application still needs connection retries with a bounded deadline. Compose is useful for development and small deployments; it is not a Kubernetes compatibility layer.

Resource and Security Controls

docker run --rm \
  --read-only \
  --cap-drop=ALL \
  --security-opt=no-new-privileges \
  --memory=512m \
  --cpus=1.5 \
  --pids-limit=200 \
  example/app:dev

Add a writable tmpfs or volume only where the process needs it. Test limits under realistic load; resource controls that are lower than startup or peak needs cause restarts and throttling.

Prefer rootless Docker where compatible, but still apply least privilege inside containers. Avoid mounting /var/run/docker.sock; access commonly grants host-equivalent control.

Debugging

docker logs --since=10m <container>
docker stats <container>
docker top <container>
docker inspect --format '{{json .State}}' <container>
docker diff <container>
docker image history example/app:dev

Check the exit code, out-of-memory state, health, mounts, configuration, network membership, and emitted logs. Minimal images may lack a shell; use platform-supported debug tooling or reproduce in a diagnostic image instead of expanding the production image permanently.

Image Supply Chain

  • use trusted, maintained bases and pin deployment by digest;
  • generate a software bill of materials where required;
  • scan dependencies and images, then prioritize reachable and exploitable risk;
  • sign or attest artifacts in a verified build pipeline;
  • rebuild rather than patch running containers;
  • enforce registry retention without deleting deployed content.

Checklist

  • Is the build context minimal and secret-free?
  • Does a multi-stage build exclude unnecessary build tooling?
  • Does the process run as a non-root user and receive signals?
  • Are mutable and durable paths explicit?
  • Are ports, mounts, capabilities, and resources minimal?
  • Is the deployed image identified immutably and reproducible?
  • Can the container be diagnosed without manual mutation?