Images, Containers and the Difference
An image is a recipe and a container is the meal. Why that distinction explains almost every confusing thing Docker does, plus the commands to inspect both.
An image is a recipe and a container is the meal. Why that distinction explains almost every confusing thing Docker does, plus the commands to inspect both.
You have already used both words. docker run nginx pulled an
image and started a container, and so far you have had no
reason to care which was which. That stops being true the moment
something surprises you — a file you created inside a container
that vanished, an image you cannot delete, a latest tag that
turned out not to be the latest.
This is the lesson that makes those surprises stop being surprising. By the end of it you will know precisely what each one is, how to inspect both, and how to clean up the ones you no longer want.
The kitchen metaphor is exact enough to lean on. An image is a recipe: precise, repeatable, and not edible. A container is the meal you cooked from it.
A read-only snapshot on disk.
An operating system's files, a language runtime, your application's code, and a note saying what to run when it starts.
It sits there doing nothing at all.
A live process, running from that snapshot.
It has its own view of the system and uses the image's files as its starting point.
Nothing it does can change the image — which is why the same image produces an identical starting point every time, on your laptop and in production alike.
docker run -d --name web1 nginx
docker run -d --name web2 nginx
docker run -d --name web3 nginxThree containers, three independent processes with three separate filesystems, one image on disk shared between them.
An image is not one solid block. It is a stack of layers, each one a set of filesystem changes on top of the layer below — applied in order and presented to the container as one merged filesystem.
Layers are shared. If you have three different Python images all built on the same Python base, that base exists once on your disk and all three point at it. The next section draws the stack.
You are reading the image's construction history from top to
bottom, newest first. The <missing> ids are not an error — they
are layers that arrived as part of a pull rather than being built
here, so your machine never gave them their own image id.
If an image is read-only, how does a container write anything?
When a container starts, Docker adds one more layer on top of the image's stack: a thin writable layer belonging to that container alone.
Created when the container starts, deleted when the container is deleted. Every file it creates or modifies goes here.
Read-only, and shared by every container from this image.
Read-only.
Read-only, and shared with every other image built on the same base — which is why eight images totalling "3 GB" cost your disk far less than that.
Watch it happen:
Start a new container from the same image and the file is not there:
The file was never in the image. It was in the first container's writable layer, and the second container has its own empty one. Start the original container again and the file is back, because that layer still exists:
Delete that container and the layer goes with it, permanently.
You typed nginx and got an image. The full form of what you
asked for is:
Docker filled in the defaults: Docker Hub as the registry,
library as the owner for official images, and latest as the
tag. A tag you write yourself looks like nginx:1.27 or
myapp:v2.
A tag is a human-friendly label pointing at a particular image. It is a pointer, not a name carved into the image, and the thing it points at can be changed by whoever owns the repository.
Underneath every tag is a digest — a sha256: hash of the
image's actual content. A digest is immutable by construction: if
the content changes at all, it is a different digest.
Bad — the base image can change under you between builds:
Good — pins the version you tested against:
latest is not a promise about newness; it is just the tag applied
when nobody specified one. The first version builds against
whatever latest happens to point at today, so a build that
worked last week can fail this week with no change on your side —
and the failure lands on whoever is unlucky, not on the person who
changed the tag.
Two families of commands, one for each concept. Images first:
Then containers:
docker exec is worth dwelling on, because it is the difference
between looking at a container and looking inside one. It runs a
new process in a container that is already running — as opposed to
docker run, which creates a whole new container.
docker inspect prints a wall of JSON. Narrow it with a format
string when you know what you want:
Now the rule that explains a confusing error message:
You cannot delete an image while a container is using it, because that container's filesystem is built on it. Containers come off first, then images:
Docker also accumulates two kinds of clutter worth knowing by name.
Dangling images are layers left behind when a rebuild moved a
tag to a new image, leaving the old one nameless — they show as
<none> in docker images. The build cache is the stored
intermediate layers from your builds, and on a busy machine it can
reach tens of gigabytes.
You have been running other people's images. The next lesson is where you write your own: a Dockerfile that turns your application into an image you can run, tag and share — the point at which Docker starts being about your code rather than about nginx.
Before that, one experiment worth doing yourself. Start a container
from ubuntu, install something inside it with apt, exit, and
start a fresh container from the same image. Watch your install
disappear. Understanding why that is correct behaviour rather
than a bug is the whole content of this lesson.
IMAGE CREATED CREATED BY SIZE
a480a496ba95 2 weeks ago CMD ["nginx" "-g" "daemon off… 0B
<missing> 2 weeks ago EXPOSE map[80/tcp:{}] 0B
<missing> 2 weeks ago COPY docker-entrypoint.sh / #… 391B
<missing> 2 weeks ago RUN /bin/sh -c set -x && apt… 114MB
<missing> 2 weeks ago ADD file:4f4fb700ef54461cfa0… 74.8MBdocker.io/library/nginx:latest
^^^^^^^^^ ^^^^^^^ ^^^^^ ^^^^^^
registry owner name tagError response from daemon: conflict: unable to delete a480a496ba95
(cannot be forced) - image is being used by running container c9a1f4e2docker history nginxdocker run -it --name experiment ubuntu bash
# inside the container:
echo "notes" > /tmp/notes.txt
cat /tmp/notes.txt # notes
exitdocker run -it --rm ubuntu cat /tmp/notes.txt
# cat: /tmp/notes.txt: No such file or directorydocker start -ai experiment
cat /tmp/notes.txt # notesdocker images --digests nginx
docker pull nginx@sha256:0c86dddac19f2ce4fd716ac58c0fd87bf69b...FROM python:latestFROM python:3.12-slimdocker images # images on this machine
docker pull redis:7 # fetch one without running it
docker inspect nginx # everything Docker knows about it
docker history nginx # its layersdocker ps # running containers
docker ps -a # all of them, including exited
docker inspect web1 # config, network, mounts, state
docker top web1 # processes running inside it
docker exec -it web1 bash # a shell inside a running containerdocker exec web1 ls /usr/share/nginx/html # one command
docker exec -it web1 sh # an interactive shelldocker inspect --format '{{.State.Status}}' web1docker stop web1 web2 web3
docker rm web1 web2 web3
docker rmi nginxdocker container prune # stopped containers
docker image prune # dangling images only
docker system df # what is using your disk
docker system prune # containers, networks, dangling images# Images: what you build and store
docker images # list local images
docker images --digests # ...with their content hashes
docker pull redis:7 # fetch without running
docker history nginx # the layers it is made of
docker inspect nginx # full metadata as JSON
docker rmi nginx # delete an image
# Containers: images that are running
docker ps # running
docker ps -a # all, including exited
docker inspect web1 # config, state, mounts
docker top web1 # processes inside it
docker exec -it web1 sh # shell inside a running one
docker exec web1 ls /app # one command inside it
docker rm web1 # delete a stopped container
# Referring to an image precisely
nginx # = docker.io/library/nginx:latest
nginx:1.27 # a specific tag
nginx@sha256:0c86ddd... # an exact, immutable image
# Reclaiming disk
docker system df # what is using space
docker container prune # every stopped container
docker image prune # dangling (<none>) images
docker system prune # the safe combined sweep