Cheatsheet

Docker and Docker Compose command reference

Everything is grouped by what you are trying to do: start a container, build an image, find out why the thing exited, reclaim disk, or drive a multi service stack with Compose. All Compose entries use the v2 syntax, so it is docker compose with a space, never the retired hyphenated binary.

Running and managing containers

Command What it does
docker run -it --rm alpine sh Throwaway interactive shell. The container is deleted when you exit.
docker run -d --name api -p 3000:3000 img Detached run with a stable name and a host port mapped to a container port.
docker run -e NODE_ENV=production img Set one environment variable. Repeat the flag for more.
docker run --env-file .env img Load every variable from a file instead of listing them on the command line.
docker run -v $(pwd):/app -w /app node:22 npm ci Bind mount the current directory, set the working directory, run a command.
docker run --restart unless-stopped img Restart on failure and on daemon start, but not after a manual stop.
docker run --memory 512m --cpus 1.5 img Cap memory and CPU so one container cannot starve the host.
docker run --user 1000:1000 img Run as a non root UID and GID, which fixes most bind mount permission pain.
docker ps Running containers only.
docker ps -a Every container including exited ones, with the exit code in the status column.
docker start -ai api Restart a stopped container and attach to its output interactively.
docker stop api Graceful stop: SIGTERM, then SIGKILL after ten seconds.
docker kill api Immediate SIGKILL with no grace period.
docker rm -f api Stop and delete a container in one step.
docker rename api api-old Rename a container so you can reuse the good name for the new one.

Gotcha: the port flag is host:container in that order, and a container listening on 127.0.0.1 inside itself is unreachable from the host no matter what you map. Bind to 0.0.0.0 inside the container.

Building and moving images

Command What it does
docker build -t app:1.4.0 . Build the Dockerfile in the current directory and tag the result.
docker build -f Dockerfile.dev -t app:dev . Use a differently named Dockerfile with the same build context.
docker build --target builder -t app:build . Stop at a named stage of a multi stage build, which is how you debug one.
docker build --build-arg VERSION=1.4.0 . Pass a value to an ARG declared in the Dockerfile.
docker build --no-cache . Ignore every cached layer. The first thing to try when a build lies to you.
docker build --progress=plain . Full uncollapsed build log, so you can actually read a failing step.
docker buildx build --platform linux/amd64,linux/arm64 . Multi architecture build, the fix for Apple Silicon images failing on servers.
docker images Local images with size. Add --filter dangling=true for orphan layers.
docker history app:1.4.0 Layer by layer size breakdown. This is how you find the fat layer.
docker tag app:1.4.0 ghcr.io/me/app:1.4.0 Add a registry qualified tag before pushing.
docker push ghcr.io/me/app:1.4.0 Upload an image to a registry you are logged into.
docker save app:1.4.0 | gzip > app.tar.gz Export an image to a file for an air gapped host. Restore with docker load.

Gotcha: layer cache invalidation is ordered. Copy the lockfile and install dependencies before copying the rest of the source, or every source edit reinstalls the whole dependency tree.

Debugging a container that misbehaves

Command What it does
docker logs -f --tail 100 api Follow the last hundred lines of stdout and stderr.
docker logs --since 10m api Only the last ten minutes of output.
docker exec -it api sh Shell inside a running container. Use bash if the image has it.
docker exec -u root -it api sh Get in as root when the image drops privileges and you need a package.
docker inspect api Full JSON: mounts, networks, env, entrypoint, health, exit code.
docker inspect -f '{{.State.ExitCode}}' api Pull one field out of that JSON with a Go template.
docker stats Live CPU, memory, network, and disk per container.
docker top api Processes running inside the container, from the host's point of view.
docker diff api Files added, changed, or deleted since the container started.
docker cp api:/app/out.log ./out.log Copy a file out of a container, or in by reversing the arguments.
docker port api Which host ports actually got bound, which is rarely what you assumed.
docker events --filter container=api Live daemon event stream, which catches restart loops you cannot see otherwise.

