Container foundations
Connect images, containers, and registries
Trace an image from a registry to the local cache and into one or more containers without confusing their lifecycles.
When you type nginx:alpine, Docker reads two parts: nginx is the repository, and alpine is the tag. Together they name one image.
Docker looks for it in the local cache first. If it’s not there, it pulls it from a registry. By default that’s Docker Hub. A name like ghcr.io/owner/tool:1.2 points to a different registry, GitHub’s in this case.
Pull, list, run
Let’s pull the Node.js image we’ll use for the rest of the course:
docker pull node:22-alpine
docker image ls node
docker run --rm node:22-alpine node --version
The pull downloads the image in pieces, called layers, and prints a line for each one. docker image ls node shows the image in your local cache, with its image ID and size. The last command starts a container from the image, runs node --version, prints something like v22.x.x, and removes the container.
Run that last command twice. Two containers started and died. The image is still there, untouched. Containers share the read-only layers of the image, and each one gets its own thin writable layer on top. That’s why starting a second container costs almost nothing.
Tags move, digests don’t
Here’s something that bites people in production. A tag is a pointer, and pointers can move.
Today node:22-alpine points at a specific build. Next week the maintainers publish a security fix and point the same tag at a new build. Your docker pull on another machine gets different bytes than the ones you tested.
A digest is different. It’s a sha256:... hash of the exact image content. It can’t change. Two images with the same digest are the same bytes.
You can see the digest of a local image:
docker image inspect node:22-alpine --format '{{index .RepoDigests 0}}'
Tags are fine for humans. When you record a deployment, record the digest too. That lets another machine pull exactly what you tested. This matters most for broad tags like latest or 22-alpine, which move often.
One name, many platforms
One more thing hidden behind a name. node:22-alpine can point to a multi-platform manifest, a list of images for different CPU architectures. Docker picks the one matching your machine. On an Apple Silicon Mac you get the arm64 build, on most servers amd64.
That’s convenient, but it means “it works on my laptop” doesn’t prove it works on your server. Before going to production, check both the digest and the platform with docker image inspect or in the registry UI.
Try this: pull the image, run two short containers from it, and compare the image ID in docker image ls before and after. It’s the same.
Lesson completed