sehaj/learn
Docker/02 Images

02.06 · with code · 12 min

Tags, digests and registries

Push your image to a registry, learn why a tag can change under you, and how digests and multi-platform images actually work.

The core idea

A tag is a human-friendly pointer, like a git branch: notes-api:1.0.0 points at an image, and it can be moved to point at a different one. A digest is the image's content hash, like a git commit SHA: it can only ever mean one exact image. You push and pull by tag for convenience, and deploy by digest when you need certainty.

Push to a registry

Pushing works the same for Docker Hub, GHCR or a private registry: tag the image with the registry's address, then push. To try it without an account, run a registry locally (the official registry image):

❯terminal
$ docker run -d --name registry -p 5050:5000 registry:2
$ docker tag notes-api localhost:5050/notes-api:1.0.0
$ docker push localhost:5050/notes-api:1.0.0
The push refers to repository [localhost:5050/notes-api]
b902110041f8: Pushed
3919ea552a8c: Pushed
467306870dd0: Pushed
...
1.0.0: digest: sha256:ce278236dbb7cebf92d1289392cfdca2e8463f61c63cee64112d28ba527c1569 size: 856

docker tag doesn't copy anything; it adds another name for the same image. The push uploads each layer the registry doesn't already have, then reports the digest. Push the same image again and every layer is skipped.

For GitHub's registry, the only differences are the name and logging in first:

ghcr
echo $GITHUB_TOKEN | docker login ghcr.io -u YOUR_USERNAME --password-stdin
docker tag notes-api ghcr.io/YOUR_USERNAME/notes-api:1.0.0
docker push ghcr.io/YOUR_USERNAME/notes-api:1.0.0

The token needs the write:packages scope. You'll automate this from CI in 07.06 Project: ship with GitHub Actions.

Tags move

Nothing stops anyone with push access from pushing a different image under an existing tag. Official images do it on purpose: node:22-alpine is re-pushed for every Node 22 patch and every Alpine security fix. So the same FROM line can give you a different base next week.

Pinning a base image
node:22-alpinereadable, moves
@
sha256:fe4f6d74e203…exact, never moves
With both, Docker uses the digest and ignores the tag. The tag stays for humans.

You can find an image's digest with docker image inspect:

❯terminal
$ docker image inspect --format '{{json .RepoDigests}}' node:22-alpine
["node@sha256:fe4f6d74e20370acb04ad889077d14d0f64a9c8ea6a42bc60cddf1e844699914"]

Your digest will differ: this one is from the day it was measured, which is exactly the point.

One tag, many platforms

How does node:22-alpine work on both an Intel server and an Apple Silicon laptop? The tag doesn't point at one image. It points at an index (also called a manifest list) that lists one image per platform:

❯terminal
$ docker buildx imagetools inspect node:22-alpine
Name: docker.io/library/node:22-alpine
MediaType: application/vnd.oci.image.index.v1+json
Digest: sha256:fe4f6d74e20370acb04ad889077d14d0f64a9c8ea6a42bc60cddf1e844699914
Manifests:
Name: docker.io/library/node:22-alpine@sha256:025b8e975a61…
Platform: linux/amd64
...

Output trimmed. That index also lists linux/arm64, linux/arm/v6, linux/arm/v7 and linux/s390x. When you pull, Docker picks the one that matches your machine. An image you build on an M-series Mac is linux/arm64 only, and won't run on a typical amd64 server without emulation. Building for both is 07.04 Multi-platform builds.

A tagging scheme that works

TagUse
1.4.2an exact release; never re-push it
1.4, 1move to the latest patch or minor; convenient for consumers
sha-3f9a1c2the git commit it was built from; great for tracing a deploy back to code
latestat most a convenience for humans; don't deploy it
Predict first

Production runs ghcr.io/acme/api:1.4. A teammate pushes a broken build as 1.4 by mistake. A server restarts and pulls. What runs?

Commit to an answer before you read on.
CheckpointPush, re-tag, and pin

Start the local registry, push the notes API as 1.0.0, and note the digest. Change a response in server.ts, rebuild, and push it as 1.0.0 again. Pull localhost:5050/notes-api:1.0.0 and check which version you get. Then pull the first version back using its digest.

STUCK? ▸

connection refused on push: the local registry container isn't running, or it's on a different port. docker ps should show registry with 0.0.0.0:5050->5000/tcp.

Recap

Interview questions

JuniorfreqMust-knowConcept

What's the difference between an image tag and an image digest?

MidfreqGood to knowDebugging

Your Mac-built image fails on the production server with 'exec format error'. What happened?

Report a mistakeEdit this page

Fundamentals you'll use

Used in

☰ Images02.06 · 12/44next →