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.31-alpine

# Add a local alias tag
docker tag nginx:1.31-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
OptionBenefitCost
SHA digest tag (sha256:...)Exact and unambiguous — guarantees the same content, 100% reproducibleNot human-readable, hard to communicate or remember as a version
Semver tag (e.g. v1.2.3)Easy to read, clearly communicates version and intentMutable — anyone can overwrite the same tag with new content if nothing prevents it
  • Pushing and deploying only the latest tag, leaving no pinned version to roll back to when something breaks.
  • Overwriting an existing semver tag with new content instead of always creating a new tag.
  • Not pinning by SHA digest alongside a semver tag, so there is no way to verify the tag actually deployed is still the same content or has been swapped out.

💡 Real-world example

Many teams using GitOps (Argo CD, Flux) pin deployment manifests to a SHA digest rather than a semver tag, to guarantee the cluster always runs the same content — even if the original tag gets overwritten later.

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?