EthicsComputerCourses
Join training +91 90532 10052

Registries: Docker Hub & GitHub Container Registry

Docker & DevOps · Lesson 27 of 39 Advanced 7 min read Updated

An image on your laptop is useful only to you. To run it on a server, share it with your team or deploy it from a CI pipeline, you put it in a registry: a server that stores images and lets authorised people push and pull them. You have been pulling from Docker Hub since your first docker run. Now you will push your own images.

This lesson covers the two registries most developers start with, Docker Hub and GitHub Container Registry (GHCR), plus a tagging plan that keeps deployments safe.

How registries are organised

A registry holds repositories, and each repository holds many tags of one image. The image's full name tells Docker which registry to talk to:

Image nameRegistryRepositoryTag
mysql:8.4Docker Hublibrary/mysql8.4
priyasharma/medical-shop:2.1.0Docker Hubpriyasharma/medical-shop2.1.0
ghcr.io/priya-sharma/medical-shop:2.1.0GHCRpriya-sharma/medical-shop2.1.0

To push an image, its name must start with a registry and namespace you are allowed to write to. You do not rebuild the image for this; you add another tag (another name) to the same image with docker tag.

Pushing to Docker Hub

Create a free account at hub.docker.com. Then, instead of your account password, create a personal access token in your account settings. A token can be limited (for example read and write, but not delete), shown once, and revoked at any time without changing your password.

Terminal
echo "$DOCKERHUB_TOKEN" | docker login -u priyasharma --password-stdin
docker tag medical-shop:2.1.0 priyasharma/medical-shop:2.1.0
docker push priyasharma/medical-shop:2.1.0
Output
Login Succeeded
The push refers to repository [docker.io/priyasharma/medical-shop]
6a8d3f1c9e02: Pushed
b4e2c7a1d953: Pushed
2d35ebdb57d9: Mounted from library/node
2.1.0: digest: sha256:e3b7c9a1f0d24c6b8a5e7f3d1c9b0a2e4f6d8c1a3b5e7f9d0c2a4b6e8f1a3c5d size: 856

--password-stdin reads the token from standard input, so it does not appear in your shell history or in the list of running processes. Look at the push output: only your own layers were uploaded. The Node.js base layer says Mounted from library/node, because Docker Hub already had it and simply linked it. Pushing a new version of your app later uploads only the layers that changed.

Running docker login with no options opens a web-based login instead: it shows a one-time code and asks you to confirm it in the browser. That is convenient on your laptop; tokens with --password-stdin are the way to log in from scripts and servers.

On the server, anyone you allow can now run:

Terminal
docker pull priyasharma/medical-shop:2.1.0

New Docker Hub repositories are public unless you choose private. A public image can be pulled by anyone in the world, so never put secrets, private code you cannot share or customer data in it.

Pull rate limits

Docker Hub limits how many images can be pulled. Today, anonymous users get 100 pulls per 6 hours per IPv4 address (or per IPv6 /64 subnet), and logged-in users on a free personal account get 200 per 6 hours. A busy CI system or a classroom of students behind one college IP address can hit the anonymous limit quickly, and pulls then fail with a "toomanyrequests" error. Logging in, even with a free account, raises the limit. Docker can change these numbers, so check the Docker Hub documentation for current values.

Pushing to GitHub Container Registry

GHCR stores images next to your code on GitHub, at ghcr.io/OWNER/IMAGE. Its big advantage is in CI: a GitHub Actions workflow can push to it with its built-in token, with no extra account. From your own computer, log in with a GitHub personal access token (classic) that has the write:packages scope:

Terminal
echo "$GHCR_TOKEN" | docker login ghcr.io -u priya-sharma --password-stdin
docker tag medical-shop:2.1.0 ghcr.io/priya-sharma/medical-shop:2.1.0
docker push ghcr.io/priya-sharma/medical-shop:2.1.0

Things to know about GHCR:

  • The owner and image names must be lowercase. A GitHub user Priya-Sharma must still write ghcr.io/priya-sharma/....
  • A new package is private by default. Change its visibility in the package settings on GitHub if you want a public image.
  • Add the label org.opencontainers.image.source with your repository URL in the Dockerfile, and GitHub links the package to that repository automatically:
Dockerfile
LABEL org.opencontainers.image.source=https://github.com/priya-sharma/medical-shop

A tagging plan for deployments

Tags are how you choose which version runs where. A simple, safe plan:

  • A version tag for every release, such as 2.1.0. Treat it as permanent: never push different content to an existing version tag.
  • A Git commit tag, such as sha-4f2a9c1, so you can trace any running image back to the exact code.
  • Moving tags such as 2.1, 2 or latest for convenience, but deploy servers with exact version tags (or digests), never with latest.

Pushing several tags of one image is cheap: the layers are uploaded once.

Terminal
docker tag medical-shop:2.1.0 priyasharma/medical-shop:2.1
docker tag medical-shop:2.1.0 priyasharma/medical-shop:latest
docker push --all-tags priyasharma/medical-shop

Other registries

Every cloud has its own registry (Amazon ECR, Google Artifact Registry, Azure Container Registry), and GitLab has one built in. They all work the same way: log in, tag with the registry's host name, push. You can even run a private registry yourself with the official registry image, for example docker run -d -p 5000:5000 --name registry registry:3, and push to localhost:5000/medical-shop:2.1.0.

Common mistakes

  • "denied: requested access to the resource is denied". The image name does not start with your namespace (priyasharma/...), or you are not logged in to that registry.
  • Uppercase letters in GHCR names. Repository names must be lowercase.
  • Logging in with your account password. Use an access token that you can limit and revoke.
  • Overwriting a version tag. If 2.1.0 changes after release, servers that pulled it earlier run different code. Publish 2.1.1 instead.
  • Pushing an image with secrets in it. Check .dockerignore and use build secrets before your first push.

Key points

  • A registry stores repositories of tagged images; the image name decides which registry is used.
  • docker tag gives an image a registry-qualified name; docker push uploads only layers the registry lacks.
  • Log in with access tokens and --password-stdin; Docker Hub needs a namespace like priyasharma/, GHCR uses ghcr.io/owner/.
  • Docker Hub limits pulls, and logged-in users get a higher limit than anonymous ones.
  • Release with permanent version tags plus commit tags; deploy exact versions, not latest.

Exercise

Kabir has built an image fees-api:1.4.0 on his laptop. His GitHub username is KabirMehta and his repository is KabirMehta/fees-api. Write the commands to log in to GHCR with a token stored in the variable GHCR_TOKEN, tag the image as version 1.4.0 and latest, and push both tags. Also write the Dockerfile line that links the package to his repository.

Show solution
Terminal
echo "$GHCR_TOKEN" | docker login ghcr.io -u KabirMehta --password-stdin
docker tag fees-api:1.4.0 ghcr.io/kabirmehta/fees-api:1.4.0
docker tag fees-api:1.4.0 ghcr.io/kabirmehta/fees-api:latest
docker push --all-tags ghcr.io/kabirmehta/fees-api
Dockerfile
LABEL org.opencontainers.image.source=https://github.com/KabirMehta/fees-api

The login user name may keep its capitals, but the image path must be lowercase. --all-tags pushes both tags, and the shared layers are uploaded only once.

Quick Quiz

1. Why does a push show Mounted from library/node for some layers?

2. Why use --password-stdin with docker login?

3. Which GHCR image name is valid?

4. Which tag should a production server deploy?

Learn Docker & DevOps with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call