Docker Runbook & Cheatsheet
Docker Runbook & Cheatsheet
Docker packages an application and its dependencies into an image, then runs it as an isolated container. Part one goes install → run → build → persist → network → Compose → ship; part two is the reference: CLI, Dockerfile instructions, Compose keys, and hardening.
Install
# Windows (PowerShell) - Desktop, with WSL2 backend winget install -e --id Docker.DockerDesktop # Linux / inside WSL2 - Engine only, no Desktop licence curl -fsSL https://get.docker.com | sh sudo usermod -aG docker $USER # log out and back in docker version docker run hello-world
Windows performance Under WSL2, bind-mounting source from /mnt/c crosses a 9P share and is roughly ten times slower than the WSL2 native filesystem. Keep repositories under ~/ inside the distro. Also: the docker group is effectively root-equivalent on the host.
Run containers
# Detached, named, port-mapped, auto-restarting docker run -d --name web -p 8080:80 --restart unless-stopped nginx:alpine docker ps # running containers docker ps -a # including stopped docker logs -f web # follow output docker exec -it web sh # shell inside docker stop web && docker rm web # Throwaway container, removed on exit docker run --rm -it alpine sh
Port syntax -p HOST:CONTAINER. Bind to one interface with -p 127.0.0.1:8080:80 so the service is not exposed on every network interface by accident.
Build an image
Build in one stage, copy only the artifact into a small runtime stage. Smaller image, fewer packages, smaller attack surface.
# --- build stage --- FROM golang:1.23-alpine AS build WORKDIR /src COPY go.mod go.sum ./ RUN go mod download # cached unless deps change COPY . . RUN CGO_ENABLED=0 go build -o /out/app ./cmd/app # --- runtime stage --- FROM alpine:3.20 RUN adduser -D -u 10001 app COPY --from=build /out/app /usr/local/bin/app USER app EXPOSE 8080 HEALTHCHECK --interval=30s --timeout=3s \ CMD wget -qO- http://localhost:8080/healthz || exit 1 ENTRYPOINT ["app"]
# .dockerignore - keep junk out of the build context .git node_modules *.log .envdocker build -t bubim/app:1.0 . docker build -t bubim/app:1.0 --no-cache . docker images
Layer caching Order instructions least-changing first: copy dependency manifests and install, then copy source. Reversing that invalidates the cache on every edit and makes builds crawl.
Persist data
# Named volume - Docker manages it; the right default for data docker volume create pgdata docker run -d --name db -v pgdata:/var/lib/postgresql/data postgres:16 # Bind mount - a host path; for source code in development docker run -d -v /srv/site:/usr/share/nginx/html:ro nginx:alpine # tmpfs - in memory, never written to disk (secrets, scratch) docker run --tmpfs /tmp:rw,noexec,nosuid alpine docker volume ls docker volume inspect pgdata
| Type | Use for |
|---|---|
| volume | Databases and app state. Portable, backed up as a unit. |
| bind mount | Live source code during development; host config files. |
| tmpfs | Sensitive or scratch data that must not persist. |
Anything not in a volume is gone when the container is removed. Add :ro to any mount the container only needs to read.
Networking
docker network create appnet docker run -d --name db --network appnet postgres:16 docker run -d --name web --network appnet -p 8080:80 bubim/app:1.0 # 'web' reaches the database at the hostname 'db' docker network ls docker network inspect appnetDNS by container name On a user-defined network, containers resolve each other by name — no linking, no IPs. The default bridge does not do this, which is why you should always create your own network.
Compose a stack
Compose declares a multi-container stack in one file — the practical unit for anything beyond a single container.
services: web: image: bubim/app:1.0 build: . ports: - "127.0.0.1:8080:8080" environment: DB_HOST: db env_file: [.env] depends_on: db: condition: service_healthy restart: unless-stopped db: image: postgres:16 volumes: - pgdata:/var/lib/postgresql/data environment: POSTGRES_PASSWORD_FILE: /run/secrets/db_pw secrets: [db_pw] healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s retries: 5 restart: unless-stopped volumes: pgdata: secrets: db_pw: file: ./db_password.txt
docker compose up -d # start the stack docker compose ps docker compose logs -f web docker compose down # stop and remove (volumes survive)
Modern syntax Use docker compose (v2 plugin, space) rather than the old docker-compose binary. The version: key at the top of the file is obsolete and can be dropped.
Tag & push
docker login registry.example.com docker tag bubim/app:1.0 registry.example.com/bubim/app:1.0 docker push registry.example.com/bubim/app:1.0 docker pull registry.example.com/bubim/app:1.0 # Save / load without a registry (air-gapped transfer) docker save bubim/app:1.0 | gzip > app-1.0.tar.gz gunzip -c app-1.0.tar.gz | docker loadNever deploy :latest Tag with an immutable version. latest is just a default tag, not a pointer to the newest build, and it makes rollbacks and reproducibility impossible.
Maintain
docker stats # live CPU / memory per container docker system df # what is using disk docker inspect web # full config as JSON # Reclaim: dangling images, stopped containers, unused networks docker system prune # DANGER: -a also removes unused images; --volumes deletes DATA docker system prune -a --volumes
Read the flags prune –volumes destroys named volumes not currently attached to a container — that is your database. Run plain docker system prune unless you are certain.
Command line
| Command | Does |
|---|---|
| docker run IMAGE | Create and start a container. |
| docker ps [-a] | List running [or all] containers. |
| docker logs [-f] NAME | Show [follow] container output. |
| docker exec -it NAME sh | Shell into a running container. |
| docker start / stop / restart | Control lifecycle. |
| docker rm [-f] NAME | Remove a container. |
| docker build -t NAME:TAG . | Build an image from a Dockerfile. |
| docker images / rmi | List / remove images. |
| docker pull / push / tag | Registry operations. |
| docker volume / network ls | List volumes / networks. |
| docker cp SRC NAME:DEST | Copy files in or out. |
| docker inspect NAME | Full JSON configuration. |
| docker stats / system df | Live resource use / disk usage. |
| docker compose up -d / down | Start / stop a stack. |
Dockerfile instructions
| Instruction | Does |
|---|---|
| FROM image AS name | Base image; starts a (named) stage. |
| WORKDIR /path | Set the working directory. |
| COPY src dst | Copy from context; –from=stage for multi-stage. |
| ADD | Like COPY but also unpacks archives / fetches URLs. Prefer COPY. |
| RUN cmd | Execute at build time; creates a layer. |
| ENV KEY=value | Environment variable in the image. |
| ARG KEY | Build-time variable (not present at runtime). |
| EXPOSE port | Document the listening port (does not publish it). |
| USER name | Drop privileges for subsequent steps and runtime. |
| VOLUME /path | Declare a mount point. |
| HEALTHCHECK CMD … | How Docker tests liveness. |
| ENTRYPOINT [“bin”] | The executable; args from CMD are appended. |
| CMD [“arg”] | Default command or default args. |
ENTRYPOINT vs CMD ENTRYPOINT is what always runs; CMD supplies default arguments a user can override on docker run. Use exec form (JSON array) so signals reach the process.
Compose keys & commands
| Key | Sets |
|---|---|
| image / build | Pull a published image / build from a path. |
| ports | “HOST:CONTAINER” publishing. |
| environment / env_file | Inline variables / a file of them. |
| volumes | Named volumes and bind mounts. |
| depends_on + condition | Start order; wait for service_healthy. |
| healthcheck | Liveness probe for this service. |
| restart | no | on-failure | always | unless-stopped. |
| networks / secrets | Attach networks; mount secrets at /run/secrets. |
| deploy.resources | CPU and memory limits. |
| Command | Does |
|---|---|
| compose up -d –build | Rebuild then start detached. |
| compose down -v | Stop and also delete volumes (destructive). |
| compose logs -f [svc] | Follow logs. |
| compose exec svc sh | Shell into a service. |
| compose pull / restart | Update images / restart services. |
| compose config | Print the merged, validated file. |
docker run flags
| Flag | Effect |
|---|---|
| -d | Detached (background). |
| -it | Interactive terminal. |
| –rm | Delete the container when it exits. |
| –name | Give it a stable name. |
| -p H:C | Publish a port. |
| -v NAME:/path | Mount a volume; append :ro for read-only. |
| -e KEY=val / –env-file | Environment variables. |
| –network NET | Attach to a network. |
| –restart unless-stopped | Restart policy. |
| -u UID / –user | Run as a specific user. |
| -m 512m / –cpus 1.5 | Memory / CPU limits. |
| –read-only | Immutable root filesystem. |
| –cap-drop ALL | Drop Linux capabilities. |
Troubleshooting
| Symptom | Check |
|---|---|
| Container exits immediately | docker logs NAME; the main process ended. Containers live only as long as PID 1. |
| “port is already allocated” | Another container or host service holds it — change the host side of -p. |
| Cannot reach another container | Both must be on the same user-defined network; use the container name as hostname. |
| Permission denied on a mount | UID mismatch between host and container user; align –user or fix ownership. |
| Build is slow every time | Cache-busting instruction order, or a fat context — add a .dockerignore. |
| Disk filling up | docker system df, then a plain prune. |
| Slow file access on Windows | Source is under /mnt/c; move it into the WSL2 filesystem. |
| Changes not appearing | You rebuilt but still run the old image — recreate the container, not just restart it. |
Harden
| Area | Lever |
|---|---|
| Privilege | Add a USER in the Dockerfile; never run as root. Avoid –privileged entirely. |
| Capabilities | –cap-drop ALL, then add back only what is needed. |
| Filesystem | –read-only plus a tmpfs for scratch; mount configs :ro. |
| Docker socket | Never bind-mount /var/run/docker.sock into a container you do not fully trust — it is root on the host. |
| Base images | Pin by digest or exact tag; prefer minimal or distroless bases; rebuild to pick up patches. |
| Secrets | Compose secrets or a secrets manager — not ENV, which is visible in inspect and image history. |
| Exposure | Bind to 127.0.0.1 unless the service must be reachable; put a reverse proxy in front. |
| Limits | Set memory and CPU limits so one container cannot starve the host. |
| Scanning | Scan images in CI and rebuild regularly; a pinned base still ages. |
References
| Resource | Use | Link |
|---|---|---|
| Docker docs | Full reference | docs.docker.com |
| Dockerfile reference | Every instruction | reference/dockerfile |
| Compose spec | All Compose keys | reference/compose-file |
| Docker Hub | Public images | hub.docker.com |

0 comments