4.1 · Registries: Hub, GHCR and private registry

Level 4 · Registries, reliability & CI

4.1Registries: Hub, GHCR and private registry

Goal: Publish and pull images from the right registry, public or private.
Estimated time: 12 min · self-paced reading · If you only have 5 min: read “The essentials in 30s” + the diagram.

The essentials in 30 seconds

  • Three registries, three uses: Docker Hub for sharing in public, GHCR tied to code for CI, private registry (internal to the company) for sensitive stuff — the full name (registry/account/image:tag) always tells where the image comes from.
  • Authenticate with a limited-scope token, never with the account password: passed via --password-stdin, it lingers neither in shell history nor in CI logs — and revokes in one click if in doubt.
  • Idir's reflex: in production, pull a full name + immutable tag (never an ambiguous shortcut), recorded in the prod compose file to know which image runs where.

Threaded mini-case — Idir publishes his startup's image without leaking the token

Idir, junior DevOps in Algiers, must publish his startup's application image so the team can deploy it: he chooses GHCR (the code is already on GitHub), creates a token limited to push/pull, logs in via --password-stdin and pushes an immutable version tag. In production, the team pulls the full name and knows exactly which image is running. First milestone of the thread: the image is in the right place, accessible to the right people, with no secret exposed.

This same case returns in the lesson, the diagram, the script and the 48h challenge — and continues in 4.2, 4.3, 4.4 and 4.5.

1. Three registries, three uses

Docker Hub: the default public library, for sharing (open source, base images). GHCR (GitHub Container Registry): the registry tied to your code, perfect for CI — each repository has its packages. Private registry (internal to the company): the vault for sensitive images, with restricted access. The full name tells it: registry/account/image:tag — Idir sees at a glance where each image comes from.

Choosing means deciding by sensitivity and use: public for open source, GHCR for your GitHub CIs, private for sensitive internal stuff. The production compose file freezes the choice by recording the full names.

2. Authenticating without exposing

docker login with a limited-scope token (push/pull), never the account password. The token goes through --password-stdin (a pipe, not a visible argument): nothing in shell history, nothing in logs. In CI, the token lives in the repository secrets, never in the script or the workflow file.

A leaked token revokes in one click: that is why accounts are never shared — a compromised shared account means redoing all access. Like Idir, create one token per use and revoke at the slightest doubt.

3. Pulling in production: freeze the source

In production, pull full name + immutable tag (a frozen version), never an ambiguous shortcut whose content can change between two deployments. The “which image runs where” traceability starts with full names recorded in the prod compose file.

Verify after pulling: the started image matches the wanted tag. A moving tag in prod is a deployment you can neither reproduce nor audit.

Common misinterpretation: “To go fast, I'll put my password in plain text with -p and push to latest: it's the same”. No. The plain-text password lingers in history and logs, and it grants total access that cannot be cleanly revoked; a moving tag in production makes the deployment irreproducible. Limited token via --password-stdin, stored as a CI secret, immutable tag in prod: this trio is what makes a clean publication.
Publishing without exposing Right registry, clean token, frozen tag Choose the registry Public Hub,CI GHCR, internal privateLimited token Push/pullrights, revocableClean login --password-stdin,CI secretImmutable tag Full namefrozen in prod
Right registry, clean token, frozen tag
Text version of the diagram (SEO / screen readers / translation): 1) Idir chooses the registry according to use: GHCR for the image tied to his startup's code. 2) He creates a limited-scope (push/pull) token instead of using the account password. 3) He logs in via --password-stdin so the secret lingers neither in history nor in logs. 4) He pushes an immutable version tag and production pulls the full name. Lesson: right registry, stdin-limited token, frozen tag — and revoke at the slightest doubt.

🔎 Concrete example — 🔎 Concrete example — Pushing to GHCR

Idir's move: traceless login, traceable image.

echo $GHCR_TOKEN | docker login ghcr.io -u moncompte --password-stdin
docker build -t ghcr.io/monequipe/app:1.2.0 .
docker push ghcr.io/monequipe/app:1.2.0
# en prod :
docker pull ghcr.io/monequipe/app:1.2.0

