Skip to main content
ANVISoftware Solutions
Lesson 3 of 16Intermediate14 min

Images and Containers

By the end of this lesson

Distinguish the package from the running instance.

An image is a read-only package. A container is a running instance of one. That distinction was introduced alongside the virtual machine comparison; this lesson goes into what each one is actually made of, because the practical rules of working with Docker fall straight out of it.

An image has two parts. The first is a stack of read-only layers holding files: a base operating system layer, then whatever each build instruction added. The second is metadata — which command to run on start-up, which user to run it as, which environment variables to set, which port the image documents.

Starting a container adds a thin writable layer on top of that stack and runs the configured command. The image itself never changes. Every write the container performs lands in its own writable layer, which belongs to that container and nothing else.

The vocabulary that appears in every Docker command from here on:

Layer
One read-only set of filesystem changes produced by a single build instruction. Layers stack, and identical layers are stored once and reused across images.
Image
A stack of layers plus metadata, identified by a content-derived digest. Read-only, and identical wherever it runs.
Tag
A human-readable label pointing at an image, written as name:tag — for example postgres:16. The Building Images lesson covers how movable those labels are.
Registry
A server that stores images so they can be shared. Docker Hub is one; cloud providers and private registries work the same way.
Container
One running or stopped instance of an image, with its own writable layer, its own name and its own identifier.
One image, three containers
Shell
# Fetch an image without starting anything
docker pull postgres:16

# Three isolated databases from that one image
docker run -d --name test-db-1 -p 5441:5432 \
  -e POSTGRES_PASSWORD=local-dev-placeholder postgres:16
docker run -d --name test-db-2 -p 5442:5432 \
  -e POSTGRES_PASSWORD=local-dev-placeholder postgres:16
docker run -d --name test-db-3 -p 5443:5432 \
  -e POSTGRES_PASSWORD=local-dev-placeholder postgres:16

# Three containers running
docker ps

# Still one image
docker images postgres
  • docker pull downloads the layers and stores them locally. It starts nothing — this is the package arriving, not the application running.
  • Each docker run creates a separate container from the same layers. They share the read-only layers on disk, so the three cost far less space than three copies would.
  • The names and host ports have to differ, because those belong to the containers rather than the image. The port inside each container is 5432 in all three cases.
  • docker images still lists one postgres:16 entry. Counting containers and counting images are different questions.

Why changes inside a container do not last

Proving the writable layer is per-container and temporary
Shell
# Write a file inside the first container
docker exec test-db-1 sh -c "echo note > /tmp/reminder.txt"
docker exec test-db-1 cat /tmp/reminder.txt      # note

# The second container never saw it
docker exec test-db-2 cat /tmp/reminder.txt      # No such file or directory

# Stopping keeps the writable layer
docker stop test-db-1 && docker start test-db-1
docker exec test-db-1 cat /tmp/reminder.txt      # note

# Removing the container discards it
docker rm -f test-db-1
docker run -d --name test-db-1 -p 5441:5432 \
  -e POSTGRES_PASSWORD=local-dev-placeholder postgres:16
docker exec test-db-1 cat /tmp/reminder.txt      # gone
  • docker exec runs an extra command inside a container that is already running. It is how you look inside one without restarting it.
  • The file is invisible to the second container because each container has its own writable layer. Sharing files between containers takes a volume, covered later in the course.
  • Stop and start reuse the same container, so the writable layer survives. This is worth knowing, because it makes the next step surprising if you have not separated the two ideas.
  • docker rm deletes the container and its writable layer. The replacement is built from the image alone, so it knows nothing about the file. The image never changed at any point.

Two things with similar names and almost nothing in common:

 ImageContainer
What it isStacked read-only layers plus start-up metadataA process, plus one writable layer of its own
Can it change?No. A new build produces a new imageYes, and the changes belong to that container only
How many from oneOne image, any number of containersEach container comes from exactly one image
LifetimeUntil you delete it, or the registry doesUntil it is removed — which in production is routine
Listed bydocker imagesdocker ps, or docker ps -a to include stopped ones
Where the definition livesA Dockerfile in version controlThe run command or Compose file that started it

Summary

  • An image is read-only layers plus start-up metadata; a container is a process with one writable layer
  • Any number of containers can run from one image, sharing its layers on disk
  • A container's writable layer survives stop and start, and dies with the container
  • Settings that are true everywhere belong in the image; settings that vary belong in the environment
  • Editing a running container by hand leaves no record and does not survive replacement

Practice

Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.

Try it yourself

Two containers, one image

Start two containers from the same image with different names. Create a file inside the first one using docker exec.

Check whether the second container can see it. Then remove the first container, start a replacement with the same name, and check again.

Show solution

The second container cannot see the file, and the replacement cannot either. Each container gets its own writable layer, and removing a container throws that layer away.

The point of doing this by hand is that it makes the phrase "containers are disposable" concrete. It is not a recommendation about how to treat them — it is a description of what happens to anything you put inside one.

Shell
docker run -d --name api-a alpine:3.20 sleep 600
docker run -d --name api-b alpine:3.20 sleep 600

docker exec api-a sh -c "echo hello > /tmp/scratch.txt"
docker exec api-a cat /tmp/scratch.txt
docker exec api-b cat /tmp/scratch.txt

docker rm -f api-a
docker run -d --name api-a alpine:3.20 sleep 600
docker exec api-a cat /tmp/scratch.txt

Think about it

Think about it

A colleague fixes a failing employees API in a test environment by opening a shell in the container and correcting a connection string in a configuration file. The API recovers. Two days later it fails the same way.

What happened, and where should the fix have gone?

Show solution

Something replaced the container — a redeployment, a restart of the host, a scaling event. The new container came from the image, which still carried the wrong connection string.

A connection string differs between environments, so it does not belong in the image either. It belongs in configuration supplied at run time: an environment variable or a mounted file, set per environment.

There is a second cost worth naming. While the hand-edited container was running, the deployed image no longer matched what was actually in production. Anyone reading the repository would have drawn the wrong conclusion about the state of the system.

Knowledge check

Nothing is recorded and there is no score. The explanation appears either way.

You start four containers from one image. How many copies of the image's layers are stored on disk?
Which operation discards everything a container has written to its own filesystem?

Saved in this browser only.