# Docker CLI Cheat Sheet — Images & Containers

> **Tool:** Docker CLI
> **Category:** Containers & Orchestration
> **Verified against:** Docker 29.1.5, flags verified via `docker <cmd> --help`, 2026-08-21
> **Official docs:** https://docs.docker.com/reference/cli/docker/

Building and tagging images, and running/inspecting/cleaning up containers — the day-to-day loop of local container development.

## Building images

```bash
docker build -t myapp:latest .
docker build -t myapp:v1.2.0 -f Dockerfile.prod .
docker build --no-cache -t myapp:latest .           # ignore layer cache, force a full rebuild
docker build --build-arg NODE_ENV=production -t myapp:latest .
```

`docker build` is now backed by BuildKit (`docker buildx build` under the hood) — `--no-cache` invalidates every layer, while a targeted fix is usually cheaper: touching the file that changed and letting the layer cache do its job from that point forward.

## Tagging and pushing images

```bash
docker tag myapp:latest myregistry.io/myteam/myapp:v1.2.0
docker push myregistry.io/myteam/myapp:v1.2.0
docker pull myregistry.io/myteam/myapp:v1.2.0
docker images                                        # list local images
docker rmi myapp:latest                              # remove a local image
```

## Running containers

```bash
docker run -d --name my-app -p 8080:80 myapp:latest
docker run -d --name my-app -e LOG_LEVEL=info -v ./data:/app/data myapp:latest
docker run --rm -it myapp:latest /bin/bash            # interactive, auto-removed on exit
docker run -d --restart unless-stopped myapp:latest
```

`-p 8080:80` maps **host:container** — the host port comes first. Getting this backwards is a common cause of "it works when I exec in but not from the browser."

## Listing and inspecting containers

```bash
docker ps                                            # running containers only
docker ps -a                                         # include stopped containers
docker ps --filter status=exited
docker inspect my-app                                # full container config as JSON
docker stats                                          # live CPU/memory/network usage
```

## Logs and exec

```bash
docker logs my-app
docker logs -f my-app                                # follow/stream
docker logs --tail 100 --since 1h my-app
docker exec -it my-app /bin/bash                      # interactive shell in a running container
docker exec my-app env                                 # one-off command, no shell
```

## Stopping and removing containers

```bash
docker stop my-app
docker rm my-app
docker rm -f my-app                                   # stop and remove in one step
docker container prune                                 # remove all stopped containers
```

## Multi-stage builds

```dockerfile
FROM node:20 AS build
WORKDIR /app
COPY . .
RUN npm ci && npm run build

FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
```

```bash
docker build -t myapp:latest .                        # builds every stage, keeps only the final one
docker build --target build -t myapp:build-debug .    # stop at an intermediate stage, e.g. to debug it
```

Each `FROM` starts a new stage; `COPY --from=<stage>` pulls specific artifacts out of an earlier stage into the current one. Only the final stage ends up in the built image, so build tooling (compilers, `npm`/`node_modules`, test dependencies) never ships to production — this is the standard fix for "why is my image 1.2GB for a 20MB binary."

## Building for multiple platforms with buildx

```bash
docker buildx ls                                       # list builder instances
docker buildx create --name multiarch --use --bootstrap  # create + switch to a new builder, boot it
docker buildx build --platform linux/amd64,linux/arm64 -t myregistry.io/myapp:v1 --push .
docker buildx use default                               # switch back to the default builder
```

`docker build` on a single-arch host only ever produces an image for that host's architecture. `buildx` uses BuildKit (via QEMU emulation or a remote builder) to produce a multi-platform image in one invocation — `--push` is required for a multi-platform build's output, since a multi-arch manifest can't be loaded into the local `docker images` store the way `--load` loads a single-platform one.

## Health checks

```bash
docker run -d --name my-app \
  --health-cmd="curl -f http://localhost/health || exit 1" \
  --health-interval=30s \
  --health-timeout=5s \
  --health-retries=3 \
  --health-start-period=10s \
  myapp:latest
docker inspect --format='{{.State.Health.Status}}' my-app   # healthy / unhealthy / starting
docker run -d --no-healthcheck myapp:latest              # ignore a HEALTHCHECK baked into the image
```

A container image can bake in its own `HEALTHCHECK` instruction in the Dockerfile — the `--health-*` flags on `docker run` override it per-container without rebuilding. `--health-start-period` matters for slow-starting apps: failed checks during that window don't count toward `--health-retries`, so a container isn't marked unhealthy while it's still booting.

## Setting resource limits

```bash
docker run -d --memory=512m --memory-swap=512m myapp:latest   # hard cap RAM, disable swap (swap = memory limit)
docker run -d --memory=512m --memory-reservation=256m myapp:latest  # soft limit, enforced under host pressure
docker run -d --cpus=1.5 myapp:latest                    # cap at 1.5 CPU cores
docker run -d --cpuset-cpus="0,1" myapp:latest            # pin to specific CPU cores
docker stats my-app                                        # confirm actual usage against the limits
```

Setting `--memory-swap` equal to `--memory` disables swap for the container (the flag is swap *on top of* the memory limit, not swap in isolation) — the common pattern for stopping a leaking container from silently degrading into swap thrash instead of getting OOM-killed where you'd notice it.

## Working with docker context (managing multiple daemons)

```bash
docker context ls                                        # list contexts (local + remote daemons)
docker context create staging --docker "host=ssh://user@staging-host"
docker context use staging                                # switch the CLI's target daemon
docker context show                                        # print the currently active context
docker --context staging ps                                 # one-off command against a specific context without switching
docker context rm staging
```

A context bundles a daemon endpoint (local socket, SSH, or TCP+TLS) under a name — switching context is how you point the same `docker` CLI at a different host (dev laptop vs. a remote build box) without juggling `DOCKER_HOST` env vars by hand.

## Inspecting image layers and history

```bash
docker history myapp:latest                                # each layer, its size, and the command that created it
docker history --no-trunc myapp:latest                     # full (untruncated) command per layer
docker inspect myapp:latest                                 # full image metadata as JSON (env, entrypoint, layers, config)
docker inspect --format='{{.Config.Env}}' myapp:latest      # pull one field out with a Go template
```

`docker history` is usually the fastest way to find which instruction in a Dockerfile bloated an image — layers are listed newest-first with their individual size, so a surprisingly large layer points straight at the offending `RUN`/`COPY` line.

## Cleaning up unused resources

```bash
docker system prune                                          # remove stopped containers, dangling images, unused networks, build cache
docker system prune -a                                        # also remove ALL unused images, not just dangling ones
docker system prune --volumes                                  # also remove anonymous (unnamed) volumes
docker image prune -a --filter "until=24h"                    # only images untouched in the last 24h
docker buildx prune                                             # clear the BuildKit build cache specifically
```

`system prune` never touches named volumes or anything attached to a running container by default — `-a` is still safe for that reason, but always confirms what it's about to remove interactively unless you pass `-f`. `buildx prune` is separate because BuildKit's cache lives outside the regular image/container/volume/network bookkeeping `system prune` covers.
