Docker CLI Commands Reference: The Only Guide You'll Need to Ship Containers Without Regret
Docker CLI commands explained from zero.
20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.
- ✓A terminal
- ✓Docker installed (any recent version)
- ✓Willingness to break things in a sandbox
To run a container: docker run nginx. To list running containers: docker ps. To stop one: docker stop . That's the bare minimum to get started.
Think of Docker CLI as a remote control for a container factory. You press 'build' to assemble a container from a recipe (Dockerfile). You press 'run' to start it. You press 'ps' to see which containers are running on the factory floor. You press 'stop' to shut one down. The CLI is your interface to the factory manager (the Docker daemon).
I've seen a single docker run without --restart take down a payment processing pipeline at 2am. The container crashed, nobody noticed, and 14,000 transactions got lost. The Docker CLI is powerful — but it's also sharp. One wrong flag and you're debugging at 3am.
This guide isn't a man page. It's the survival kit I wish I had when I started. We'll cover every command you'll actually use in production, the flags that matter, and the mistakes that will burn you. By the end, you'll be able to navigate Docker CLI like a senior engineer — not just run docker ps and pray.
You'll learn how to build images that don't bloat, run containers that survive crashes, debug networking when containers can't talk, and clean up disk space without accidentally deleting production data. Every command comes with a real scenario, not a toy example.
Why You Need the Docker CLI — Containers Are Not VMs
Before Docker, deploying software meant fighting with dependency hell. 'It works on my machine' was the punchline of every deployment. Docker containers package your app with its entire runtime — libraries, config, binaries — into a single image. The CLI is how you control that image lifecycle.
Without the CLI, you're stuck with GUI tools that hide what's really happening. When a container crashes at 3am, you need commands, not click-ops. The CLI gives you speed, precision, and scriptability.
Here's the mental model: Docker CLI talks to the Docker daemon (dockerd) via a REST API. The daemon manages images, containers, networks, and volumes. Every command you run is a structured request to that daemon. Understanding this helps when debugging — if the CLI hangs, check if the daemon is alive (systemctl status docker).
docker ps to dps and docker images to dimg. Saves thousands of keystrokes over a career.Image Management: Pull, Build, and Tag Like a Pro
Images are the blueprints for containers. You pull them from a registry (like Docker Hub) or build them from a Dockerfile. The most common rookie mistake? Using latest tag in production. latest is a moving target — you'll deploy different code on different days without realizing it.
Always pin to a specific version tag: nginx:1.25.3 not nginx:latest. When you build, tag with a version or commit hash: docker build -t myapp:v1.2.3 .. This gives you reproducible deployments.
Another trap: bloated images. Every RUN command in a Dockerfile adds a layer. Combine commands with && to reduce layers. Use .dockerignore to exclude node_modules, .git, and other junk. A lean image is faster to pull and more secure.
docker build --no-cache in CI every time. It rebuilds all layers from scratch, wasting time. Only use it when you suspect a cached layer is stale (e.g., after a base image update).Running Containers: The Flags That Matter
docker run is the Swiss Army knife. But with 50+ flags, it's easy to miss the ones that keep your container alive in production. The four you must know: -d (detach), --restart, -p (port mapping), and -v (volume mount).
Without --restart, if your app crashes, the container dies and stays dead. Always use --restart unless-stopped for long-running services. It restarts the container unless you explicitly stop it.
Port mapping: -p 8080:80 maps host port 8080 to container port 80. Never expose container ports directly to the internet without a reverse proxy. Use Docker networks for inter-container communication.
Volumes: -v /host/path:/container/path persists data. Without volumes, container data vanishes when the container is removed. For databases, always use named volumes: -v mydata:/var/lib/mysql.
--restart always instead of unless-stopped. If you stop the container intentionally (e.g., for maintenance), always will restart it immediately. unless-stopped respects your manual stop.Container Lifecycle: Stop, Start, and Remove Without Losing Data
Containers are ephemeral by design. You stop them with docker stop (graceful, sends SIGTERM) or docker kill (immediate, sends SIGKILL). Always prefer stop — it gives your app time to clean up connections and flush data.
To remove a container: docker rm . But if it's running, you need docker rm -f (force). Be careful — force remove skips the graceful shutdown.
For bulk cleanup: docker container prune removes all stopped containers. Add -f to skip confirmation. I run this weekly in CI to prevent zombie containers from eating disk space.
Pro tip: Use docker run --rm for temporary containers. The container is automatically removed when it exits. Perfect for build jobs or one-off scripts.
docker rm $(docker ps -aq) to clean all containers. But add a confirmation prompt — I've seen people wipe production containers by accident.Debugging Running Containers: Logs, Exec, and Inspect
When a container misbehaves, your first move is docker logs. It streams stdout and stderr. Add -f to follow (like tail -f). Add --tail 100 to see only the last 100 lines.
If you need to get inside a running container: docker exec -it . The -it flags give you an interactive terminal. Inside, you can run commands like ps aux, netstat, or curl to diagnose issues.
docker inspect returns detailed metadata about the container — IP address, mounts, environment variables, restart policy. Pipe it to jq to extract specific fields: docker inspect .
For resource usage: docker stats shows live CPU, memory, and network I/O for all running containers. Use it to spot memory leaks or CPU spikes.
Networking: How Containers Talk to Each Other and the World
By default, containers run on a bridge network (docker0). They can talk to each other via IP, but not by hostname. For service discovery, create a user-defined bridge network: docker network create mynet. Containers on the same user-defined network can resolve each other by container name.
To expose a container to the host, use -p. To isolate a container from the network, use --network none. For sharing the host's network stack (e.g., for performance), use --network host — but this reduces isolation.
Common issue: container can't reach the internet. Check DNS: docker run alpine cat /etc/resolv.conf. If DNS is wrong, pass --dns 8.8.8.8 to the container.
Production pattern: put your web server and API on the same network, but keep the database on a separate network with only the API connected to it.
--link (deprecated) instead of user-defined networks. --link only works on the default bridge and doesn't support DNS-based discovery. Always create a custom network.Volumes and Data Persistence: Don't Lose Your Database
Containers are stateless by design. When you remove a container, all data inside it is gone. Volumes are the mechanism to persist data outside the container's writable layer.
There are two types: bind mounts (map a host directory) and named volumes (managed by Docker). Bind mounts are great for development — you can edit files on the host and see changes in the container. Named volumes are better for production — they're portable and can be backed up easily.
To create a named volume: docker volume create mydata. Then mount it: docker run -v mydata:/data .... To inspect: docker volume inspect mydata.
Never store database data in a bind mount to a host directory unless you're okay with permission issues. MySQL and Postgres containers run as non-root users; the host directory must have correct ownership. Named volumes handle this automatically.
--volumes-from to share volumes between containers. It's deprecated and creates tight coupling. Use named volumes and mount them independently.Docker Compose: Orchestrating Multi-Container Apps Without the Pain
When your app has multiple services (web, API, database, cache), running them individually with docker run becomes a nightmare. Docker Compose lets you define all services in a YAML file and manage them with a single command.
docker-compose up -d starts everything. docker-compose down stops and removes containers, networks, and volumes (if configured). docker-compose logs -f tails logs from all services.
Compose creates a default network for all services, so they can resolve each other by service name. No need to manually create networks.
Production tip: Use Compose for development and staging, but for production, consider Docker Swarm or Kubernetes for better orchestration (rolling updates, scaling, health checks).
docker-compose config to validate your YAML before running. Catches indentation errors and missing fields.Cleaning Up: Prune Like a Janitor, Not a Hoarder
Over time, Docker accumulates unused images, containers, volumes, and build cache. Disk space evaporates. The docker system prune command is your cleanup crew.
docker system prune removes stopped containers, dangling images, and unused networks. Add -a to remove all unused images (not just dangling). Add --volumes to also remove unused volumes. Be careful — --volumes will delete data volumes that aren't attached to a container.
For targeted cleanup: docker image prune -a removes all images not used by any container. docker container prune removes stopped containers. docker volume prune removes unused volumes.
I run docker system prune -a --volumes -f weekly in CI. But never on a production host without verifying no important volumes are dangling.
docker system prune -a --volumes on a shared host without checking if any stopped containers have important volumes. Always inspect volumes first: docker volume ls and docker volume inspect <name>.docker scout Commands: Supply Chain Security Built Into the CLI
Docker Scout is Docker's supply chain analysis tool, deeply integrated into the CLI. It analyzes images for CVEs, policy violations, and provenance. Docker has invested heavily here — it's now the default vulnerability scanner. Key commands: docker scout quickview shows a summary of vulnerabilities, CVSS scores, and available base image updates. docker scout compare highlights which packages changed. docker scout cves lists all CVEs with fix versions. docker scout policy evaluates the image against organization policies (e.g., 'no critical CVEs', 'must use signed base image'). docker scout recommendations suggests base image upgrades to reduce vulnerability count. For CI/CD, run docker scout policy --exit-code --policy-file policies.yaml to fail builds that violate policy. Scout uses the same vulnerability database as Docker Hub's analysis but runs locally without uploading your image — critical for air-gapped environments. Enable Scout: docker scout configure or set DOCKER_SCOUT_ENABLED=1.
docker scout policy --exit-code --policy-file docker-scout-policy.yaml $IMAGE to your CI pipeline. If policy fails, the build fails — no critical CVE reaches production. Pair with docker scout compare in PR comments to show vulnerability delta.docker buildx Deep Dive: Multi-Platform Builds, QEMU, Bake, and Imagetools
docker buildx is the next-generation build system, replacing the classic docker build. Key capabilities: multi-platform builds (build once for linux/amd64, linux/arm64, linux/arm/v7), QEMU emulation for cross-compilation, BuildKit caching, and concurrent builds. Setup: docker buildx create --name mybuilder --bootstrap creates a builder instance. docker buildx use mybuilder activates it. To build for multiple platforms: docker buildx build --platform linux/amd64,linux/arm64 -t myapp:1.0 --push .. QEMU is used automatically for platforms that don't match the host — install it with docker run --privileged tonistiigi/binfmt --install all. New subcommands: buildx bake runs builds from a HCL/JSON/YAML bake file (like Compose for builds), supporting parallel builds and shared variables. buildx imagetools inspects and manipulates multi-arch image manifests without pulling layers: docker buildx imagetools inspect myapp:1.0 shows all platform manifests. buildx create --driver docker-container creates a remote builder running in a container — ideal for CI runners with limited Docker capabilities.
docker compose v2 Specific Commands: What Changed and What's New
Docker Compose v2 (docker compose without the hyphen, built into the CLI) supersedes v1 (docker-compose). Key differences: v2 is a Go plugin integrated into docker, not a standalone Python script. Performance is significantly better. New commands in v2: docker compose watch — monitors source files and auto-syncs changes to running containers (like docker-compose up --watch for development live reload). docker compose up -d in v2 has stricter dependency resolution (v1 sometimes started services out of order). docker compose config --dry-run validates the compose file and prints the resolved config without starting services — critical for CI/CD. docker compose create creates containers without starting them (useful for pre-provisioning). docker compose up --no-build --no-start --wait gives granular control over the lifecycle. Other v2 improvements: docker compose run inherits the compose file's network and volume config automatically (v1 required explicit flags). docker compose logs --since=5m --tail=100 supports native time-based filtering. Migration: docker compose commands accept all v1 flags but the YAML version field is ignored — v2 uses the Compose Specification directly.
depends_on with conditions (condition: service_healthy) only works reliably in v2. Also, docker-compose down -v in v1 stops and removes volumes — in v2, use docker compose down --volumes (flag name changed).New Docker CLI Commands You Should Know: init, sbom, manifest, trust, context, system df
Docker has several modern CLI commands beyond the basics. docker init scaffolds Docker assets for your project interactively — it detects the language (Go, Python, Node.js, Rust, Java, .NET) and generates optimized Dockerfile, .dockerignore, and compose.yml. Run docker init in your project root and answer the prompts. docker sbom generates a Software Bill of Materials (SPDX or CycloneDX format) for any image: docker sbom nginx:1.25 lists all packages, versions, and licenses. docker manifest inspects and pushes multi-arch manifests (prefer buildx imagetools now, but manifest is still useful for low-level ops). docker trust manages Docker Content Trust (DCT) signatures — docker trust sign myapp:1.0 && docker trust inspect --pretty myapp:1.0. docker context switches between Docker endpoints (local, remote SSH, Docker Swarm, ECS, ACI): docker context create remote --docker host=ssh://user@host && docker context use remote. docker system df shows disk usage by type (images, containers, volumes, build cache) — critical for capacity management. Combine with docker system df --verbose for per-image/per-volume breakdown.
docker context create staging --docker host=tcp://staging-host:2375. Then docker context use staging and all commands run remotely. Never SSH into production hosts — use Docker contexts with TLS client certificates for secure remote access.The 4GB Container That Kept Dying
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory.--memory=4g but no --memory-swap limit. Docker defaulted swap to 4g, allowing the container to use up to 8g of combined memory+swap. The Node process grew beyond the 4g limit, hit swap, then the GC couldn't keep up, causing the crash.--memory-swap=4g to match the memory limit, effectively disabling swap. Also add --memory-reservation=3g to signal pressure earlier.- Always set
--memory-swapequal to--memoryunless you explicitly want swap. - Otherwise, you're giving your container a memory limit that's effectively doubled.
docker inspect <container> | jq '.[0].State.OOMKilled' — if true, the container ran out of memory. 2. Increase memory limit: docker update --memory=512m <container>. 3. For long-term fix, add --memory-reservation to signal pressure earlier.docker pull fails with net/http: TLS handshake timeoutdocker run alpine ping -c 4 google.com. 2. If DNS fails, add --dns 8.8.8.8 to Docker daemon config. 3. If behind a proxy, configure HTTP_PROXY in /etc/systemd/system/docker.service.d/http-proxy.conf.docker build fails with no space left on devicedocker system df to see disk usage. 2. Run docker system prune -a to free space. 3. If still full, check /var/lib/docker size and move it to a larger partition if needed.sudo lsof -i :<port>docker ps --filter "publish=<port>"docker stop <container> or change the host port mapping.| File | Command / Code | Purpose |
|---|---|---|
| check-docker-status.sh | systemctl status docker | Why You Need the Docker CLI |
| Dockerfile | FROM node:18-alpine AS builder | Image Management |
| run-nginx.sh | docker run -d \ | Running Containers |
| lifecycle.sh | docker run --rm alpine echo "Hello, ephemeral world!" | Container Lifecycle |
| debug-container.sh | docker run -d --name debug-test -p 9999:80 nginx:alpine | Debugging Running Containers |
| networking.sh | docker network create app-net | Networking |
| volume-example.sh | docker volume create pgdata | Volumes and Data Persistence |
| docker-compose.yml | version: '3.8' | Docker Compose |
| cleanup.sh | docker system df | Cleaning Up |
| docker-scout-examples.sh | docker scout quickview nginx:1.25 | docker scout Commands |
| buildx-deep-dive.sh | docker buildx create --name multiarch --driver docker-container --bootstrap | docker buildx Deep Dive |
| compose-v2-commands.sh | docker compose watch | docker compose v2 Specific Commands |
| new-cli-commands.sh | docker init | New Docker CLI Commands You Should Know |
Key takeaways
latest in production.--restart unless-stopped for long-running services to survive crashes.docker system prune -a --volumes is powerful but dangerousInterview Questions on This Topic
What happens when you run `docker run` without `--restart` and the process inside crashes? How would you handle this in production?
--restart unless-stopped to automatically restart on crash. For critical services, add health checks and monitoring to alert if the container restarts too frequently.Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.
That's Docker. Mark it forged?
7 min read · try the examples if you haven't