Skip to main content

Docker vs Virtual Machines

Containers and virtual machines both let you run isolated environments, but they do it differently. This page explains "docker vs virtual machine explained" in simple terms, shows a quick hands-on example, and helps you choose the right tool for development and deployment.

Concept explained

  • Virtual machine (VM): a full guest OS running on a hypervisor. Each VM includes its own kernel, device drivers, and userspace. Good isolation, higher overhead.
  • Container (Docker): shares the host kernel; only bundles application code and required libraries. Lightweight, fast startup, smaller images.
  • Key differences:
    • Isolation: VMs isolate at hardware/firmware level; containers isolate at OS/process level.
    • Overhead: VMs use more CPU, RAM, and disk (guest OS). Containers are much lighter.
    • Portability: Container images are portable across hosts with the same OS kernel family.
    • Use cases: containers for app packaging and microservices; VMs for running different OS kernels or full-system isolation.
tip

Think of a VM as a full computer inside your machine. A container is a process (or group of processes) sandboxed to behave like a tiny machine.

Step-by-step example

Two minimal examples: run an HTTP server in Docker and in a local VM (Vagrant). Both expose port 8080 on your host.

Docker (fast, no guest OS):

# pull and run nginx in a container
docker run --rm --name nginx-test -d -p 8080:80 nginx:latest

# test
curl http://localhost:8080

VM with Vagrant + VirtualBox (full guest OS):

  1. Create a directory and a Vagrantfile:
# Vagrantfile
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/bionic64"
config.vm.network "forwarded_port", guest: 80, host: 8080
config.vm.provision "shell", inline: <<-SHELL
apt-get update
apt-get install -y nginx
systemctl enable nginx
systemctl start nginx
SHELL
end
  1. Start the VM:
vagrant up
# then open http://localhost:8080
note

The Docker example starts in seconds and uses little disk. The Vagrant VM takes longer and includes a full Ubuntu OS.

Variations & gotchas

  • Lightweight VMs and microVMs (Firecracker, Kata Containers) blur the line: they provide VM-like isolation with lower overhead.
  • Kernel features: containers need a compatible host kernel. You can't run a Windows container on a Linux host (and vice versa).
  • Filesystem performance: bind-mounted files can be slower in some VM setups (e.g., VirtualBox sync).
  • Networking: container networking is usually NAT/bridge by default; VMs often have separate virtual NICs – behavior and firewalls differ.
  • Privileged containers or sysctl tweaks can reduce isolation and increase risk.
caution

Don't assume containers are as isolated as VMs. Certain kernel exploits or misconfigurations can let containers affect the host.

Common mistakes

  • Expecting containers to provide the same security boundary as VMs.
  • Packaging OS-level services (init systems) into containers when a simple process would do.
  • Running stateful databases in ephemeral containers without persistent volumes or backups.
  • Forgetting to set resource limits on containers, causing noisy-neighbor issues.
  • Assuming identical behavior across different hosts without testing (kernel, cgroups, storage drivers vary).

Best practices

  • Use containers for application-level isolation and fast CI/CD iterations.
  • Use VMs when you need a different kernel, full OS features, or stronger isolation.
  • For containers:
    • Build small images and pin image versions.
    • Use volumes for stateful data and regular backups.
    • Limit CPU and memory with --cpus and --memory (or compose limits).
    • Add health checks and logging.
  • For VMs:
    • Keep images minimal and use cloud-init or provisioning scripts.
    • Snapshot or backup critical VMs before major changes.

When to use / when not to use

When to use containers:

  • Deploying web apps, APIs, and microservices.
  • Fast local dev, reproducible CI builds, and horizontal scaling.
  • You want low resource overhead and quick restarts.

When to use VMs:

  • Running a different OS kernel than the host.
  • Need strong isolation for multi-tenant or untrusted workloads.
  • You require full system-level testing (boot process, kernel modules).

When not to use containers:

  • If you must run an OS that doesn’t match the host kernel.
  • For legacy apps that require a full init system and lots of host emulation.

Key takeaways

  • Containers share the host kernel; VMs run a full guest OS (different isolation and overhead).
  • Containers are lightweight and fast; VMs provide stronger, hardware-like isolation.
  • Choose containers for app portability and CI/CD; choose VMs when you need full OS isolation or a different kernel.
  • Always use volumes for persistent state and set resource limits to avoid noisy neighbors.
  • Test on environments similar to production – kernel and storage differences matter.