05 — Containers 101
Read this first: this lesson makes containers stop being magic. It answers precisely what an image is, what a container is, why they are not virtual machines, and what survives when one is deleted. It does not build an image — that is lesson 06.
Time: about 40 minutes. You need Docker installed; docker --version should print something.
The one sentence
An image is a stack of read-only filesystem layers plus a default command. A container is one running process using that filesystem, with its own view of the network and process list.
Everything below is a consequence of that sentence.
Not a virtual machine
This is the single most common wrong model, and it produces wrong predictions all day.
A virtual machine emulates hardware and boots a whole operating system, with its own kernel. A container does not. A container is a normal process on your machine's existing kernel, which Linux has been convinced to lie to about what it can see — its own filesystem root, its own network interfaces, its own process IDs.
Three predictions fall out of that, and all three are correct:
| Because | Therefore |
|---|---|
| No kernel boots | Containers start in milliseconds, not seconds |
| The kernel is shared | A Linux container cannot run on a Windows kernel (Docker Desktop quietly runs a Linux VM to hide this) |
| It is just a process | When the process exits, the container stops. There is no "idle" container |
That last one catches everyone. Let's prove it.
Break it on purpose: the container that "won't stay running"
Predict: you are about to run three containers. Each prints a line and finishes. Afterwards, how
many containers do you think docker ps will list?
for i in 1 2 3; do docker run --name "l05-$i" alpine:3 echo "hello from container $i"; done
hello from container 1
hello from container 2
hello from container 3
docker ps
NAMES STATUS
Nothing. Now the same question with -a, which means "all, including stopped":
docker ps -a --filter 'name=l05'
NAMES STATUS
l05-3 Exited (0) Less than a second ago
l05-2 Exited (0) Less than a second ago
l05-1 Exited (0) 1 second ago
The containers exist. They are just not running, because their process finished. echo printed
a line and exited 0, and a container whose process has exited is a stopped container.
This is the correct answer to "my container keeps stopping". It is not a bug. The container ran the command it was given, the command ended, and so did the container. A web server container stays up because a web server does not exit.
Note Exited (0) — that is the exit code from lesson 01, surfacing again.
Exited (1) or Exited (137) tells you how it died. 137 means it was killed (128 + signal 9),
which usually means it ran out of memory.
One image, many containers
docker images alpine:3
alpine:3 d529dd0c6e55 8.42MB
One image, 8.42 MB, and you just ran three containers from it. The image was not copied three times. Containers share the image's read-only layers and add a thin writable layer of their own on top. That is why starting a container is nearly free.
Clean up the stopped ones:
docker rm l05-1 l05-2 l05-3
docker rm deletes a stopped container. docker rm -f force-removes a running one. --rm on
docker run deletes it automatically when it exits, which is what you want for one-off commands.
The flags you will actually use
Run something that stays up:
docker run -d --name web -p 8081:80 nginx:alpine
| Flag | Means |
|---|---|
-d | Detached — run in the background and give me my prompt back |
--name web | Give it a name, so you can refer to it without copying an ID |
-p 8081:80 | Publish host port 8081 to container port 80. Host first. |
-e KEY=value | Set an environment variable inside the container |
-v name:/path | Mount a volume — see below |
--rm | Delete the container when it exits |
Then the four commands you will use forever:
docker ps # what is running
docker logs web # what did it say
docker logs -f web # ...and keep following
docker exec -it web sh # open a shell inside it
docker stop web && docker rm web
docker exec -it web sh is worth understanding: it starts a second process inside the container
that is already running. -i keeps stdin open, -t gives it a terminal. It is how you look around
inside a running container.
Break it on purpose: what survives deletion
Predict: you will write a file inside the running container, delete the container, and start a new one from the same image. Is the file there?
docker exec web sh -c 'echo "I was here" > /usr/share/nginx/html/index.html'
docker exec web cat /usr/share/nginx/html/index.html
I was here
docker rm -f web
docker run -d --name web -p 8081:80 nginx:alpine
docker exec web cat /usr/share/nginx/html/index.html
The file is gone — you get nginx's default page back. A container's writable layer dies with the container. Anything you want to keep has to live somewhere else, and that somewhere is a volume:
docker run -d --name web -v web_content:/usr/share/nginx/html nginx:alpine
A named volume is storage managed by Docker that outlives any container using it. This is exactly
why the MotorPH database has motorph_payroll_db_data:/var/lib/postgresql/data in its compose file,
and why docker compose down -v — which deletes volumes — is a different and much more dangerous
command than docker compose down.
It is also why a rollback changes your code but not your data. You will see that directly in the capstone.
Break it on purpose: the name collision
docker run -d --name web nginx:alpine
docker: Error response from daemon: Conflict. The container name "/web" is already in use
Container names are unique per machine. This matters more than it sounds: on the real MotorPH VPS, four separate compose projects share one host, and deploy/preflight.sh has a dedicated check for exactly this collision — it inspects each container's compose-project label to tell "our container" apart from "someone else's container that happens to be holding our name".
Clean up:
docker rm -f web
Where this shows up in MotorPH
docker psshowing(healthy)next to the backend is a healthcheck result, not a guess. You will write one in lesson 08.- The backend image is built
FROM eclipse-temurin:21-jre-alpine— a JRE, not a JDK, because the runtime image should not carry a compiler. That is lesson 07's topic. docker compose down -von a production box would deletemotorph_payroll_db_data. The-vis the entire difference between "restart the stack" and "destroy the database".
Recap
- An image is read-only layers plus a default command; a container is one process using it. Not a VM — it shares your kernel, which is why it starts instantly.
- A container stops when its process exits. "It won't stay running" almost always means "the command finished".
- The writable layer dies with the container. Volumes are the only thing that survives, which is
why
down -vis a different order of danger fromdown.
Next: 06 — Your first Dockerfile.