Skip to content

Tag, Push & Pull

Every Docker image has a fully-qualified name made of four parts:

registry/namespace/repository:tag
PartExampleDefault
registrydocker.iodocker.io
namespaceacmelibrary (for official images)
repositoryapi-server
tag2.1.0latest

So nginx is shorthand for docker.io/library/nginx:latest, and acme/api-server:2.1.0 means docker.io/acme/api-server:2.1.0.

For GHCR (GitHub Container Registry) the pattern is:

ghcr.io/<github-owner>/<repository>:<tag>

docker tag does not copy the image. It adds a new pointer to the same set of layers:

Terminal window
# Build the image locally
docker build -t api-server:2.1.0 .
# Add a registry-qualified name ready to push
docker tag api-server:2.1.0 acme/api-server:2.1.0
# Also tag as latest (optional)
docker tag api-server:2.1.0 acme/api-server:latest

Both acme/api-server:2.1.0 and acme/api-server:latest now refer to the exact same image ID — no extra storage is used.

Push requires authentication (see the Docker Hub lesson). After docker login:

Terminal window
docker push acme/api-server:2.1.0
docker push acme/api-server:latest

Each layer is uploaded once and deduplicated on the registry. If another image already has the same layer, Docker skips it.

Expected output:

The push refers to repository [docker.io/acme/api-server]
abc123def456: Pushed
789ghijkl012: Layer already exists
2.1.0: digest: sha256:deadbeef... size: 1234
Terminal window
# Pull a specific version
docker pull acme/api-server:2.1.0
# Pull from GHCR
docker pull ghcr.io/acme/api-server:2.1.0

If the image already exists locally, Docker checks whether the remote digest has changed and only downloads updated layers.

A good tagging strategy uses multiple tags for the same image:

Terminal window
docker tag api-server:2.1.0 acme/api-server:2.1.0 # exact version
docker tag api-server:2.1.0 acme/api-server:2.1 # minor stream
docker tag api-server:2.1.0 acme/api-server:2 # major stream
docker tag api-server:2.1.0 acme/api-server:latest # latest (optional)

Consumers can pin to 2.1.0 for stability or follow 2 for non-breaking updates. latest is just a convenience alias — it has no automatic semantics.

Once a tag has been pushed and other systems have started pulling it, never overwrite it. Re-pushing 2.1.0 with different content breaks reproducibility — other teams’ deployments may pull a different image than yours even though the tag looks the same.

Instead, always create a new tag (2.1.1, 2.2.0) for changed content.

Hands-on: pull and run a versioned public image

Section titled “Hands-on: pull and run a versioned public image”

You cannot push to Docker Hub from Play with Docker (no credentials), but you can practice docker pull, docker tag, and docker run on a public image.

# Pull a specific nginx version
docker pull nginx:1.27-alpine

# Add a local alias tag
docker tag nginx:1.27-alpine my-nginx:stable

# Check both tags point to the same image ID
docker images nginx
docker images my-nginx

# Run it to verify it works
docker run --rm -d --name test-nginx -p 8080:80 my-nginx:stable
curl http://localhost:8080
docker stop test-nginx
What does `docker tag acme/app:1.0 acme/app:latest` actually do to storage?
What is the default registry when no registry address is given in an image name?
Why should you treat pushed version tags as immutable?
Which command uploads a locally built image to a registry?