How to Ship Identical Images from Dev to Production
For DevOps and platform engineers · Based on KodeKloud Docker Containerisation Blueprint
// TL;DR
For DevOps and platform engineers, the Docker Containerisation Blueprint turns runbooks into version-controlled Dockerfiles and Compose files that developers and operations jointly own. The core discipline: pin every production image to a specific tag so upstream maintainers never silently upgrade your stack, authenticate in CI to dodge Docker Hub rate limits, and deploy the exact image a developer built and verified — unchanged. You use virtualisation to provision Docker hosts elastically and Docker to scale applications rapidly on them. The result is guaranteed identical behaviour from a developer's laptop through CI to production.
How does Docker eliminate 'works on my machine' in the deploy pipeline?
By making the image the single source of truth. Once a developer builds and verifies an image, operations deploy that same image unchanged — no rebuild, no environment drift. The Dockerfile transforms the ops runbook into a reproducible, version-controlled artefact jointly owned by developers and operations. This shared artefact is the foundation of DevOps culture: the thing a developer verifies is literally the thing that runs in production, guaranteeing identical behaviour.
For multi-service stacks, Docker Compose defines every service, its port mappings, volumes, and inter-service dependencies in one file, so the entire environment is reproducible and reviewable in version control.
How do I keep production deployments stable?
The non-negotiable rule: pin tags in production. The `latest` tag is a moving target — upstream maintainers can push a new version and your container silently upgrades on the next deploy, sometimes with breaking changes. Pin to a specific tag like `redis:7-alpine` so you decide when to bump versions, not the maintainers.
Other production disciplines:
- Design containers around a single foreground process. A container lives only as long as its process; use process managers or orchestration for restarts rather than cramming multiple daemons into one container.
- Map volumes for all stateful services. Data written inside a container is destroyed on removal, so databases and persistent stores need `-v` or the explicit `--mount type=bind,...` form.
- Plan port assignments across services. Each host port binds to only one container; a collision fails the run.
How do I stop CI pipelines from hitting Docker Hub rate limits?
Docker Hub caps unauthenticated pulls at 100 per 6 hours per IP, and 200 on the free Personal tier after `docker login`. CI runners often share an IP, so pipelines hit 'too many requests' quickly. Always authenticate with `docker login` before pulling, or mirror images to a private registry — GitHub Container Registry, AWS ECR, Google Artifact Registry, or Azure Container Registry. Authenticating and pinning tags together make CI pulls both fast and deterministic.
How do containers and VMs fit together at scale?
They're complementary, not competing. Use virtualisation to provision and decommission Docker hosts elastically — spin up VMs to add capacity, tear them down when load drops. Then use Docker to provision and scale applications rapidly on those hosts. This layering gives you elastic infrastructure underneath and fast application scaling on top. Remember that Linux containers on Windows hosts run inside a Linux VM, so the kernel-sharing constraint always applies.
How do I run controlled migrations and side-by-side versions?
For a database migration, pull both the current stable image and the target upgrade image, then run each with `docker run -d` mapped to different host ports (e.g. 3306 and 3307). Each container has its own isolated file system and volume mapping, so there are no conflicts. Use `docker inspect` to confirm each container's network settings and mount points before cutting over.
Next step
Audit your production images for any `latest` tags and pin them to explicit versions. Then add `docker login` to your CI pipeline before every pull, and move your stack definition into a version-controlled Dockerfile and Docker Compose file so developers and operations deploy the identical artefact.
// FREQUENTLY ASKED QUESTIONS
Why is pinning image tags critical in production but not in dev experiments?
In production, the latest tag can silently upgrade to a new upstream version with breaking changes on your next deploy, causing unpredictable outages. Pinning to a specific tag like postgres:16 means you control upgrades. In quick experiments the risk is trivial and speed matters more, so latest is acceptable there but never in production or CI.
How do I avoid Docker Hub rate limits in CI?
Run docker login before pulling so you get the authenticated 200-pull tier, or mirror images to a private registry like AWS ECR, GHCR, Google Artifact Registry, or Azure Container Registry. Since CI runners often share one IP, unauthenticated pulls hit the 100-per-6-hours cap fast, breaking builds with 'too many requests' errors.
Should I replace my VMs with containers?
No — use both. Provision and decommission Docker hosts elastically with virtualisation, then run and scale applications rapidly with Docker on those hosts. Containers are lightweight and fast but share the host kernel; VMs provide the elastic, isolated infrastructure layer beneath them. It's containers and VMs, not containers or VMs.