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 <container>. 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 <container> sh. 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 <container> | jq '.[0].NetworkSettings.IPAddress'.
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>.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 |
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?
4 min read · try the examples if you haven't