I’ve been working with containerized applications for a while, and one thing that always confused me in the beginning was Docker image tagging.
Nobody explained it properly.
I just followed whatever the team did, pushed images, and hoped things worked.
But over time.. especially when handling multi-tenant apps, shared registries, and too many deployments. I started to see how bad tagging decisions can break everything.
So here’s my experience and what I learned the hard way.
The moment I realized “latest” is a trap
When I first started using Docker, I thought:
“Ah, just push it with latest. Simple.”
Until one day, a tenant’s production environment suddenly rolled back to a previous version.
Why?
Because someone else on the team pushed a new image with the same latest tag. The registry replaced it silently. The cluster pulled it automatically. Chaos.
That’s the moment I understood:
- latest is not a version
- latest is not safe
- latest will eventually hurt you
But I didn’t stop there. I kept experimenting with different tagging styles.
The Semantic Versioning Phase
After the latest catastrophe, we switched to semantic versioning:
v1.2.3
v1.2.4
v1.3.0
And honestly... it felt clean.
Every release had a meaning.
We knew which version fixed what.
Rollback was easy — just redeploy the previous version.
But then reality hit:
- My team was not disciplined enough.
- People forgot to update the version.
- Someone tagged two different builds with the same version.
- And sometimes we just wanted to test something quickly, but bumping semantic version felt “too heavy”.
Semantic versioning worked well for production, but not so much for day-to-day development.
Branch-Based Tagging: Easy but Dangerous
Then we tried a simpler approach:
dev
staging
production
It felt nice.
Our CI/CD pipeline was clean.
Each environment had its own tag.
But later, when our application grew into a multi-tenant product, things got messy.
Imagine this scenario:
- Tenant A and Tenant B both use development tag
- You push a new feature for Tenant A
- Cluster for Tenant B also pulls the new development image (because it’s the same tag)
- Tenant B breaks
This happened to us more times than I’m proud of.
And again, older images became “untagged” when new ones reused the same tag.
No trace.
git-revert rollback.
I knew we needed something safer.
Finally: Branch + Commit Hash
This was the game changer.
Instead of just development, we started tagging like this:
development-a1b2c3d
development-f39e2a1
production-c41e162
Just 7 characters of the commit hash, unique, simple, and enough to avoid collisions.
Suddenly:
- Every image had a unique identity
- Rollbacks became extremely easy
- Multi-tenant setup was safe
- Nothing accidentally overwrote something else
The only downside?!
Registry will populate a lot of different tags
Our container registry filled up fast.
We had to implement retention policies.
But honestly, compared to breaking tenants?
I will happily pay for storage.
This tagging style solved everything semantic versioning and branch tagging couldn’t.
So what do I actually recommend?
From my own experience,
For development:
Use branch + commit hash
Example:
development-7f92c3a
It keeps things safe, traceable, and rollback-friendly. cover frequently changes on development.
Tips
keeplatestimage (e.g.development-latest). you may push alternative tag on single commit.
dont worry it doesn't take your storage, just tag. it will help you to trace which latest tag quickly
For production:
Use semantic versioning
Example:
v1.4.0
Clean, stable, professional.
For personal or hobby projects:
Just use latest or dev.
No need to over-engineer.
Top comments (0)