Home DevOps Docker CLI Commands Reference: The Only Guide You'll Need to Ship Containers Without Regret
Beginner 4 min · July 11, 2026

Docker CLI Commands Reference: The Only Guide You'll Need to Ship Containers Without Regret

Docker CLI commands explained from zero.

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.

Follow
Production
production tested
July 11, 2026
last updated
258
articles · all by Naren
Before you start⏱ 35 min
  • A terminal
  • Docker installed (any recent version)
  • Willingness to break things in a sandbox
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer

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.

✦ Definition~90s read
What is Docker CLI Commands Reference?

The Docker CLI is your command-line interface to the Docker daemon. It lets you build, run, stop, and manage containers and images. Every command maps to a REST API call under the hood.

Think of Docker CLI as a remote control for a container factory.
Plain-English First

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).

check-docker-status.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
# io.thecodeforge — DevOps tutorial

# Check if Docker daemon is running
systemctl status docker

# Or for non-systemd systems
docker info

# If daemon is down, start it
sudo systemctl start docker

# Verify with a simple command
docker run --rm hello-world
Output
● docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2024-01-15 10:23:45 UTC; 2h 30min ago
...
Hello from Docker!
This message shows that your installation appears to be working correctly.
Senior Shortcut:
Alias 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.

DockerfileDOCKERFILE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# io.thecodeforge — DevOps tutorial

FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/server.js"]
Output
Successfully built a1b2c3d4e5f6
Successfully tagged myapp:1.0.0
Never Do This:
Using 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.

run-nginx.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
# io.thecodeforge — DevOps tutorial

# Run nginx with restart policy, port mapping, and a volume for static files
docker run -d \
  --name web \
  --restart unless-stopped \
  -p 8080:80 \
  -v /home/user/html:/usr/share/nginx/html:ro \
  nginx:1.25.3

# Verify it's running
docker ps --filter "name=web"
Output
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1b2c3d4e5f6 nginx:1.25.3 "/docker-entrypoint.…" 5 seconds ago Up 4 seconds 0.0.0.0:8080->80/tcp web
Production Trap:
Using --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.

lifecycle.shBASH
1
2
3
4
5
6
7
8
#!/bin/bash
# io.thecodeforge — DevOps tutorial

# Start a temporary container that prints a message and exits
docker run --rm alpine echo "Hello, ephemeral world!"

# The container is gone after exit. Verify:
docker ps -a | grep alpine  # Should return nothing
Output
Hello, ephemeral world!
# No output from grep — container was removed automatically
Senior Shortcut:
Alias 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.

debug-container.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash
# io.thecodeforge — DevOps tutorial

# Run a simple web server in background
docker run -d --name debug-test -p 9999:80 nginx:alpine

# Check logs
docker logs --tail 10 debug-test

# Enter the container
docker exec -it debug-test sh
# Inside: run 'curl localhost' to test the web server

# Inspect IP address
docker inspect debug-test | jq -r '.[0].NetworkSettings.IPAddress'

# Clean up
docker rm -f debug-test
Output
10.0.0.2
# (plus nginx access logs from docker logs)
The Classic Bug:

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.

networking.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
# io.thecodeforge — DevOps tutorial

# Create a network
docker network create app-net

# Run two containers on the same network
docker run -d --name api --network app-net alpine sleep 3600
docker run -d --name web --network app-net alpine sleep 3600

# From web, ping api by name
docker exec web ping -c 1 api

