An image is only useful once it can leave your laptop. Registries are the distribution layer of Docker: you push a built image, and servers, teammates and CI pipelines pull it. In this lesson you will learn how image names encode their registry, how to authenticate safely, how to push to Docker Hub and GitHub Container Registry, how to run your own registry, and why pulling by digest matters in production.
A full image reference has four parts: registry/namespace/repository:tag. Missing parts are filled with defaults.
| Reference | Expands to |
|-----------|-----------|
| nginx | docker.io/library/nginx:latest |
| yourname/my-app:1.4.2 | docker.io/yourname/my-app:1.4.2 |
| ghcr.io/yourname/my-app:1.4.2 | GitHub Container Registry, no default |
| localhost:5000/my-app | A registry on port 5000 of this machine |
The library/ namespace holds Docker Official Images. docker tag does not copy anything; it adds a second name that points to the same image ID, and the registry part of that name decides where docker push sends it.
docker login stores a credential for a registry in ~/.docker/config.json (or a credential helper such as the OS keychain when Docker Desktop is installed). Always use an access token rather than your account password, and pass it on stdin so it does not land in shell history:
echo "$DOCKER_TOKEN" | docker login -u yourname --password-stdin
echo "$GHCR_TOKEN" | docker login ghcr.io -u yourname --password-stdin
docker logout ghcr.ioOn Docker Hub, create the token under Account Settings > Personal access tokens with read/write scope. On GitHub, a classic personal access token with write:packages works locally, while in GitHub Actions the built-in GITHUB_TOKEN is enough.
docker build -t yourname/my-app:1.4.2 .
docker push yourname/my-app:1.4.2
# The push refers to repository [docker.io/yourname/my-app]
# 5f70bf18a086: Pushed
# 1.4.2: digest: sha256:3b1c5e... size: 1570Pushes are layer-based: only layers the registry does not already have are uploaded, so pushing a new tag of an image that shares its base layers with an earlier tag transfers little data. Pulling behaves the same way.
docker pull yourname/my-app:1.4.2
docker pull yourname/my-app@sha256:3b1c5e... # by digest: immutable
docker image ls --digests yourname/my-app
docker manifest inspect yourname/my-app:1.4.2 # platforms in a multi-arch imageA tag such as latest or even 1.4.2 can be re-pointed to a different image tomorrow. A digest cannot, which is why deployment manifests and CI should record the digest printed by docker push. Docker Hub also applies pull-rate limits to anonymous and free accounts, so servers that pull frequently should log in.
For an internal network or a lab, the open-source Distribution registry runs as a container:
docker run -d -p 5000:5000 --name registry \
-v registry-data:/var/lib/registry \
--restart unless-stopped registry:2
docker tag my-app:1.4.2 localhost:5000/my-app:1.4.2
docker push localhost:5000/my-app:1.4.2
curl http://localhost:5000/v2/_catalog # {"repositories":["my-app"]}Docker treats localhost as trusted, but a registry on another host must serve TLS; otherwise add it to insecure-registries in daemon.json, which is acceptable only on isolated networks. For a team, a managed registry is usually the better choice: GitHub Container Registry, GitLab Container Registry, Amazon ECR, Google Artifact Registry and Azure Container Registry all speak the same API and integrate with their platform's permissions and vulnerability scanning.
When a target machine has no registry access, export the image as a tar archive:
docker save -o my-app.tar yourname/my-app:1.4.2
docker load -i my-app.tar # on the other machinesave/load preserve tags and layers; export/import (a different pair) flatten a container's filesystem and lose its history, so use them only when that is what you want.
docker push my-app tries docker.io/library/my-app and is denied.latest and being surprised when a rebuild silently changes what runs; pin by digest or immutable version tags.What does `docker tag my-app:1.0 ghcr.io/team/my-app:1.0` do?
registry/namespace/repository:tag; missing parts default to Docker Hub, library and latest.docker push uploads only missing layers; record the digest it prints and pull by digest in production.docker run registry:2 away, but remote registries need TLS or an insecure-registries entry.docker save and docker load move images as tar files when no registry is reachable.Next lesson: Volumes and Persistent Data — keep data alive beyond the life of a container.