Docker Hub and Private Registries: Pushing and Pulling Images

Intermediate
11 min

Docker Hub and Private Registries: Pushing and Pulling Images

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.

Anatomy of an Image Name

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.

Authenticating

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:

bash
echo "$DOCKER_TOKEN" | docker login -u yourname --password-stdin echo "$GHCR_TOKEN" | docker login ghcr.io -u yourname --password-stdin docker logout ghcr.io

On 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.

Pushing and Pulling

bash
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: 1570

Pushes 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.

bash
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 image

A 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.

Running a Private Registry

For an internal network or a lab, the open-source Distribution registry runs as a container:

bash
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.

Moving Images Without a Registry

When a target machine has no registry access, export the image as a tar archive:

bash
docker save -o my-app.tar yourname/my-app:1.4.2 docker load -i my-app.tar # on the other machine

save/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.

Common Mistakes

  • Pushing before tagging with the registry path; docker push my-app tries docker.io/library/my-app and is denied.
  • Logging in with a password instead of a token, or passing the token as a command-line argument.
  • Deploying latest and being surprised when a rebuild silently changes what runs; pin by digest or immutable version tags.
Quick Quiz
Question 1 of 3

What does `docker tag my-app:1.0 ghcr.io/team/my-app:1.0` do?

Key Takeaways

  • Image names are registry/namespace/repository:tag; missing parts default to Docker Hub, library and latest.
  • Log in with access tokens passed on stdin; credentials are stored per registry.
  • docker push uploads only missing layers; record the digest it prints and pull by digest in production.
  • A private registry is one 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.

Docker Hub and Private Registries: Pushing and Pulling Images - Docker | CodeYourCraft | CodeYourCraft