Docker
Printed from:
Complete Docker Cheatsheet
Targets Docker Engine 27+ with Buildx, Compose v2 (docker compose), and BuildKit on by default.
Table of Contents
- Installation
- Daily Commands
- Images
- Containers
- Logs, Exec, Inspect
- Volumes & Bind Mounts
- Networking
- Dockerfile
- Building with Buildx / BuildKit
- Docker Compose
- Registries
- Secrets, Configs, Env
- System / Cleanup
- Healthchecks
- Security Hardening
- Troubleshooting
- Quick Reference
Installation
123456789101112# Docker Desktop (macOS / Windows / Linux GUI) — easiest
# https://www.docker.com/products/docker-desktop
# Linux engine (Ubuntu/Debian) — official repo
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER # log out/in afterwards
# Verify
docker version
docker info
docker compose version
Alternatives: Podman (rootless, drop-in CLI compatible), OrbStack / colima / Rancher Desktop on macOS.
Daily Commands
123456789101112docker run -it --rm alpine sh # quick disposable shell
docker run -d --name web -p 8080:80 nginx
docker ps # running
docker ps -a # all
docker stop web && docker start web
docker restart web
docker rm -f web # force remove
docker images
docker pull node:22-alpine
docker exec -it web sh
docker logs -f --tail 100 web
Images
1234567891011121314docker pull <image>[:tag]
docker images
docker image ls --filter "dangling=true"
docker tag local-image registry.example.com/team/app:1.2.3
docker push registry.example.com/team/app:1.2.3
docker rmi <image-id>
docker image prune # dangling
docker image prune -a # unused
docker history <image>
docker save myimg:1 | gzip > myimg.tgz # export
gzip -d < myimg.tgz | docker load
docker scan <image> # Snyk vulnerability scan (deprecated, use scout)
docker scout cves <image> # built-in vulnerability scan
Containers
123456789101112131415161718192021222324252627282930313233# Run options worth knowing
docker run \
--name api \ # name
-d \ # detached
-p 3000:3000 \ # host:container
-e DATABASE_URL=postgres://... \ # env
--env-file .env \
-v $(pwd):/app \ # bind mount
-v data:/data \ # named volume
--network mynet \
--restart unless-stopped \ # no | on-failure | always | unless-stopped
--rm \ # remove on exit
-u 1000:1000 \ # uid:gid
-w /app \ # workdir
--cpus 2 --memory 1g \ # resource limits
--read-only --tmpfs /tmp \ # immutable rootfs
--cap-drop ALL --cap-add NET_BIND_SERVICE \
--security-opt no-new-privileges:true \
myimage:tag
# Lifecycle
docker start <id|name> docker stop <id|name> docker pause / unpause
docker kill <id> # SIGKILL
docker rename old new
docker update --memory 2g --cpus 4 <id>
docker wait <id> # block until exit; print exit code
# Inspection
docker top <id> # processes
docker stats # live CPU/mem
docker port <id>
docker diff <id> # filesystem changes
Logs, Exec, Inspect
1234567891011121314151617docker logs <id>
docker logs -f --since 10m --tail 200 <id>
docker logs --until 2025-01-01T00:00:00Z <id>
docker exec -it <id> sh
docker exec -u root <id> apt update
docker exec <id> env
docker inspect <id> # JSON dump
docker inspect -f '{{.State.Health.Status}}' <id>
docker inspect -f '{{.NetworkSettings.IPAddress}}' <id>
docker inspect -f '{{json .Config.Env}}' <id> | jq
# Copy in / out
docker cp ./local.conf <id>:/etc/nginx/conf.d/
docker cp <id>:/var/log/app.log .
Volumes & Bind Mounts
123456789101112131415161718# Named volume (managed by Docker)
docker volume create data
docker volume ls
docker volume inspect data
docker volume rm data
docker volume prune
# Mounts — prefer --mount over -v for clarity
docker run --mount type=volume,src=data,dst=/var/lib/data myimg
docker run --mount type=bind,src=$(pwd),dst=/app myimg
docker run --mount type=tmpfs,dst=/tmp myimg
# -v shortcut still works
-v data:/var/lib/data # named volume
-v $(pwd):/app # bind mount
-v $(pwd):/app:ro # read-only
-v $(pwd):/app:Z # SELinux relabel
Backup a volume:
123docker run --rm -v data:/data -v $(pwd):/backup alpine \
tar czf /backup/data.tgz -C /data .
Networking
1234567891011121314151617181920212223docker network ls
docker network create mynet
docker network create --driver bridge --subnet 172.30.0.0/16 mynet
docker network inspect mynet
docker network rm mynet
docker network prune
docker run --network mynet --name api myapp # DNS: other containers reach this as "api"
docker network connect mynet existing-container
docker network disconnect mynet existing-container
# Built-in drivers: bridge (default), host, none, overlay (swarm), macvlan, ipvlan
docker run --network host myapp # share host network — Linux only
docker run --network none myapp # no network
# Port publishing
-p 8080:80 # tcp/8080 → container 80
-p 127.0.0.1:8080:80 # bind to localhost only
-p 8080:80/udp
-P # publish all EXPOSEd ports to random host ports
docker port <id>
Dockerfile
1234567891011121314151617181920212223242526272829# syntax=docker/dockerfile:1.7 FROM node:22-alpine AS base WORKDIR /app # Cache deps first COPY package.json pnpm-lock.yaml ./ RUN --mount=type=cache,target=/root/.npm \ corepack enable && pnpm install --frozen-lockfile # App code COPY . . RUN pnpm build # --- runtime stage (smaller image) --- FROM node:22-alpine AS runtime WORKDIR /app ENV NODE_ENV=production COPY --from=base /app/node_modules ./node_modules COPY --from=base /app/dist ./dist COPY --from=base /app/package.json ./ # Run as non-root USER node EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=3s --retries=3 \ CMD wget -qO- http://localhost:3000/healthz || exit 1 CMD ["node", "dist/server.js"]
Instruction quick reference
1234567891011121314151617181920FROM <image>[:tag] [AS name] ARG NAME=default # build-time variable ENV KEY=value # runtime + build WORKDIR /path RUN cmd # shell form RUN ["exec", "form"] COPY src dst # local → image COPY --from=stage src dst # from previous stage ADD url|tar dst # like COPY but also URLs/tar extraction USER name|uid[:gid] EXPOSE 80 443/tcp VOLUME /data # declares mount point HEALTHCHECK ... ENTRYPOINT ["/usr/local/bin/entry"] CMD ["arg1", "arg2"] # default args to ENTRYPOINT LABEL maintainer="..." version="1.0" SHELL ["/bin/bash", "-c"] STOPSIGNAL SIGTERM ONBUILD <instr>
.dockerignore
12345678node_modules .git .env *.log dist .next __pycache__
Building with Buildx / BuildKit
1234567891011121314151617181920212223242526272829# Basic
docker build -t myapp:1.2 .
# Multi-platform (Buildx)
docker buildx create --use --name multi
docker buildx inspect --bootstrap
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:1.2 --push .
# Cache mounts (in Dockerfile RUN)
RUN --mount=type=cache,target=/root/.cache/pip pip install -r req.txt
# Secret at build time (won't end up in layers)
docker buildx build --secret id=npmrc,src=$HOME/.npmrc .
# In Dockerfile:
# RUN --mount=type=secret,id=npmrc,target=/root/.npmrc pnpm install
# Build args
docker build --build-arg VERSION=1.2.3 -t myapp:1.2.3 .
# SBOM + provenance (supply chain)
docker buildx build --sbom=true --provenance=true -t myapp:1.2 --push .
# Local layer cache (no registry push)
docker buildx build --cache-from type=local,src=.cache --cache-to type=local,dest=.cache .
# Inline cache in registry
docker buildx build --cache-from type=registry,ref=myapp:cache \
--cache-to type=registry,ref=myapp:cache,mode=max -t myapp:1.2 --push .
Docker Compose
compose.yaml at the project root:
1234567891011121314151617181920212223242526272829303132333435363738394041services:
web:
build: .
image: myapp:dev
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://user:pass@db:5432/app
env_file: .env
depends_on:
db:
condition: service_healthy
volumes:
- ./:/app
- node_modules:/app/node_modules
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/healthz"]
interval: 30s
retries: 3
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
retries: 5
volumes:
pgdata:
node_modules:
networks:
default:
name: myapp_net
12345678910111213141516171819202122# Compose v2 (built into the CLI)
docker compose up -d # start (build if needed)
docker compose up -d --build # force rebuild
docker compose ps
docker compose logs -f web
docker compose exec web sh
docker compose run --rm web npm test # one-off container
docker compose pull
docker compose down # stop + remove
docker compose down -v # also remove volumes
docker compose restart web
docker compose config # print effective merged config
# Profiles
docker compose --profile dev up
# in YAML:
# web:
# profiles: ["dev"]
# Multiple files (overrides)
docker compose -f compose.yaml -f compose.prod.yaml up -d
compose.override.yaml is loaded automatically and merged on top of compose.yaml.
Registries
123456789101112docker login # Docker Hub
docker login ghcr.io -u USER --password-stdin <<< "$TOKEN"
docker login registry.example.com:5000
docker tag local-image ghcr.io/me/app:1.2.3
docker push ghcr.io/me/app:1.2.3
docker pull ghcr.io/me/app:1.2.3
# Local registry (handy for CI)
docker run -d -p 5000:5000 --name registry registry:2
docker tag myimg localhost:5000/myimg && docker push localhost:5000/myimg
Secrets, Configs, Env
12345678910111213# Build-time secret (BuildKit)
docker buildx build --secret id=key,src=./secret.txt .
# Dockerfile:
# RUN --mount=type=secret,id=key cat /run/secrets/key
# Runtime secret (Swarm only)
docker secret create db_pass ./pass.txt
docker service create --secret db_pass myapp
# Env vars
docker run -e KEY=val ...
docker run --env-file .env ...
For local dev, an .env file at the project root is picked up automatically by Compose (substitutes ${VAR} in compose.yaml).
System / Cleanup
1234567891011121314docker system df # disk usage
docker system prune # stopped containers, unused networks, dangling images
docker system prune -a --volumes # nuclear option
docker container prune
docker image prune -a
docker volume prune
docker network prune
docker builder prune # build cache
# Inspect overall
docker info
docker version
docker events --since 1h # firehose log of API events
Healthchecks
123HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ CMD curl -fsS http://localhost:3000/healthz || exit 1
123docker inspect -f '{{json .State.Health}}' <id> | jq
# Statuses: starting → healthy | unhealthy
In Compose, use depends_on: { service_name: { condition: service_healthy } } so dependents wait.
Security Hardening
1234567891011121314151617181920212223242526# Run as non-root (in image: USER node; at runtime: -u 1000:1000)
docker run -u 1000:1000 myapp
# Read-only rootfs + tmpfs for /tmp
docker run --read-only --tmpfs /tmp --tmpfs /run myapp
# Drop all caps; add only what you need
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE myapp
# Prevent privilege escalation
docker run --security-opt no-new-privileges:true myapp
# Limit resources
docker run --cpus 1.5 --memory 512m --pids-limit 200 myapp
# Don't run as privileged — almost never needed
# docker run --privileged # ⚠️ full host access
# Scan
docker scout cves myapp:1.2
docker scout quickview myapp:1.2
# Sign / verify (Docker Content Trust)
export DOCKER_CONTENT_TRUST=1
docker trust sign myapp:1.2
Troubleshooting
123456789101112131415161718192021222324252627282930# Container won't start
docker ps -a # see exit code in STATUS
docker logs <id> # last error
docker inspect <id> # check OOMKilled, ExitCode
# Permission denied on bind mount (Linux)
# UID/GID inside container must match host file owner, or chown the path
# "no space left on device"
docker system df
docker system prune -a --volumes
# Networking — container can't reach others
docker network inspect <net>
docker exec -it <id> nslookup other-service
# Rebuild from scratch (ignore cache)
docker build --no-cache -t myapp .
docker compose build --no-cache
# Slow build
docker buildx build --progress=plain . # show full RUN output
# Container hung
docker kill --signal=SIGTERM <id>; sleep 5; docker kill <id>
# Check daemon status (Linux)
sudo systemctl status docker
sudo journalctl -u docker -n 100
Quick Reference
12345678910111213141516171819202122232425262728# Daily
docker run -it --rm alpine sh
docker run -d -p 80:80 --name web nginx
docker exec -it web sh
docker logs -f web
docker stop web && docker rm web
# Images
docker build -t app:1 .
docker pull / push / images / rmi / scan
# Compose
docker compose up -d
docker compose down [-v]
docker compose logs -f
docker compose exec svc sh
# Build (modern)
docker buildx build --platform linux/amd64,linux/arm64 -t app:1 --push .
# Cleanup
docker system prune -a --volumes
# Inspect
docker ps / ps -a / images / volume ls / network ls
docker inspect <id>
docker stats
Tip: in 2025+, default to Compose v2 (
docker compose, notdocker-compose), Buildx + BuildKit for everything, multi-stage Dockerfiles, non-rootUSER, named volumes over bind mounts in production, anddocker scoutfor vulnerability scanning.
Continue Learning
Discover more cheatsheets to boost your productivity