# Clean up
docker rm -f api web
docker network rm app-net
Output
PING api (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.123 ms
--- api ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
Production Trap:
Using --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.

volume-example.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/bash
# io.thecodeforge — DevOps tutorial

# Create a named volume
docker volume create pgdata

# Run Postgres with the volume
docker run -d \
  --name postgres \
  -e POSTGRES_PASSWORD=secret \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16

# Verify data persists
docker exec postgres psql -U postgres -c "CREATE DATABASE test;"
docker rm -f postgres

# Run a new Postgres container with the same volume
docker run -d \
  --name postgres2 \
  -e POSTGRES_PASSWORD=secret \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16

# Check if database 'test' exists
docker exec postgres2 psql -U postgres -l | grep test

# Clean up
docker rm -f postgres2
docker volume rm pgdata
Output
test | postgres | UTF8 | en_US.utf8 | en_US.utf8 |
Never Do This:
Using --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.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# io.thecodeforge — DevOps tutorial

version: '3.8'

services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro
    depends_on:
      - api

  api:
    build: ./api
    environment:
      - DB_HOST=db
      - DB_PASSWORD=secret
    depends_on:
      - db

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:
Output
Creating network "myapp_default" with driver "bridge"
Creating volume "myapp_pgdata" with default driver
Creating myapp_db_1 ... done
Creating myapp_api_1 ... done
Creating myapp_web_1 ... done
Senior Shortcut:
Use 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.

cleanup.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
# io.thecodeforge — DevOps tutorial

# See what's taking space
docker system df

# Dry run: show what would be removed
docker system prune -a --volumes --dry-run

# Actual cleanup (use with caution!)
docker system prune -a --volumes -f

# Verify freed space
docker system df
Output
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 12 5 2.345GB 1.123GB (47%)
Containers 8 3 1.234GB 0.567GB (45%)
Local Volumes 6 2 0.789GB 0.456GB (57%)
Build Cache 0 0 0B 0B
Total reclaimed space: 2.146GB
Production Trap:
Running 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>.
● Production incidentPOST-MORTEMseverity: high

The 4GB Container That Kept Dying

Symptom
A Node.js API container would run for 3-4 hours, then crash with FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory.
Assumption
The team assumed a memory leak in the application code.
Root cause
The container was started with --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.
Fix
Add --memory-swap=4g to match the memory limit, effectively disabling swap. Also add --memory-reservation=3g to signal pressure earlier.
Key lesson
  • Always set --memory-swap equal to --memory unless you explicitly want swap.
  • Otherwise, you're giving your container a memory limit that's effectively doubled.
Production debug guideSystematic recovery paths for the failure modes engineers actually hit.3 entries
Symptom · 01
Container exits with code 137 (OOMKilled)
Fix
1. Check 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.
Symptom · 02
docker pull fails with net/http: TLS handshake timeout
Fix
1. Check internet connectivity: docker 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.
Symptom · 03
docker build fails with no space left on device
Fix
1. Run docker 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.
★ Docker CLI Triage Cheat SheetFirst-response commands for when things go wrong — copy-paste ready.
Container won't start: `docker: Error response from daemon: driver failed programming external connectivity on endpoint`
Immediate action
Check if the port is already in use on the host.
Commands
sudo lsof -i :<port>
docker ps --filter "publish=<port>"
Fix now
Stop the container using the port: docker stop <container> or change the host port mapping.
`docker exec` fails: `the input device is not a TTY`+
Immediate action
You forgot the `-it` flags.
Commands
docker exec -it <container> sh
Fix now
Always use -it together for interactive sessions.
Container runs but can't connect to the internet+
Immediate action
Check DNS resolution inside the container.
Commands
docker exec <container> cat /etc/resolv.conf
docker exec <container> ping -c 4 8.8.8.8
Fix now
Pass --dns 8.8.8.8 to docker run or configure Docker daemon DNS.
`docker build` is slow because it's not using cache+
Immediate action
Check if the Dockerfile layers are ordered correctly.
Commands
docker build --no-cache -t test .
docker history test
Fix now
Reorder Dockerfile: put infrequent changes (like COPY package*.json) before frequent changes (like COPY .).
Featuredocker rundocker-compose up
Single containerYesYes (but overkill)
Multi-containerManual (multiple commands)Yes (single command)
Network creationManual (--network)Automatic
Volume managementManual (-v)Declarative in YAML
Environment variablesManual (-e)Declarative in YAML
ScalingNot supportedSupported (docker-compose up --scale)
Production readinessLow (no health checks, rolling updates)Medium (basic health checks)
⚙ Quick Reference
9 commands from this guide
FileCommand / CodePurpose
check-docker-status.shsystemctl status dockerWhy You Need the Docker CLI
DockerfileFROM node:18-alpine AS builderImage Management
run-nginx.shdocker run -d \Running Containers
lifecycle.shdocker run --rm alpine echo "Hello, ephemeral world!"Container Lifecycle
debug-container.shdocker run -d --name debug-test -p 9999:80 nginx:alpineDebugging Running Containers
networking.shdocker network create app-netNetworking
volume-example.shdocker volume create pgdataVolumes and Data Persistence
docker-compose.ymlversion: '3.8'Docker Compose
cleanup.shdocker system dfCleaning Up

Key takeaways

1
Always pin image tags to specific versions
never use latest in production.
2
Set --restart unless-stopped for long-running services to survive crashes.
3
Use named volumes for production data
bind mounts are for development only.
4
docker system prune -a --volumes is powerful but dangerous
always dry-run first.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
What happens when you run `docker run` without `--restart` and the proce...
Q02SENIOR
When would you choose a bind mount over a named volume in production?
Q03SENIOR
A container is running out of memory and getting OOMKilled. What's your ...
Q04JUNIOR
What is the difference between `docker stop` and `docker kill`?
Q05SENIOR
You have a multi-service application. How would you design the networkin...
Q06SENIOR
How would you handle rolling updates of a containerized application with...
Q01 of 06SENIOR

What happens when you run `docker run` without `--restart` and the process inside crashes? How would you handle this in production?

ANSWER
The container exits and stays stopped. In production, always use --restart unless-stopped to automatically restart on crash. For critical services, add health checks and monitoring to alert if the container restarts too frequently.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
What is the difference between `docker run` and `docker start`?
02
How do I copy a file from a container to my host?
03
How do I see the logs of a container that has already exited?
04
What happens to the data in a container when I run `docker rm`?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.

Follow
Verified
production tested
July 11, 2026
last updated
258
articles · all by Naren
🔥

That's Docker. Mark it forged?

4 min read · try the examples if you haven't

Previous
Docker Installation and Setup
20 / 32 · Docker
Next
Dockerfile Best Practices