Skip to main content

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:

BecauseTherefore
No kernel bootsContainers start in milliseconds, not seconds
The kernel is sharedA Linux container cannot run on a Windows kernel (Docker Desktop quietly runs a Linux VM to hide this)
It is just a processWhen 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
FlagMeans
-dDetached — run in the background and give me my prompt back
--name webGive it a name, so you can refer to it without copying an ID
-p 8081:80Publish host port 8081 to container port 80. Host first.
-e KEY=valueSet an environment variable inside the container
-v name:/pathMount a volume — see below
--rmDelete 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 ps showing (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 -v on a production box would delete motorph_payroll_db_data. The -v is 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 -v is a different order of danger from down.

Next: 06 — Your first Dockerfile.