Containers vs VMs for Microservices: A Lead's Guide
For Engineering leads evaluating containers vs VMs for microservices · Based on Piyush Docker-to-Kubernetes Container Fundamentals Framework
// TL;DR
If your team is deciding how to run many microservices on limited infrastructure, this framework gives you the decision model. Containers share the host OS kernel and consume only the resources each service needs, so 20 microservices can run densely as 20 containers rather than 20 resource-wasting VMs. Beyond density, the Build-Ship-Run workflow gives you auditable, versioned promotion across Dev, Test, and Prod through a registry, killing environment-drift failures. Use this to justify a container strategy, size infrastructure realistically, and set up a safe promotion pipeline your team can standardise on.
Should we run microservices in containers or VMs?
For most microservice workloads, containers. The deciding factor is the Shared Infrastructure, Isolated Tenants principle: containers share the host OS kernel — like flats sharing one building and land — while VMs each run a full isolated OS, like independent houses on separate plots.
Concretely, running 20 microservices as 20 VMs means 20 full OS installations, each reserving CPU and memory whether it's used or not. That's massive resource wastage. Running them as 20 containers means each shares the host kernel and consumes only the CPU, memory, and storage it actually needs. For density and cost efficiency on a single physical server, containers are the correct architectural choice.
How does the Build-Ship-Run workflow reduce operational risk?
Beyond density, containers give your team a standardised, auditable deployment pipeline. The Build-Ship-Run framework has exactly three stages that no engineer should skip:
1. BUILD — each service has one Dockerfile that packages code, dependencies, and a minimal OS into a versioned image.
2. SHIP — the image goes to a registry (Docker Hub, Artifact Registry, JFrog Artifactory, or Nexus), which acts as version control for binaries — trackable, auditable, controlled access.
3. RUN — every environment pulls and runs the identical image.
This directly eliminates environment misconfiguration — the most common cause of build-promotion failures, where a service works in Test but fails in Prod because a dependency was missing. Standardising on this workflow removes an entire category of production incidents.
What does a container strategy cost us in infrastructure?
Because containers only consume the resources they need — not a fixed VM allocation — you can size hardware to real usage rather than to the sum of worst-case reservations. Higher density per server means fewer machines for the same workload. Factor in the operational win too: one immutable image promoted through a registry is cheaper to support than per-environment shell-script deployments that drift and generate incident load.
What should we mandate to avoid rollout failures?
Set team-wide rules straight from the framework's pitfalls:
- Never push an image directly to Prod. Always go through the registry so promotion is versioned and auditable.
- One Dockerfile per service, named exactly `Dockerfile`, as the single source of truth.
- Build once, promote the same image through Dev → Test → Prod. Never rebuild per environment.
- Distinguish images from containers in tooling and runbooks — the image ships; `docker run` creates the live instance.
- Keep images lightweight — bare-minimum OS plus required libraries only, not a full OS.
How do we validate the architecture is sound?
Have the team map every deployment against the Docker component model: Docker Client → Daemon → local image storage → registry → Daemon on target → Container Runtime → running container. When each of build, push, pull, and run traces to a specific component, incident triage becomes fast — a failure is always attributable to one component in the pipeline. This traceability is a governance win as much as a technical one.
Next step: Pilot the container strategy on a small cluster of your microservices. Containerise three services, push them to a shared registry, and run them densely on one host. Compare resource utilisation and promotion reliability against your current VM or script-based setup — the density and incident-reduction data will make the org-wide case for you.
// FREQUENTLY ASKED QUESTIONS
Are there cases where VMs still beat containers for microservices?
Yes — when services require strong kernel-level isolation, run different OS kernels, or have strict compliance boundaries that demand separate operating systems, VMs remain appropriate. Containers share the host kernel, so workloads needing full OS isolation are the exception. For typical microservices sharing a common runtime, containers win on density and efficiency, but the isolation trade-off should be evaluated per workload.
How does a registry improve governance and auditability?
A registry is version control for your binary images, giving versioned, trackable storage with controlled access — the same role GitHub plays for source code. Every promotion from Dev to Prod pulls a specific, auditable image version rather than an untracked artifact. Mandating registry-based promotion means you can trace exactly what ran where, which is essential for compliance and incident review.
Is Docker the only option, or should we evaluate alternatives?
Docker is one implementation of a container engine and the most common starting point, but alternatives like Podman implement the same containerisation concept. The Build-Ship-Run methodology is engine-agnostic — build an image, ship via a registry, run as a container transfers across tools. Standardise on one engine for consistency, but the strategic decision is containers versus VMs, not Docker versus everything else.