Before containers, "it works on my machine" was a real, constant problem. An application depends on a specific language runtime version, a set of system libraries, particular environment variables, maybe a specific OS-level package — and getting all of that to match exactly between a developer's laptop, a CI runner, and a production server was mostly manual, error-prone work (README files full of setup steps, configuration management tools, or just tribal knowledge).
Docker's answer is to package the application and its entire runtime environment into a single artifact — an image — that runs identically anywhere Docker itself runs. The host machine's OS, installed packages, and configuration stop mattering, because the container brings its own.
This is easy to confuse with virtual machines, which solve a similar-sounding problem very differently:
| Virtual Machine | Container | |
|---|---|---|
| What it virtualizes | Entire hardware, including its own kernel | Just the process — shares the host's kernel |
| Startup time | Seconds to minutes (boots a full OS) | Milliseconds to a few seconds |
| Size | Gigabytes (a full OS image) | Megabytes to a few hundred MB, typically |
| Isolation strength | Very strong — separate kernel entirely | Weaker — isolated via kernel namespaces/cgroups, not a separate kernel |
| Density per host | Low — each VM carries full OS overhead | High — many containers can share one kernel cheaply |
A container is not a "lightweight VM" — it's a normal process on the host, made to look isolated using two Linux kernel features: namespaces (which give a process its own view of the process tree, network interfaces, filesystem mounts, and hostname, so it can't see or interfere with anything outside it) and cgroups (which cap and account for the CPU, memory, and I/O that process is allowed to use). There is no hypervisor and no second kernel — which is exactly why containers start so much faster and cost so much less overhead than a VM, but also why they isolate less strongly. A kernel-level exploit that escapes a container's namespace can reach the host in a way that a VM's separate kernel would generally block.