Why Containers Exist
The problem containers solve, told through the machine that worked and the one that did not: what a container actually is, how it differs from a virtual machine, and why the answer is a shipping metaphor.
The problem containers solve, told through the machine that worked and the one that did not: what a container actually is, how it differs from a virtual machine, and why the answer is a shipping metaphor.
Every developer has had the same conversation. The code runs perfectly on your laptop, you hand it to a colleague, and twenty minutes later a message arrives: it does not start. Nothing about the code changed on the way over. The machine did.
Containers are the answer to that conversation. By the end of this lesson you will know exactly what a container is, what it is not, and why so much of the industry rearranged itself around the idea — so that when you start typing commands in the next lesson, every one of them makes sense.
Think about everything a program needs besides its own source files. A language runtime of a particular version. Libraries, and the libraries those libraries need. System packages installed under the right names. Environment variables. File paths that exist. A database it can reach, and a user account with permission to read what it reads.
All of that together is the program's environment. Your source code is a small, carefully version-controlled part of a much larger pile of assumptions — and until containers, that pile lived nowhere except in the memory of whoever set the machine up.
So the pile gets written down as instructions, usually in a README, and instructions rot. Somebody installs a newer runtime. Somebody is on a different operating system. Somebody already had a conflicting library from another project. Every one of those is the same failure: the environment on this machine is not the environment the code was written against.
A container is a running program plus the whole environment it needs, packaged together and isolated from everything else on the machine.
That is the entire idea. Instead of shipping your code and hoping the destination has the right surroundings, you ship the code and the surroundings as one unit. The runtime, the libraries, the system packages, the file layout — all of it travels with the program.
Two Linux kernel features make it possible, and neither came from Docker. Namespaces give a process its own private view of the system: its own visible processes, its own network interfaces, its own filesystem root. Ask a process inside a container to list what is running and it sees only itself. Control groups, usually written cgroups, cap what a process may consume — this much memory, this share of the CPU, no more.
Put those together and you get a program that behaves as though it has a machine to itself, while actually sharing one kernel with every other container on the host.
This is the distinction most worth getting right early, because it explains every performance number you will meet later. The quickest way to see it is to stack both up and count the layers.
its own kernel, its own everything — gigabytes
software pretending to be hardware
shared — there is no second one to boot
Two whole layers are gone, and with them the reason starting one used to be slow. Booting a virtual machine means booting an operating system: tens of seconds, and gigabytes of disk and memory for the copy of Linux or Windows inside it. Starting a container is starting a process — milliseconds — and its size is only whatever files it actually brought with it.
| Virtual machine | Container | |
|---|---|---|
| Contains | A whole OS, kernel included | Your program and its files |
| Startup | Seconds to minutes | Milliseconds |
| Typical size | Gigabytes | Tens of megabytes |
| Isolation | Strong, hardware-level | Kernel features, weaker |
| Density per host | A handful | Hundreds |
The practical consequence: you can run a dozen containers on a laptop without noticing, which is why containers changed how people develop software and not only how they deploy it.
The name is borrowed, and the metaphor is worth taking seriously because it explains the industry-wide part of the story.
Before standardised shipping containers, cargo was loaded piece by piece. Every ship, crane, truck and warehouse had to handle barrels, crates and sacks of different shapes. Moving goods meant constant repacking, and every handover was a chance to break something.
The steel shipping container did not make cargo lighter or ships faster. It agreed on one shape for the outside of the box. Once the outside was standard, the crane no longer needed to know whether it was lifting bananas or car parts, and the same box moved from ship to train to lorry untouched.
A software container does the same trick. Whatever is inside — Python, Java, a Go binary, a database — the outside is the same shape. Anything that can run one container can run any container. That is why one tool can deploy wildly different applications, and why a platform like Kubernetes can schedule work without knowing anything about what the work is written in.
Those kernel features existed for years before anyone used them much, because using them directly was miserable. Docker's contribution was not the isolation — it was making the isolation usable, and it did that with two ideas. The first is the one beginners most often blur together, so it is worth pinning down now.
A packaged snapshot of a filesystem, with your program in it.
Built from a text file you keep in version control. It sits on disk doing nothing, and it can be copied, stored and shared. Think of it as the recipe plus every ingredient.
That image, running.
A process in isolation, started from an image. One image can start a hundred containers, and throwing one away leaves the image untouched.
The second idea is the registry: a shared place to push images and pull them from. Because the outside shape is standard, a registry works like a package manager for entire environments — one command fetches a working PostgreSQL, or your teammate's build of the app, already assembled.
Four things change in your daily work, and they are worth naming concretely.
Onboarding becomes one command. The README's twelve setup steps become a file the machine follows identically every time, so a new laptop is productive in minutes rather than an afternoon.
Projects stop fighting each other. One project needs an old Python and another needs the newest; one wants PostgreSQL 14 and another 16. In containers they never meet, and nothing is installed on your actual machine to conflict later.
What you tested is what you deploy. The image that passed your tests is the same image, byte for byte, that runs in production — so "it passed CI but broke in production" stops being an environment story and becomes a real bug you can chase.
Throwing things away is free. A broken database, a corrupted cache, a half-finished experiment: delete the container and start a clean one in seconds. That changes how willing you are to experiment at all.
You will install Docker properly in the next lesson, but it is worth seeing the shape of it now. This is a complete, real example — one command that runs a web server:
docker run -p 8080:80 nginxFour things happen, and none of them touch your machine's configuration:
Looks for nginx locally
finds no copy on this machine
Pulls the image from a registry
the packaged filesystem, downloaded once and cached
Starts a container from it
a process in isolation, up in milliseconds
Wires port 8080 to its port 80
visit localhost:8080 and a real web server answers
Nothing was installed. No configuration file was edited, no system package added. Stop the container and your machine is exactly as it was — and the same command produces the same server on any laptop, in CI, or on a server in a data centre.
That is the whole promise, and the rest of this course is about making it yours: building your own images rather than borrowing other people's, keeping data that should survive, connecting containers together, and diagnosing them when they misbehave.
# Vocabulary
# image a packaged filesystem snapshot + a program: what
# you build and store
# container an image that is running: a process in isolation
# registry a shared store you push images to and pull from
# namespaces kernel feature giving a process its own view of
# processes, network and filesystem
# cgroups kernel feature capping the CPU and memory a
# process may use
# host the machine the containers run on
docker run -p 8080:80 nginx # run nginx, host 8080 -> its 80
docker --version # confirm Docker is installedThe next lesson gets Docker installed on your own machine and
runs your first container properly — including what each part of
that docker run line means and how to stop what you started.
After that comes the distinction between images and containers
in full, which is the one idea that makes the rest of Docker
feel obvious.
For now, sit with the central claim and check it against your own experience: the hardest part of shipping software has rarely been the code. It has been everything around the code. Containers are what happens when you decide to version-control that too.