Skip to content

BLOG

Docker Commands Cheat Sheet: The Essential Guide for 2026

April 13, 2026 · 12 min read

I keep a sticky note on my monitor with six Docker commands. Not because I forget them, but because muscle memory types docker rm when I mean docker rmi at 2 AM, and the wrong one just silently deletes containers instead of images. After rebuilding a dev environment from scratch one too many times, the sticky note earned its permanent spot.

Docker's CLI has over 60 subcommands. Most developers regularly use about 15 of them. This cheat sheet focuses on those 15, plus the handful of Compose and networking commands that round out a typical day. Every example here is something you can paste directly into a terminal.

Container Lifecycle: Run, Stop, Remove

The three commands you type more than anything else:

# Start a container from an image
docker run -d --name myapp -p 8080:80 nginx:alpine

# Stop a running container
docker stop myapp

# Remove a stopped container
docker rm myapp

The -d flag runs the container in the background (detached mode). Without it, Docker attaches your terminal to the container's stdout, which is useful for debugging but not for running services. The --name flag gives your container a human-readable name instead of Docker's auto-generated ones like quirky_morse.

A pattern that catches people off guard: docker rm refuses to remove a running container. You either docker stop first, or use docker rm -f to force-kill and remove in one step. In production, always stop gracefully. In local dev, -f saves a few keystrokes.

Port mapping gotchas

The syntax is -p HOST:CONTAINER. The number on the left is your machine's port. The one on the right is the port inside the container. Reversing them is one of those mistakes you make exactly once.

# Map host port 3000 to container port 80
docker run -d -p 3000:80 nginx

# Map to a random available host port
docker run -d -p 80 nginx

# Check which host port was assigned
docker port <container_id>

Building Images: Dockerfile to Container

Building images is where Docker goes from "I can run Nginx" to "I can ship my own app."

# Build from current directory's Dockerfile
docker build -t myapp:1.0 .

# Build with a specific Dockerfile
docker build -f Dockerfile.prod -t myapp:prod .

# Build with build arguments
docker build --build-arg NODE_ENV=production -t myapp:prod .

The -t flag tags the image with a name and optional version. Without it, you get an image ID like sha256:a3f2... that nobody wants to type. Tag everything. Future you will be grateful.

Multi-stage builds have become the standard pattern for production images. The idea: use a full build environment (Node.js, Go compiler, etc.) in one stage, then copy just the compiled output into a minimal runtime image.

# Multi-stage Dockerfile example
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

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

This pattern typically cuts image sizes by 70-90%. A Node.js app that weighs 900MB as a single-stage build drops to 40-80MB with multi-stage.

If you find yourself writing Dockerfiles from scratch repeatedly, the Docker Compose Generator can scaffold both your Dockerfile and docker-compose.yml with common service patterns already configured.

Docker Compose: Multi-Container Workflows

Most real applications need more than one container. A web app with a database, a cache layer, and maybe a background worker. Docker Compose defines all of these in a single YAML file.

# Start all services defined in docker-compose.yml
docker compose up -d

# Stop and remove all containers, networks
docker compose down

# Rebuild images before starting
docker compose up -d --build

# View logs for a specific service
docker compose logs -f api

# Scale a service to N instances
docker compose up -d --scale worker=3

A few notes on the modern CLI: Docker Compose v2 dropped the hyphen. It is docker compose (space, no hyphen), not docker-compose. If you are still running the Python-based v1, it works but is no longer maintained.

The YAML configuration for Compose is where most people hit formatting errors. YAML is whitespace-sensitive, and mixing tabs with spaces will break things silently. A YAML Formatter catches these issues before docker compose up throws a cryptic parse error.

Common docker-compose.yml patterns

services:
  api:
    build: ./api
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/myapp
    depends_on:
      db:
        condition: service_healthy

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

volumes:
  pgdata:

The depends_on with condition: service_healthy is critical. Without health checks, Docker starts services in order but does not wait for them to be ready. Your API container boots in 2 seconds, tries to connect to Postgres, and crashes because Postgres needs 8 seconds to initialize. Health checks solve this properly.

Inspecting and Debugging Containers

Things go wrong. Here is how you figure out what happened.

# View container logs
docker logs myapp
docker logs -f myapp         # follow (live tail)
docker logs --tail 50 myapp  # last 50 lines

# Execute a command inside a running container
docker exec -it myapp /bin/sh
docker exec myapp cat /etc/nginx/nginx.conf

# Inspect container details (JSON output)
docker inspect myapp

# Check resource usage
docker stats
docker stats myapp

The docker exec -it combination gets you a shell inside a running container. The -i flag keeps stdin open, -t allocates a TTY. Together they give you an interactive terminal session. Use /bin/sh instead of /bin/bash for Alpine-based images, which do not include Bash by default.

When you need to inspect JSON output from docker inspect, the output is often deeply nested. Piping it through a JSON Formatter makes it readable, or you can use Docker's built-in Go template formatting:

# Get just the IP address
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' myapp

# Get environment variables
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' myapp

Networking: Connecting Containers

Docker creates an isolated network for each Compose project by default. Containers within the same network can reach each other by service name. Containers on different networks cannot communicate unless you explicitly connect them.

# List networks
docker network ls

# Create a custom network
docker network create my-network

# Run a container on a specific network
docker run -d --network my-network --name api myapp

# Connect a running container to another network
docker network connect my-network existing-container

