# Docker Cheatsheet

- URL: https://karandeepsingh.ca/cheatsheets/containers/docker/
- Author: Karandeep Singh
- Published: 2025-04-18
- Tags: docker, containers
- Summary: A dense, practical Docker reference covering the image lifecycle, container run flags, Dockerfiles with multi-stage builds, compose, and cleanup commands.

I use Docker more than any other tool and still end up looking up the same flags every week, so I keep this page open in a tab. Just the commands I actually run, nothing padded out. If you run containers in production, pair this with my [Kubernetes Cheatsheet](https://karandeepsingh.ca/cheatsheets/containers/kubernetes/), because most of these images end up in a cluster eventually.

## Image Lifecycle

> **Docs:** [docker image commands (Docker Docs)](https://docs.docker.com/reference/cli/docker/image/)

```bash
docker build -t myapp:1.0 .               # Build from Dockerfile in current dir
docker build -t myapp:1.0 -f prod.Dockerfile .   # Custom Dockerfile name
docker build --no-cache -t myapp:1.0 .    # Ignore build cache
docker tag myapp:1.0 registry.example.com/team/myapp:1.0
docker push registry.example.com/team/myapp:1.0
docker pull nginx:1.27-alpine
docker images                             # List local images
docker image inspect myapp:1.0            # Full metadata (JSON)
docker history myapp:1.0                  # Layer-by-layer sizes
docker save myapp:1.0 -o myapp.tar        # Export image to tarball
docker load -i myapp.tar                  # Import from tarball
```

| Command | What it does |
|---------|--------------|
| `docker build -t name:tag .` | Build an image from a Dockerfile |
| `docker tag src tgt` | Add another name/tag to an image |
| `docker push name:tag` | Upload to a registry |
| `docker pull name:tag` | Download from a registry |
| `docker rmi name:tag` | Delete a local image |
| `docker login registry.example.com` | Authenticate to a registry |

## Running Containers

> **Docs:** [docker container run reference on Docker Docs](https://docs.docker.com/reference/cli/docker/container/run/)

```bash
docker run -d --name web -p 8080:80 nginx:1.27-alpine
docker run --rm -it ubuntu:24.04 bash     # Throwaway interactive container
docker run -d --restart unless-stopped -e TZ=America/Edmonton myapp:1.0
```

### Run Flags I Use Constantly

| Flag | Meaning | Example |
|------|---------|---------|
| `-d` | Detached (background) | `docker run -d nginx` |
| `-p` | Publish port host:container | `-p 8080:80` |
| `-v` | Mount volume or bind mount | `-v pgdata:/var/lib/postgresql/data` |
| `-e` | Set environment variable | `-e POSTGRES_PASSWORD=secret` |
| `--env-file` | Load env vars from file | `--env-file .env` |
| `--network` | Attach to a network | `--network backend` |
| `--rm` | Auto-remove on exit | `docker run --rm alpine echo hi` |
| `--restart` | Restart policy | `--restart unless-stopped` |
| `-it` | Interactive TTY | `-it ubuntu bash` |
| `--name` | Name the container | `--name web` |
| `--memory` / `--cpus` | Resource limits | `--memory 512m --cpus 1.5` |

Restart policies: `no` (default), `on-failure[:max-retries]`, `always`, `unless-stopped`. I use `unless-stopped` on standalone hosts so containers survive reboots but respect a manual `docker stop`.

## Exec, Logs, Inspect, Stats

> **Docs:** [docker container commands (Docker Docs)](https://docs.docker.com/reference/cli/docker/container/)

```bash
docker ps                                 # Running containers
docker ps -a                              # Include stopped ones
docker exec -it web sh                    # Shell into a running container
docker exec web env                       # One-off command, no TTY
docker logs web                           # Dump logs
docker logs -f --tail 100 web             # Follow, last 100 lines
docker logs --since 15m web               # Logs from the last 15 minutes
docker inspect web                        # Full JSON: mounts, IP, env, state
docker inspect -f '{{ .NetworkSettings.IPAddress }}' web
docker stats                              # Live CPU/mem/net per container
docker top web                            # Processes inside the container
docker cp web:/etc/nginx/nginx.conf ./    # Copy files out (or in)
docker stop web && docker rm web          # Stop then remove
docker kill web                           # SIGKILL immediately
```

## Dockerfile Essentials

```diff
# Dockerfile
@@ -1,8 +1,9 @@
 FROM python:3.12-slim
 WORKDIR /app
-COPY . .
-RUN pip install --no-cache-dir -r requirements.txt
+# Dependencies change rarely, source changes constantly. Copying the
+# manifest first means the pip layer is cached until it actually changes.
+COPY requirements.txt .
+RUN pip install --no-cache-dir -r requirements.txt
+COPY . .
 CMD ["python", "-m", "app"]
```

**tip:**
The rule behind that diff: **order Dockerfile instructions from least to most
frequently changed.** Every layer after the first changed one is rebuilt, so
a `COPY . .` near the top throws away the whole cache on every edit.

> **Docs:** [Dockerfile reference on Docker Docs](https://docs.docker.com/reference/dockerfile/)

| Instruction | Purpose |
|-------------|---------|
| `FROM` | Base image (first non-comment line; one per stage) |
| `WORKDIR` | Set working directory (creates it if missing) |
| `COPY` | Copy files from build context into image |
| `RUN` | Execute a command at build time (new layer) |
| `ENV` | Set persistent environment variable |
| `ARG` | Build-time variable (not in final image env) |
| `EXPOSE` | Document the listening port (metadata only) |
| `USER` | Run as a non-root user (drop root) |
| `ENTRYPOINT` | Fixed executable |
| `CMD` | Default args (overridable at `docker run`) |
| `HEALTHCHECK` | Command Docker runs to test container health |

### Multi-Stage Build (Go example)

```dockerfile
# Stage 1: build
FROM golang:1.24-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /app ./cmd/server

# Stage 2: minimal runtime
FROM alpine:3.21
RUN adduser -D -u 10001 appuser
COPY --from=builder /app /app
USER appuser
EXPOSE 8080
ENTRYPOINT ["/app"]
```

The final image here is a few MB instead of ~800MB, since the entire Go toolchain stays behind in the builder stage. If you write Go, my [Go (Golang) Cheatsheet](https://karandeepsingh.ca/cheatsheets/languages/golang/) covers the build flags used above.

### .dockerignore

Keep the build context small. Docker uploads everything in the context to the daemon before building.

```
.git
node_modules
tmp/
*.log
.env
Dockerfile
docker-compose*.yml
```

## Volumes & Networks

> **Docs:** [Docker volumes docs](https://docs.docker.com/engine/storage/volumes/)

```bash
docker volume create pgdata
docker volume ls
docker volume inspect pgdata              # Shows host mountpoint
docker volume rm pgdata

docker network create backend             # User-defined bridge (has DNS)
docker network ls
docker network inspect backend
docker network connect backend web       # Attach a running container
docker network disconnect backend web
```

Containers on the same user-defined network resolve each other by container name, so `db:5432` just works. The default `bridge` network does **not** do name resolution; always create your own.

```bash
# Bind mount (host path) vs named volume
docker run -v $(pwd)/conf:/etc/nginx/conf.d:ro nginx   # bind, read-only
docker run -v pgdata:/var/lib/postgresql/data postgres # named volume
```

## Docker Compose

> **Docs:** [Docker Compose docs](https://docs.docker.com/compose/)

```bash
docker compose up -d                      # Start stack in background
docker compose up -d --build              # Rebuild images first
docker compose down                       # Stop and remove containers/networks
docker compose down -v                    # ...and volumes (destructive!)
docker compose ps                         # Status of services
docker compose logs -f api                # Follow one service's logs
docker compose exec api sh               # Shell into a service
docker compose restart worker            # Restart one service
docker compose pull                       # Refresh images
```

### Sample compose.yaml

```yaml
services:
  api:
    build: .
    ports:
      - "8080:8080"
    environment:
      DATABASE_URL: postgres://app:secret@db:5432/app
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

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

volumes:
  pgdata:
```

## Cleanup

> **Docs:** [docker system prune (Docker Docs)](https://docs.docker.com/reference/cli/docker/system/prune/)

```bash
docker system df                          # What's eating disk
docker system prune                       # Stopped containers, dangling images, unused networks
docker system prune -a --volumes          # Everything unused, incl. volumes - careful
docker image prune                        # Dangling images only
docker image prune -a                     # All images not used by a container
docker container prune                    # All stopped containers
docker volume prune                       # Unused volumes
docker builder prune                      # Build cache
```

**warning:**
`docker system prune -a --volumes` deletes every image not attached to a running container and every unused volume, including database data. On a shared host, run `docker system df` first and prune selectively.

## Debugging Tips

> **Docs:** [docker CLI reference on Docker Docs](https://docs.docker.com/reference/cli/docker/)

```bash
docker logs --details web                          # Extra log metadata
docker inspect -f '{{ .State.ExitCode }}' web      # Why did it die?
docker inspect -f '{{ json .State.Health }}' web | jq   # Healthcheck history
docker events --filter container=web               # Live daemon events
docker run --rm -it --entrypoint sh myapp:1.0      # Bypass a crashing entrypoint
docker exec -it web sh -c 'wget -qO- localhost:8080/healthz'   # Test from inside
docker run --rm --network container:web nicolaka/netshoot      # Netshoot in the container's netns
docker diff web                                    # Files changed since start
```

- **Exit code 137** = OOM-killed (or SIGKILL). Check `--memory` limits and `dmesg`.
- **Exit code 126/127** = command not executable / not found, usually a bad `ENTRYPOINT` or a missing shell in a distroless image.
- Container exits immediately? The main process finished. Run with `-it ... sh` or check that your process runs in the foreground (no daemon mode inside containers).
- Can't reach a service? Confirm `-p` mapping with `docker port web`, and remember published ports bind to the host; container-to-container traffic uses the internal port on a shared network.

**Question:** Time a rebuild of your main image after changing one line of source. If it reinstalls dependencies, where is the `COPY . .` in your Dockerfile?

## Related Cheatsheets

- [Kubernetes](https://karandeepsingh.ca/cheatsheets/containers/kubernetes/) is the next step for these images: kubectl verbs, Deployment and Service YAML skeletons, rollouts, and crash-loop triage
- [Jenkins](https://karandeepsingh.ca/cheatsheets/devops/jenkins/) covers the declarative pipelines that build, tag, and push these images, including docker agents and credential binding
- [Ubuntu Terminal](https://karandeepsingh.ca/cheatsheets/terminal/ubuntu-terminal/) handles the host side: systemd units, journal logs, disk cleanup, and the ufw firewall in front of your containers

The rest of my container notes are in the [Containers group](https://karandeepsingh.ca/cheatsheets/containers/), and everything else lives in the [cheatsheet library](https://karandeepsingh.ca/cheatsheets/).