Gotcha: if a container exits instantly, docker logs on the dead container still works, and docker ps -a shows the exit code. Code 137 means it was killed, usually by the memory limit; 139 is a segfault; 126 means the entrypoint was not executable.

Volumes, networks, and reclaiming disk

Command What it does
docker volume ls List named volumes.
docker volume create pgdata Create a named volume up front so its lifecycle is yours, not a container's.
docker volume inspect pgdata Where the volume actually lives on the host filesystem.
docker network ls List networks. Compose creates one per project automatically.
docker network create backend Create a user defined bridge, which gives containers DNS by name.
docker network connect backend api Attach a running container to another network.
docker system df How much disk images, containers, volumes, and the build cache are using.
docker image prune Delete dangling images only. The safe daily cleanup.
docker builder prune Clear the BuildKit cache, which is usually the biggest hidden consumer.
docker container prune Remove every stopped container.
docker system prune -a Remove stopped containers, unused networks, and all unreferenced images.
docker system prune -a --volumes The same, plus unused volumes. This is the one that eats your database.

Gotcha: named volumes survive docker rm and even docker compose down. They do not survive --volumes on either command. Never add that flag to a script that runs unattended.

Docker Compose day to day

Command What it does
docker compose up -d Create and start every service in the background.
docker compose up -d --build Rebuild images first. Compose will not notice a Dockerfile change otherwise.
docker compose up -d --wait Block until every service with a healthcheck reports healthy. Ideal in CI.
docker compose up -d --force-recreate Recreate containers even when nothing in the config changed.
docker compose up -d --remove-orphans Delete containers from services you removed from the file.
docker compose down Stop and remove containers and the project network. Volumes survive.
docker compose down -v The same, plus the named volumes. Your database data is gone.
docker compose ps Service status, health, and published ports for this project.
docker compose logs -f api db Follow the logs of named services only, interleaved and color coded.
docker compose exec db psql -U postgres Run a command in an already running service container.
docker compose run --rm api npm run migrate One off task in a fresh container that deletes itself afterwards.
docker compose restart api Restart one service without touching the rest of the stack.
docker compose build --no-cache api Rebuild one service's image from scratch.
docker compose pull Fetch newer versions of every image the file references.
docker compose config Print the merged and interpolated file. The fastest way to debug variables.
docker compose watch Sync or rebuild automatically on file change, driven by the develop block.
docker compose -f base.yml -f prod.yml up -d Layer override files. Later files win on conflicting keys.
docker compose --profile debug up -d Start optional services that are tagged with a profile.

Gotcha: the project name defaults to the directory name, and it is what scopes container names, networks, and volumes. Two checkouts of the same repo in differently named folders get two independent stacks. Pin it with -p or the name: key.

Reference files worth copying

A Compose file with the parts people forget

name: myapp

services:
  api:
    build:
      context: .
      target: runner
      args:
        NODE_VERSION: "22"
    ports:
      - "3000:3000"
    env_file: [.env]
    environment:
      DATABASE_URL: postgres://postgres:secret@db:5432/app
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://localhost:3000/healthz"]
      interval: 10s
      timeout: 3s
      retries: 5
      start_period: 20s
    restart: unless-stopped
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
        - action: rebuild
          path: package.json

  db:
    image: postgres:17-alpine
    environment:
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: app
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 10

volumes:
  pgdata:

Gotcha: the top level version: key is obsolete in Compose v2 and prints a warning. Delete it. Plain depends_on only waits for the container to start, not for the service to be usable, which is why the condition form above matters.

A multi stage Node Dockerfile

FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]

Gotcha: use the exec form CMD ["node", "x.js"], not the shell form. The shell form wraps your process in /bin/sh -c, so it never receives SIGTERM and every stop takes the full ten second timeout. Also add a .dockerignore containing at least node_modules, .git, and dist.

Keep going

The database container in that Compose file needs queries: the SQL cheatsheet is Postgres flavored and starts where psql drops you. For the install step inside the Dockerfile, the npm, pnpm, and bun mapping covers the lockfile aware commands each one wants in CI.

More references sit in the cheatsheet index, and the hosting section of the tool directory covers where these images actually run.