What is a container?
A container is an isolated process that ships with everything it needs to run - your application code, the language runtime it depends on, the system libraries it expects to find, and the configuration that ties them together. You start it on any machine that has a container runtime, and it behaves the same way. No installs. No "did you remember to set PATH?" No surprises.
That's the entire promise. Wrap a program and all of its dependencies into a single, immutable unit - and treat that unit as the thing you build, test, ship, and run. Whether the host underneath is your laptop, a CI runner, a bare-metal server, or a cloud platform doesn't matter. The container is the same bytes everywhere.
Containers are isolated processes for each of your app's components. Each component runs in its own isolated environment, completely isolated from everything else on your machine.- docs.docker.com
The problem they solve
Imagine a small web app with three pieces - a frontend, an API, and a database. To run it on your machine you need a specific Node version for the frontend, a specific Python version for the API, and a specific PostgreSQL version for the database. The version on your machine has to match the one your teammate uses, the one CI uses, and the one production uses - or strange things will start happening, the kind of strange things that take an afternoon to track down.
The traditional answers were all painful:
- Install everything globally and hope versions don't drift. They drift. Two projects on the same machine eventually want incompatible versions of the same dependency, and someone has to pick.
- Write a long setup document that walks new engineers through the install steps. The document goes stale within weeks. Onboarding takes a full day.
- Use virtual machines to keep each project isolated. It works, but a VM per project is an enormous amount of overhead - you're paying for a whole operating system to run one program.
Containers replace all three. The application's environment - every library, every runtime, every config file - is described once, frozen into an image, and shipped as a single artifact. You run it. It works. The same image runs the same way for the next person who pulls it.
What's inside a container
It helps to be concrete about what a container actually contains - and, just as importantly, what it does not.
app.py, server.js, a compiled binary, whatever you're running.openssl, libc, zlib, anything else the binary expects to load at startup.That last line is the entire trick. A container is not a tiny virtual machine - it's an ordinary process running on the host's kernel, but with a curated view of the filesystem, the network, and the rest of the system. It thinks it has its own machine. The host knows better.
Containers vs virtual machines
The clearest way to see what makes a container different is to put it next to a virtual machine and look at what each one actually packages up.
A virtual machine is an entire operating system - a guest kernel, hardware drivers, an init system, the same programs that run on any computer - running on top of a hypervisor. To isolate one application, a VM brings along several gigabytes of OS just so the app has somewhere to live.
A container skips that whole layer. It's a process on the host kernel with a private view of the filesystem and the network. Starting one is as fast as starting any other process - typically a fraction of a second - and it sips memory because there is no guest OS to feed. You can run dozens of containers comfortably on a machine that would only fit a handful of VMs.
The trade-off is straightforward. Containers share the host kernel, so they cannot run a different operating system than the host - a Linux container needs a Linux kernel underneath. VMs can run anything, at the cost of running it expensively. In practice the two are used together: cloud providers run VMs, and inside each VM they run many containers.
Images vs containers
Two words get used a lot, and the difference matters.
- An image is a read-only, content-addressed snapshot of everything the container needs - your code, dependencies, runtime, configuration. It's the recipe. It is built once and stored in a registry, where anyone can pull it.
- A container is a running instance of an image. The image is the bytes on disk; the container is the live process. You can start many containers from the same image, and each one is a fresh, isolated copy.
The relationship is the same as a class and its instances in code, or a recipe and the dishes you cook from it. The recipe doesn't change when you cook from it; the dish is short-lived and disposable. Containers are designed to be disposable - stopped, thrown away, started again from the same image - which is precisely what makes them easy to scale, restart, and reason about.
How they actually work
Containers are not magic. They are built out of features the Linux kernel has had for over a decade, composed together by a container runtime. You don't need to know the details to use containers - but it helps to know they aren't a black box.
- Namespaces give a process its own view of the system. A PID namespace makes the container see itself as process 1; a mount namespace gives it its own filesystem root; a network namespace gives it its own interfaces and ports. The process is on the same kernel as everything else, but it can only see what the namespaces allow.
- Control groups (cgroups) put limits on what a process can use - how much CPU, how much memory, how much I/O bandwidth. A container that tries to use more than its share is throttled or killed; one runaway container doesn't take the rest of the host down with it.
- Layered filesystems make images cheap to store and fast to pull. An image is a stack of read-only layers - the base OS files, the runtime layer, your dependencies, your code - and the container gets a thin writable layer on top. Two images that share a base layer share the bytes on disk and on the wire.
A container runtime is the program that turns "run this image" into the kernel calls that set up the namespaces, configure the cgroups, mount the layered filesystem, and start the process. The most familiar one is Docker, but it's not the only one - containerd sits underneath Docker and is used directly by Kubernetes, Podman runs containers without a daemon, and CRI-O is built specifically for Kubernetes. They all produce and run the same kind of container, because they all speak the same standard.
Where containers run
The reason containers feel ubiquitous is that they were standardized. The Open Container Initiative (OCI) defines two specs - one for what an image looks like on disk, and one for how a runtime starts a container from that image. Any tool that implements the specs can build and run any compliant image. That's why the same image you build on your laptop can be pulled and run, byte-for-byte, on a server you've never logged into.
Today that "anywhere" is genuinely anywhere:
- Locally - a developer laptop running Docker Desktop, Rancher Desktop, Podman, or OrbStack.
- On a single server - one or two containers behind a reverse proxy, replacing a traditional package install.
- On an orchestrator - Kubernetes, Nomad, or a cloud-managed equivalent, scheduling thousands of containers across a fleet.
- On serverless container platforms - Azure Container Apps, Google Cloud Run, AWS Fargate - where you hand off a container image and the platform handles scaling, networking, and rollout.
- Inside CI - every modern CI system runs your build steps in throwaway containers so each job starts from a known-clean state.
One image. Many runtimes. The thing you build is the thing that ships, all the way through.
Why they took over
Containers won because they fix problems that engineers had been working around with discipline and luck for decades. The wins compound:
- Portability. The container is the deploy unit. If it runs on your laptop, it runs in production - because the environment travels with the code, not as separate setup steps. The "works on my machine" defense finally went away.
- Density. No guest OS per app means a single host can run dozens or hundreds of containers. Cloud bills shrink in proportion.
- Immutability. Images are content-addressed - the same image is exactly the same bytes, every pull, every time. Deploys become bit-for-bit reproducible.
- Fast iteration. Starting a container takes milliseconds, not minutes. Tests, CI jobs, and ephemeral environments stop feeling expensive, and patterns that depend on them - per-PR preview environments, fan-out testing, scale-to-zero workers - become routine.
- One model for everything. The same primitive runs a web service, a database, a batch job, a test runner, or a one-off script. Tooling, operations, and security all converge on one unit instead of one per kind of workload.
None of these are theoretical. They show up the day a team adopts containers - in shorter onboarding, in faster CI, in fewer "but it ran locally" tickets - and they compound from there. Containers are now the default substrate for almost every new service, and the rest of the stack - orchestrators, observability, package registries, cloud platforms - is built around them.
Code + deps
App, runtime, libraries, and config wrapped together. One artifact to build, test, ship, and run.
Own view
A private filesystem, network, and process tree on the host's kernel. Light, fast, and contained.
Same bytes
Build once, run anywhere a runtime exists. Laptop, CI, server, cloud - same image, same behavior.
Start fast
Milliseconds, not minutes. Spin up, do the work, throw it away - then do it again, ten thousand times.
References
- Docker docs · What is a container?docs.docker.com
- Open Container Initiativeopencontainers.org
- OCI image specificationgithub.com/opencontainers/image-spec
- OCI runtime specificationgithub.com/opencontainers/runtime-spec
- Linux man page · namespaces(7)man7.org/linux/man-pages
- Linux man page · cgroups(7)man7.org/linux/man-pages
- What is an Azure Container App Job?stacknova · cloud · container app jobs