2.3Versioning and publishing your images
The essentials in 30 seconds
- Tag at build time, account included: account/image:version is the full address — no namespace, no push.
- Immutable per release: one frozen, tested tag per version (1.2.0), never rewritten — moving aliases are only for comfort.
- Never push a secret: everything entering the image leaves on push — a public push is irreversible.
Running mini-case — Idir and the prod pulling latest
Idir's lean image (2.2) runs locally, but the team shares it by hand, as latest, with no version: prod already drifted twice. Idir tags at build time with the account (moncompte/mon-app:1.2.0 plus the 1.2 alias), logs in to the registry, pushes both tags, then pulls 1.2.0 on another machine: build here, run elsewhere, frozen version. He checks the repository visibility before pushing. In 2.4, he will make this image configurable; in 2.5, he will check it holds no secret before any new push.
Following the 2.2 thread: Idir's lean image travels with a version number — and continues in 2.4 and 2.5.
1. Tagging like a pro
Tag at build time (-t moncompte/mon-app:1.2.0) and add useful aliases (1.2, latest in dev). Rule: one immutable tag per release (1.2.0), never rewritten; moving aliases are only for comfort. Prod always pulls the frozen, tested tag, never latest. Idir freezes 1.2.0 and lets 1.2 follow the fixes.
2. Publishing: login, push, pull
docker login authenticates, docker push moncompte/mon-app:1.2.0 publishes, any machine fetches it with pull. The account/image:tag name is the full address: without an account (namespace), the push is refused. Idir pushes his two tags then, on another machine (or after a local rmi), pulls 1.2.0 and runs it: proof the image travels.
3. Public or private?
By default on the Hub, a repository is public: perfect for open source, forbidden for internal code. For internal code, a private repository (paid Hub, GHCR, company registry). And never a secret in the image: a COPYed or ENV secret stays in the layers and leaves on push — a public push is irreversible. Idir controls visibility before every push and keeps secrets out of the image (2.5).
🔎 Concrete example — 🔎 Concrete example — Publishing 1.2.0
Idir's image, from the local machine to the registry:
docker build -t moncompte/mon-app:1.2.0 -t moncompte/mon-app:1.2 .
docker login
docker push moncompte/mon-app:1.2.0
docker push moncompte/mon-app:1.2
# on another machine:
docker pull moncompte/mon-app:1.2.0
Reading: 1.2.0 is frozen and tested, 1.2 follows the fixes, latest stays a dev comfort.
Lesson: prod pulls 1.2.0, never latest — reproducible and traceable.
Section vocabulary
- Namespace
- The account or organization before the image (moncompte/mon-app): the full address required to push.
Simple example: Idir prefixes with his account from the build on.
What it is not: the version tag alone.
Reflex: no namespace = no push.
- Immutable tag
- A frozen, never-rewritten version (1.2.0): what prod pulls to stay reproducible.
Simple example: Idir freezes the tested 1.2.0 for prod.
What it is not: a moving alias like latest.
Reflex: one release = one frozen tag.
- Alias
- A moving comfort tag (1.2, latest) following the fixes; handy in dev, forbidden as a prod reference.
Simple example: Idir lets 1.2 follow the patches, latest for trials.
What it is not: the reference deployed in production.
Reflex: comfort in dev, never in prod.
- push / pull
- Publishing to the registry (push) and fetching from the registry (pull): the sharing trio with login.
Simple example: Idir pushes 1.2.0 and pulls it on another machine.
What it is not: a local build never shared.
Reflex: login, push, pull: share versioned.
In practice — In practice — Publishing your app
- Rebuild your app with two tags (frozen 1.0.0 and 1.0 alias).
- Log in to the registry (docker login).
- Push both tags (docker push), visibility checked.
- On another machine (or after rmi), pull 1.0.0 and run it.
Script ready to say — enforcing the frozen tag (30 s)
"We no longer deploy latest: every release gets its frozen, tested tag, never rewritten — 1.2.0 runs in prod, we know exactly what. Aliases like 1.2 or latest stay for comfort in dev. Build here, pull elsewhere, same version everywhere."
Say while showing Idir's 1.2.0 pull on a second machine.
Copy-paste template — tagging and publishing
docker build -t moncompte/mon-app:1.2.0 -t moncompte/mon-app:1.2 .
docker login
docker push moncompte/mon-app:1.2.0
docker push moncompte/mon-app:1.2
docker pull moncompte/mon-app:1.2.0
Push refused
"Denied" push: Idir checks the login, the account/image namespace and his rights on the repository.
Internal code in public
Internal code pushed to a public repository: Idir switches back to private (or a company registry) before pushing.
Rewritten tag
A 1.2.0 pushed again differently after deployment: Idir freezes 1.2.1 instead of rewriting history.
Push on every commit
Every trial pushed pollutes the registry: Idir pushes only tested versions, the CI will sort the rest (level 4).
✓ Best practices
- One immutable tag per release, never rewritten.
- Prefix with the account from the build on.
- Check visibility (public/private) before pushing.
✗ Common pitfalls
- Rewriting an already deployed tag (prod drift).
- Pushing internal code in public.
- Including secrets in the pushed image.
Key takeaways
- We tag at build time, account included (full address).
- Immutable per release (1.2.0), aliases (1.2, latest) for comfort.
- login → push → pull: the versioned sharing trio.
- Never a secret in a pushed image (public push is irreversible).
48h challenge — apply tomorrow
Publish your lean image (2.2): build with two tags (1.0.0 + 1.0), login + push with visibility checked, then pull + run 1.0.0 elsewhere.
- I can tag at build time (immutable + alias, account included).
- I can publish (login, push) and fetch (pull) elsewhere.
- I control public/private and exclude secrets before every push.
Frequently asked questions
Why is my push refused (denied)?
Often a name without namespace, or no login, or no rights on the repository. Check docker login, the account/image:tag name and your write rights on the repository before retrying.
Should I push on every commit?
No: push tested versions (frozen tags), not every trial. The CI (level 4) will automate this sorting: it builds on every commit but publishes only validated versions.
Which tag policy for production?