Reading: the token never shows, the image is traceable by its full name.

Lesson: --password-stdin keeps the secret out of history.

Section vocabulary

GHCR
GitHub's registry, tied to repositories and CI: each project publishes its images there.

Simple example: Idir pushes his application's image there since its code is already on GitHub.

What it is not: a plain file storage folder.

Reflex: code on GitHub + image to publish in CI: think GHCR.

Token
Access token with LIMITED rights, revocable, replacing the password for automated uses.

Simple example: Idir creates a push/pull token instead of pasting his password into a script.

What it is not: the account password, total and hard to revoke cleanly.

Reflex: automating access: create a limited token, never the password.

Full name
Unambiguous address of an image: registry/account/image:tag.

Simple example: Idir's team deploys a full name with a frozen version in production.

What it is not: an ambiguous shortcut whose content can change.

Reflex: production: always pull full name + immutable tag.

--password-stdin
docker login option reading the secret from a pipe instead of a visible argument.

Simple example: Idir pipes his token: nothing stays in shell history.

What it is not: the -p option exposing the secret in plain text.

Reflex: logging in to a registry: always via stdin.

In practice — In practice — Publishing to the right place

  1. Choose the registry suited to your case (Hub/GHCR/private).
  2. Create a limited-scope (push/pull) token.
  3. Log in via --password-stdin.
  4. Push an immutable tag and pull it elsewhere to verify.
Your images are in the right place, accessible to the right people.

Ready-to-say script — explaining the registry choice to your manager (30 s)

“This is Idir: I published our image on GHCR with a token limited to push and pull, logged in without leaving a trace. Production pulls the full name with the frozen version, so we know exactly which image is running. If the token leaks, I revoke it in one click.”

Say when asked why not “just push in public”: point to the sensitivity of the code.

Copy-paste template — publishing a release (GHCR)

echo $GHCR_TOKEN | docker login ghcr.io -u moncompte --password-stdin
docker build -t ghcr.io/monequipe/app:1.2.0 .
docker push ghcr.io/monequipe/app:1.2.0
docker pull ghcr.io/monequipe/app:1.2.0

Open-source image

Like Idir but to Docker Hub in public: the community can pull freely, frozen version for trust.

Sensitive internal image

Company private registry, restricted-access repository: only authorised teams pull.

Leaked token

Immediately revoke the token, create a new limited one, check access — without touching the account.

Prod that must be auditable

Full name + immutable tag recorded in the prod compose file: you know which image runs where, at any time.

Caution: Registry tokens are secrets: store them in the CI secrets (or a secret manager), never hard-coded in a script, a compose file or a workflow — and always pass them via --password-stdin. A pipeline or history showing a secret in plain text must be fixed urgently, then revoke the exposed token.

✓ Best practices

  • Limited tokens, never passwords.
  • Full names in production.
  • Revoke at the slightest doubt.

✗ Common pitfalls

  • Logging in with -p and the visible secret (history).
  • Internal images pushed in public.
  • Ambiguous shortcut tags in production.

Key takeaways

  • Hub = public, GHCR = CI, private = internal.
  • Limited tokens via --password-stdin.
  • Full name + immutable tag in production.
  • Revoke without hesitating.

48h challenge — apply tomorrow

Publish a real image: create a limited token, log in via --password-stdin, push a version tag then pull it from another machine.

Section exit checklist
  • I can choose the right registry (Hub, GHCR, private).
  • I can authenticate with a limited token via --password-stdin.
  • I can push and pull a traceable immutable tag.
Self-rating 1-5: did my last login expose a secret anywhere?could I say exactly which image runs in production?

Frequently asked questions

Why --password-stdin rather than -p?

Because -p exposes the secret in shell history and CI logs. The pipe leaves no trace: the token transits without ever appearing in plain text.

Keep the same registry everywhere?

No: public for open source, GHCR for your GitHub CIs, private for sensitive internal stuff. The production compose file freezes the choice with full names.

Check your understanding

In CI, how do you authenticate to the registry?

Additional resources (optional, to go further)

Level 4 examFresh, harder questions to test what you've learned