2026
What are containers?
The mental models that finally made containers click: layers, volumes and networking.
I remember the first time someone told me to “just throw it in a container.” I nodded like I knew what that meant. I did not.
After spending a good chunk of time in test automation, I kept bumping into Docker. Every CI pipeline had it. Every staging environment ran on it. And every time something broke, someone would say “works on my machine” which is, ironically, exactly the problem containers were invented to solve.
So let me break down what containers actually are, how they work under the hood and why they've become the default way to ship software.
The “Works On My Machine” Problem
Before containers, deploying software was a mess of environment differences. Your app might run fine on your laptop with Python 3.9, some specific library versions, a particular OS config, but blow up on the server running Python 3.7 with slightly different dependencies.
The old solution was virtual machines. You'd spin up a full OS image, install everything you needed and ship that. It worked, but VMs are heavy: gigabytes, minutes to boot and a separate OS kernel per machine.
Containers solve the same problem with a fundamentally different approach.
What a Container Actually Is
A container is not a virtual machine. It doesn't have its own kernel. It shares the host machine's kernel but runs in an isolated process space.
The analogy
Virtual Machine
Renting an entire apartment.
Full OS, gigabytes, minutes to boot.
Container
Renting a room in a shared house.
Same kernel, isolated space, starts in seconds.
Under the hood, Linux containers rely on two kernel features:
Isolate what a process can see. A container has its own view of the filesystem, network interfaces, process IDs and more. It genuinely cannot see what's happening in other containers or on the host (unless you explicitly allow it).
Limit what a process can use. cgroups let the kernel enforce resource limits on CPU, memory and disk I/O so one container can't starve out everything else on the machine.
That's really it. Containers are just Linux processes running in isolated namespaces with resource limits applied. Docker and other container runtimes are tooling built on top of those kernel primitives to make working with them actually manageable.
Images vs Containers
Image
The blueprint.
Built in layers. Each Dockerfile instruction adds a layer that gets cached and reused. Only changed layers and everything after them get rebuilt.
Container
A running instance of the blueprint.
Docker adds a thin writable layer on top of all the read-only image layers. Changes disappear when the container stops, unless you use a volume.
A Real Example
Here's a stripped-down Dockerfile for a Node.js app, with each instruction annotated:
FROM node:20-alpineStart from an existing image. Alpine is a minimal Linux distro that keeps the final image small.
WORKDIR /appSet the working directory inside the container.
COPY package*.json ./Copy dependency manifests first before the rest of the code.
RUN npm ciInstall dependencies. This layer gets cached. If your source changes but dependencies don't, Docker reuses this, which means faster builds.
COPY . .Copy the rest of the source code.
EXPOSE 3000Document the port the app listens on.
CMD ["node", "server.js"]What runs when the container starts. Only one CMD per Dockerfile; the last one wins.
Build it with docker build -t my-app ., run it with docker run -p 3000:3000 my-appand your app is running in a container on your machine exactly as it would in production.
Networking Between Containers
When you run multiple containers (e.g. a web server and a database) they need to talk to each other. By default, each container gets its own network namespace so they're isolated.
Docker handles this with virtual networks. Containers on the same Docker network can reach each other by container name. Your app container can connect to postgres:5432 if the database container is named postgres and they're on the same network. No IP addresses to hardcode, no DNS configuration headaches.
Docker Compose makes this even simpler: define all your services in a docker-compose.yml and Compose handles creating the network and wiring everything together. One docker compose up and your entire stack is running.
Why This Actually Matters
When your app and all its dependencies are packaged into an image, the environment stops being a variable. The same image that passed tests in CI is the exact same image that gets deployed to production. Not “basically the same”, identical.
That consistency is what makes modern deployment pipelines possible. It's what lets Kubernetes schedule your workloads across dozens of machines without you thinking about which libraries are installed where. It's why “works on my machine” is no longer an acceptable answer.
Where to Go From Here
Containers are the foundation, but the ecosystem goes much deeper:
Container registries
Docker Hub, AWS ECR. Store and distribute your images.
Kubernetes
Orchestrate containers at scale: scheduling, scaling, self-healing.
Container security
Scan images for vulnerabilities, run as non-root, limit capabilities.
Multi-stage builds
Keep production images small by separating build and runtime environments.
Once you actually understand what a container is, a process in a namespace with resource limits, the rest starts to make a lot more sense. The tooling is just managing that core primitive at different levels of abstraction.
Final Thoughts
Containers are one of those things that seem complicated from the outside and click into place once you understand the two or three concepts underneath them.
The next time someone tells you to “just throw it in a container,” you'll know exactly what they mean and you might even have opinions about how the Dockerfile should be structured.