An image is a read-only template made of layers; a container is a running (or stopped) instance of an image with a thin writable layer on top. Understanding that difference explains most Docker behaviour, including why data written inside a container disappears when the container is removed.
Versions in use on this date. Docker Engine 19.03.9 (released 4 May 2020) on Ubuntu 20.04 LTS. The examples use nginx; nginx 1.18 is the stable branch published in April 2020.
Lifecycle
flowchart TB
REG[Registry Docker Hub] -->|docker pull| IMG[Image on disk]
IMG -->|docker create| CR[Created container]
CR -->|docker start| RUN[Running]
IMG -->|docker run = create + start| RUN
RUN -->|docker stop SIGTERM then SIGKILL| STP[Stopped<br/>writable layer kept]
STP -->|docker start| RUN
STP -->|docker rm| GONE[Removed<br/>writable layer deleted]
RUN -->|docker commit or build| IMG2[New image]
The ten commands
# 1. Get an image (tag = version; default is latest)
docker pull nginx:1.18
# 2. Run it in the background, publish port 8080, give it a name
docker run -d --name web -p 8080:80 nginx:1.18
# 3. See what is running (-a includes stopped)
docker ps
docker ps -a
# 4. Read output (the container's stdout and stderr)
docker logs -f --tail 50 web
# 5. Look inside
docker exec -it web sh
docker exec web nginx -t
# 6. Inspect configuration and state
docker inspect web | less
docker inspect -f '{{.NetworkSettings.IPAddress}} {{.State.Status}}' web
# 7. Resource usage
docker stats --no-stream
# 8. Stop, start, restart
docker stop web && docker start web
docker restart web
# 9. Remove a container (and an image)
docker rm -f web
docker rmi nginx:1.18
# 10. Clean unused objects
docker system df
docker system prune
Test it: curl -I http://localhost:8080 should return HTTP/1.1 200 OK.
The options you will use most with docker run
| Option | Meaning |
|---|---|
-d |
Detached (background) |
-it |
Interactive terminal, for shells |
--rm |
Delete the container when it exits (great for one-off commands) |
--name |
Name instead of a random one |
-p host:container |
Publish a port; -p 127.0.0.1:8080:80 limits it to localhost |
-e KEY=value |
Set an environment variable |
-v host:container |
Mount a directory or volume (next posts) |
--restart unless-stopped |
Start again after reboot or crash |
--memory 512m --cpus 1.5 |
Resource limits |
docker run --rm alpine:3.11 cat /etc/os-release # one-off command
docker run -d --name db -e POSTGRES_PASSWORD=change-me -p 127.0.0.1:5432:5432 postgres:12
docker run -d --restart unless-stopped --memory 256m --name web nginx:1.18
Behaviours worth knowing
- Tags move.
nginx:latesttoday is notnginx:latestnext month. Use specific tags for anything you depend on, and pull deliberately. - Containers are disposable. The writable layer is lost on
docker rm. Keep data in volumes (see the volumes post), and configuration in environment variables or mounted files. - One main process. A container runs until its main process (PID 1) exits;
docker stopsends SIGTERM and after 10 seconds SIGKILL. Programs must handle SIGTERM to shut down cleanly. - Logs go to stdout. Applications should log to stdout/stderr so
docker logsworks; configure log rotation (see the install post). docker execis for debugging, not for configuring production containers by hand. Changes made inside a running container vanish when it is recreated.
Troubleshooting
docker logs web # why did it exit? read this first
docker inspect -f '{{.State.ExitCode}} {{.State.OOMKilled}}' web # 137 + true = out of memory
docker events --since 10m # what the daemon did recently
docker run --rm -it --entrypoint sh nginx:1.18 # get a shell in the image, ignoring its default command
ss -tlnp | grep 8080 # port already in use on the host?
Common errors: port is already allocated (something else uses that host port), no such image (a typo in the tag or a private registry needing docker login), and exited (1) containers that start and stop immediately (check docker logs for the application error).