The core idea
Almost everything in Docker is one of three things:
- An image is a read-only template: a stack of filesystem layers plus settings like which command to run.
- A container is a running (or stopped) instance of an image, with a thin writable layer of its own on top.
- A registry is a server that stores images so you can push and pull them, like Docker Hub or GitHub's GHCR.
If you write code, a familiar comparison helps: an image is like a class and a container is like an object. You can create many containers from one image, and changing one container doesn't change the image or its siblings.
Images: layers plus settings
An image has two parts:
- Layers: read-only snapshots of files. Each step of a Dockerfile that changes files adds one (02.02 Layers and the build cache).
- Config: environment variables, the working directory, the default command, exposed ports.
Every image has a digest, a SHA-256 hash of its contents. When this lesson was written, nginx:alpine had the digest
sha256:785835ea65db4bba6f021acc0f3be91b0fef76a53fe41f8588c6698d2aed0606.
Change one byte and the digest changes. That makes it the only name that always means exactly the same image.
Containers: an image plus a writable layer
When you start a container, Docker stacks a small writable layer on top of the image's read-only layers. Anything the container writes, creates or deletes goes into that layer. The image underneath never changes.
This is why starting a container is nearly instant and nearly free on disk: nothing is copied. It's also why the writable layer is temporary. Delete the container and its changes are gone, unless they were in a volume (04.01 The writable layer is temporary).
Containers one and two both run from nginx:alpine. Inside one, you create /tmp/hello.txt. Can two see it?
Registries and image names
A registry stores images in repositories, and each repository holds many tags. A full image name tells Docker exactly where to look:
So the short name you type most often expands to something longer:
| You type | Docker reads it as |
|---|---|
nginx | docker.io/library/nginx:latest |
node:22-slim | docker.io/library/node:22-slim |
ghcr.io/sehaj/api:1.2.0 | exactly that |
nginx@sha256:785835ea… | that exact image, by digest |
Tags are movable labels. nginx:1.27 today may point at a different image next month after a patch release.
Digests never move. That's why production deploys often pin by digest; more in 02.06 Tags, digests and registries.
See it yourself
Start two containers from the same image, write a file in one, and ask Docker what changed in each.
$ docker run -d --name one nginx:alpine$ docker run -d --name two nginx:alpine$ docker exec one sh -c 'echo hi > /tmp/hello.txt'$ docker diff oneC /runA /run/nginx.pid...C /tmpA /tmp/hello.txt...$ docker diff twoC /etcC /etc/nginx...A /run/nginx.pid
Output trimmed. A means added, C changed, D deleted. Both list files nginx wrote when it started, but only one has your new file. Clean up with docker rm -f one two.
STUCK? ▸
Both containers show changes under /etc/nginx, /run and /var/cache/nginx: that's nginx itself writing at startup. Only one should list /tmp/hello.txt. Container names must be at least two characters, which is why these aren't called a and b.
Recap
Interview questions
What is the difference between a Docker image and a container?
Why would you deploy an image by digest instead of by tag?