← Learn
Docker Course  /  Phase 1  /  Module 01
Day 1 ~1.5h · hands-on
Phase 1 · Docker · Day 1

Module 01: Images & Containers
The Lifecycle & the Core CLI

Meet Sam, a developer shipping their first containerized app. Foundations told Sam what a container is. This module is where that knowledge becomes muscle memory: the lifecycle every container moves through, and the dozen commands you'll type every working day. Reading is not enough here, so run every command yourself.

~1.5h · hands-on heavy builds on Module 00 daily CLI + lifecycle Docker 29.5 · CachyOS
0

Where we left off

Sam's goal is simple: get one web app running in a container and keep it running. From Foundations, hold these three sentences in your head, because everything in this module hangs off them:

Container = process

A normal host process, isolated by namespaces, bounded by cgroups, on a copy-on-write filesystem.

Image = blueprint

An immutable stack of read-only layers. The class. You run many containers from one image.

Container = instance

A running/stopped image with a thin writable layer + its own namespaces. The object.

Why this module matters

Memorising the flags of docker run matters far less than two things: being able to walk through a container's lifecycle, and knowing how to debug a container that won't start. Both are below.

1

Image and container, made concrete

The image is layers of read-only filesystem. Starting a container adds one thin writable layer on top (copy-on-write). Everything the running process writes lands there. Delete the container and that layer vanishes (the image layers are untouched and shared with every other container started from the same image). This is the trap Sam hit first: a config file edited inside a running container disappeared on the next run.

⬆ Writable container layerper-container · gone on rm
RO layer · app codeimage
RO layer · pip/npm depsimage · shared
RO layer · base OS (e.g. debian)image · shared

Two containers from the same image share the three RO layers; only the top layer differs.

The mental flip juniors miss

Data you write inside a running container is not in the image and does not survive docker rm. Persistence is a separate concern, covered in Module 03 (volumes). For now: treat container filesystems as disposable.

2

The container lifecycle

This is the single most important diagram in the module. Every command you learn is a transition between these states.

image
create
Created
start
Running
pause /
unpause
Paused
Running
stop / kill
Stopped
(Exited)
start
Running
rm
Removed
StateMeansEnters via
(image)Just a template on disk. No process.docker pull / build
CreatedContainer exists, namespaces set up, but the process hasn't started.docker create
RunningPID 1 is alive and executing.docker run / start
PausedProcesses frozen (SIGSTOP via cgroup freezer). RAM kept.docker pause
Stopped / ExitedProcess ended (cleanly or killed). Writable layer still on disk.docker stop / process exits
RemovedGone. Writable layer deleted.docker rm
Key insight

run = create + start. And the container lives exactly as long as its PID 1 (the main process) lives. When PID 1 exits, the container stops. That's why a container running a one-shot command exits immediately, and why docker run ubuntu stops at once but docker run nginx keeps running. Nginx is a long-lived foreground process. Sam's app needs to stay up, so it needs a process like that as PID 1.

stop vs kill

docker stop sends SIGTERM, waits (default 10s grace), then SIGKILL. That is the graceful path. docker kill sends SIGKILL immediately, which is abrupt. In production you almost always want stop so the app can flush and close connections.

3

Anatomy of docker run

One command does a lot. The first time Sam typed it, the flag order felt arbitrary. It isn't. Read it by position: the parts before the image name configure Docker, and everything after the image name is passed into the container.

docker runcreate + start -d --name web -p 8080:80DOCKER options (before image) nginx:1.27IMAGE:TAG nginx -g 'daemon off;'COMMAND + args (into container)

The flags you'll reach for constantly

FlagDoesWhen
-dDetached: run in background, return the prompt.Servers, anything long-lived.
-it-i keep STDIN open + -t allocate a TTY.Interactive shells: docker run -it ubuntu bash.
--nameGive a stable, human name instead of a random one.Always, for anything you'll reference again.
-p H:CPublish: map host port → container port.To reach a server from your browser (full detail in Module 04).
-e K=VSet an environment variable inside the container.Config, secrets-ish (real secrets = Module 07).
--rmAuto-remove the container when it exits.Throwaway / test runs, keeps your machine clean.
--restartRestart policy: no / on-failure / always / unless-stopped.Resilience for long-running services.
foreground vs detached
# Foreground: logs stream to your terminal, Ctrl-C stops it
docker run nginx

# Detached + named + port-mapped: the real-world pattern
docker run -d --name web -p 8080:80 nginx:1.27
# → open http://localhost:8080

# Interactive shell inside a fresh ubuntu (exits when you type `exit`)
docker run -it --rm ubuntu bash
4

The daily-driver CLI

These map one-to-one onto the lifecycle diagram. Commit the shape of each to memory.