# Inspect network details
docker network inspect my-network

A common architecture: put your API and database on one network, and your API and reverse proxy on another. The database never sees the reverse proxy's network, which adds a layer of isolation.

Volumes and Data Persistence

Containers are ephemeral. When a container is removed, everything inside it disappears. Volumes persist data across container restarts and removals.

# Create a named volume
docker volume create mydata

# Run with a named volume
docker run -d -v mydata:/app/data myapp

# Run with a bind mount (host directory)
docker run -d -v $(pwd)/config:/app/config:ro myapp

# List volumes
docker volume ls

# Remove unused volumes
docker volume prune

The :ro suffix makes the mount read-only inside the container. Use this for configuration files that the container should read but never modify. It is a small security measure that prevents a compromised container from altering your host filesystem.

Cleanup: Recovering Disk Space

Docker accumulates cruft. Stopped containers, dangling images, unused networks, and orphaned volumes quietly eat disk space. On a busy development machine, it is not unusual to find 20-30GB of Docker artifacts after a few months.

# Remove all stopped containers
docker container prune

# Remove dangling images (untagged)
docker image prune

# Remove ALL unused images (not just dangling)
docker image prune -a

# Nuclear option: remove everything unused
docker system prune -a --volumes

# Check disk usage
docker system df

docker system prune -a --volumes is the "start fresh" command. It removes all stopped containers, all unused networks, all images without at least one running container, and all unused volumes. Run it when your disk is full and you need space now. Do not run it on a production server unless you really mean it.

docker system df is underrated. It shows exactly how much space images, containers, volumes, and build cache consume. Check it before pruning so you know what you are actually recovering.

Working with Registries

# Pull an image from Docker Hub
docker pull nginx:alpine

# Tag a local image for a remote registry
docker tag myapp:1.0 registry.example.com/myapp:1.0

# Push to a registry
docker push registry.example.com/myapp:1.0

# Login to a registry
docker login registry.example.com

For private registries, always use specific version tags. The :latest tag is mutable and means "whatever was pushed last," which makes builds non-reproducible. Pin to exact versions in production: nginx:1.27.0-alpine, not nginx:latest.

Scheduling and Automation

Docker commands that run on schedules typically involve cron jobs for cleanup, backup, or health checks. A common pattern:

# Cron job to prune unused images every Sunday at 3 AM
0 3 * * 0 docker image prune -a -f >> /var/log/docker-prune.log 2>&1

# Cron job to backup a database volume daily at midnight
0 0 * * * docker exec db pg_dump -U postgres myapp > /backups/myapp-$(date +\%Y\%m\%d).sql

Cron syntax is notoriously easy to get wrong. The five-field format (minute, hour, day-of-month, month, day-of-week) trips people up, especially with edge cases like "every other Tuesday" or "the last day of the month." A Cron Expression Builder lets you construct and validate these expressions visually before committing them to your crontab.

Encoding and Config Secrets

Docker Compose supports environment files and secrets, but sometimes you need to embed configuration data as Base64-encoded strings, especially when working with Kubernetes secrets or passing binary data through environment variables.

# Base64-encode a config file for K8s secrets
cat config.json | base64

# Decode in the container entrypoint
echo $CONFIG_DATA | base64 -d > /app/config.json

If you are preparing configuration for Kubernetes secrets or need to quickly encode/decode strings during debugging, a Base64 Encoder/Decoder in the browser is faster than context-switching to a terminal.

Quick Reference Table

Task Command Notes
Run container docker run -d --name X image Add -p for port mapping
Stop container docker stop X Sends SIGTERM, then SIGKILL after 10s
View logs docker logs -f X -f follows live output
Shell into container docker exec -it X /bin/sh Use sh for Alpine images
Build image docker build -t name:tag . Always tag your builds
List containers docker ps -a -a includes stopped
Remove all stopped docker container prune Add -f to skip confirmation
Check disk usage docker system df Run before pruning
Start Compose stack docker compose up -d No hyphen in v2 CLI
Tear down stack docker compose down Add -v to remove volumes

Frequently Asked Questions

What is the difference between docker stop and docker kill?

docker stop sends SIGTERM first, giving the process 10 seconds (configurable with -t) to shut down gracefully. docker kill sends SIGKILL immediately. Always prefer stop unless the container is unresponsive.

Should I use Docker Compose or Kubernetes?

Compose is for development and small deployments. Kubernetes is for production orchestration at scale. If your app runs on fewer than 10 containers and does not need auto-scaling, Compose is probably enough. Kubernetes adds significant operational complexity that only pays off at scale.

How do I reduce Docker image size?

Three strategies: use Alpine base images (5MB vs 130MB for Debian), use multi-stage builds to exclude build tools from the final image, and add a .dockerignore file to prevent copying node_modules, .git, and other unnecessary files into the build context.

Is Docker free to use?

Docker Engine (the runtime) is open source and free. Docker Desktop requires a paid subscription for companies with more than 250 employees or more than $10 million in annual revenue. Alternatives like Podman, Rancher Desktop, and Colima are fully free.

Tools That Make Docker Easier

All tools run in your browser. Standard tool input stays in your browser where local processing is supported, which matters when you are working with configuration files that contain database credentials or API keys.

Looking for more DevOps content? Check out the Docker Configuration Guide for deeper coverage of Dockerfiles and multi-stage builds, or the Kubernetes YAML for Beginners guide if you are moving beyond Compose.