CommandTransition / purpose
docker psList running containers. -a = include stopped. -q = IDs only.
docker create IMAGEimage → Created (no process yet).
docker start NAMECreated/Stopped → Running.
docker stop NAMERunning → Stopped (SIGTERM → SIGKILL).
docker restart NAMEstop then start in one call.
docker pause / unpauseRunning ⇄ Paused (freeze without stopping).
docker rm NAMEStopped → Removed. -f force-removes a running one.
docker rename OLD NEWRename without recreating.
docker cp src dstCopy files between host and container.
a typical session
docker ps -a                      # see everything, running or not
docker stop web                    # graceful shutdown
docker start web                   # bring it back, same writable layer
docker rm -f web                   # nuke it, running or not

# Clean up ALL stopped containers at once
docker container prune
Naming & IDs

Every container has a 64-char ID; you can use any unambiguous prefix (docker stop a3f). No --name? Docker invents one like nostalgic_turing. Name things you care about.

5

Looking inside: exec · logs · inspect

This trio is your debugging toolkit. When a container is misbehaving, this is what you reach for.

exec

Run a new process in a running container. Your way in to poke around live.

logs

Read what PID 1 wrote to stdout/stderr. First stop for "why did it crash?".

inspect

Full JSON: config, mounts, network, state, exit code. The ground truth.

debugging a live container
# Get a shell inside the running 'web' container
docker exec -it web sh

# Follow logs live (like tail -f); --tail limits history
docker logs -f --tail 50 web

# Pull one field out of the JSON with a Go template
docker inspect -f '{{.State.Status}} {{.State.ExitCode}}' web
running 0
exec vs attach, a classic trap

docker exec starts a new process (e.g. a second shell), so exiting it leaves the container running. docker attach connects to the existing PID 1's streams, where Ctrl-C can kill the container. Sam learned this the hard way. For debugging, you almost always want exec.

Why exec'd changes don't persist to the image

Install a package via exec and it lands in the writable layer, gone on rm. To bake changes into an image you edit the Dockerfile and rebuild, which is Module 02. Never "fix" images by hand inside a container.

6

Managing images

Containers come from images; images come from a registry (or your own builds). The image-side commands:

CommandPurpose
docker pull IMAGE:TAGDownload an image (layer by layer) from a registry.
docker imagesList local images, their tags and sizes.
docker rmi IMAGERemove an image (must have no containers using it).
docker tag SRC NEWAdd another name/tag to the same image ID.
docker image prune -aReclaim disk: delete dangling / unused images.
docker history IMAGEShow the layers and the command that made each.
Tags are not versions; they're mutable pointers

nginx:latest today and next month can be different images. For reproducibility, pin a specific tag (nginx:1.27) or, best, a digest (nginx@sha256:…). This is a real production pitfall. Registries, digests, and signing are Module 06.

image housekeeping
docker images
REPOSITORY   TAG     IMAGE ID       SIZE
nginx        1.27    a1b2c3d4e5f6   188MB

docker history nginx:1.27        # see how the layers were built
docker image prune -a           # free disk (careful: removes unused images)
7

Tying it together: what docker run nginx really does

The exact trace from Foundations, now grounded in the lifecycle. Be able to narrate this end to end:

  1. Client → daemon. Your CLI sends a REST request over /var/run/docker.sock.
  2. Image resolution. Daemon checks for nginx locally; if missing it pulls the layers from the registry.
  3. Create. Daemon → containerd prepares the container: assembles the overlay filesystem + a writable layer, sets up namespaces. → state Created.
  4. Start. runc applies cgroups and spawns PID 1 (the nginx master process). → state Running.
  5. Live. Networking attached, ports published. Container lives as long as PID 1 lives.
  6. Exit. PID 1 ends → Stopped. docker rm drops the writable layer → Removed.
The clean mental model

"run is create plus start; create sets up namespaces and the writable layer via containerd, start applies cgroups and launches PID 1 via runc, and the container survives only as long as that PID 1." That sentence signals you understand the whole stack, not just the CLI.

8

Hands-on: do this now

This is the exact sequence that took Sam's app from "won't stay up" to shipped. Docker is muscle memory. Run every line, predict the output before you hit enter, then check.

exercise: drive a container through its whole lifecycle
# 1. Start a named web server, detached, on host port 8080
docker run -d --name web -p 8080:80 nginx:1.27

# 2. Confirm it's running, then hit it
docker ps
curl -s localhost:8080 | head -n 5

# 3. Look inside: shell in, check the nginx process is PID 1
docker exec -it web sh -c 'ps -ef | head'

# 4. Watch logs, then refresh the browser/curl and watch a line appear
docker logs -f web   # Ctrl-C to stop following

# 5. Lifecycle: stop → see it Exited → start → it's back
docker stop web && docker ps -a
docker start web

# 6. Prove the writable layer is disposable
docker exec web sh -c 'echo hi > /tmp/proof'
docker rm -f web
docker run -d --name web -p 8080:80 nginx:1.27
docker exec web ls /tmp        # /tmp/proof is GONE: new writable layer

# 7. Clean up
docker rm -f web
  • You saw nginx as PID 1 inside the container.
  • You watched a request appear in logs -f in real time.
  • You proved a file written inside the container did not survive rm + re-run.
  • You can recite: run = create + start, container lives = PID 1 lives.