top of page

Containers Explained Simply

  • ShiftQuality Contributor
  • Jan 12
  • 6 min read

"It works on my machine."

Four words that have wasted more engineering hours than any bug in history. You write code, it runs perfectly on your laptop, you hand it off for deployment, and it breaks. Different operating system. Different library version. Different configuration. Something, somewhere, doesn't match.

This isn't a discipline problem. It's an environment problem. Your app doesn't just depend on the code you wrote. It depends on everything underneath it — the runtime, the libraries, the system tools, the config files, the exact version of every dependency. Your machine has all of those things in a specific arrangement. The production server doesn't.

Containers fix this.

What Is a Container?

A container is a lightweight, portable box that packages your application together with everything it needs to run. The code, the runtime, the libraries, the system tools, the configuration — all of it, bundled into one unit.

When you run a container, it doesn't care what machine it's on. It carries its own environment. Your laptop, a staging server, a production cluster in the cloud — the container behaves the same way everywhere because it brings its own context.

Think of it like shipping. Before standardized shipping containers, every port had its own methods for loading cargo. Boxes of different sizes, different handling requirements, constant breakage and delays. Then someone invented the standard shipping container: a uniform box that fits on any truck, any train, any ship. The contents don't matter. The box is the interface.

Software containers work the same way. The contents (your app) are packaged into a standard format that any compatible system can run. The infrastructure doesn't need to know or care what's inside.

Containers vs. Virtual Machines

You might be thinking: "Isn't this what virtual machines do?"

Close, but there's a critical difference. Here's the clearest way to think about it.

Virtual machines are houses. Each house has its own foundation, its own plumbing, its own electrical system, its own roof. Every VM runs a complete operating system — the full stack, from kernel to user space. This is secure and fully isolated, but it's heavy. Each VM might consume gigabytes of memory and take minutes to start, because you're booting an entire operating system every time.

Containers are apartments. Apartments share the building's foundation, plumbing, and electrical. Each apartment has its own living space, its own furniture, its own lock on the door — but the core infrastructure is shared. Containers share the host operating system's kernel. Each container has its own filesystem, processes, and network interface, but they're not running separate operating systems. They're isolated spaces within the same OS.

The practical difference:

| | Virtual Machine | Container | |---|---|---| | Startup time | Minutes | Seconds | | Size | Gigabytes | Megabytes | | OS | Full OS per VM | Shared host OS kernel | | Isolation | Complete | Process-level | | Resource usage | Heavy | Light |

You can run a handful of VMs on a typical machine. You can run dozens or hundreds of containers on the same hardware.

Docker: The Tool That Made This Mainstream

Containers as a concept existed before Docker. Linux had containerization technology (cgroups, namespaces) for years. But nobody used it because it was complicated and inaccessible.

Docker, released in 2013, changed that. It wrapped the underlying Linux container technology in a simple, developer-friendly interface. Suddenly, anyone could build, share, and run containers without understanding kernel internals.

Docker gave the world three things:

  1. A simple format for defining containers

  2. A simple tool for building and running them

  3. A simple registry for sharing them

That combination turned containers from a niche Linux feature into the standard way modern software is packaged and deployed.

The Key Concepts

There are four terms you need to know. That's it.

Image

An image is the blueprint. It's a read-only template that defines what goes inside a container — the OS base, the installed software, your application code, the configuration. An image doesn't run. It's a snapshot, a recipe frozen at a point in time.

Container

A container is a running instance of an image. When you start an image, it becomes a container — a live, isolated process with its own filesystem and network. You can run multiple containers from the same image, just like you can print multiple copies of the same blueprint.

Dockerfile

A Dockerfile is the recipe for building an image. It's a plain text file with step-by-step instructions: start with this base, install these packages, copy this code, run this command. Dockerfiles are version-controlled alongside your code, so your environment definition lives with your application.

Registry

A registry is where images are stored and shared. Docker Hub is the most common public registry — it has thousands of pre-built images for databases, web servers, programming languages, and tools. You pull images from a registry to use them. You push images to a registry to share them.

A Simple Dockerfile

Here's what a real Dockerfile looks like for a basic Python web application:

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]

Line by line:

  • FROM python:3.12-slim — Start with an official Python 3.12 image. This is your base. Someone else already set up Python for you. The slim variant keeps the image small by excluding packages you probably don't need.

  • WORKDIR /app — Set the working directory inside the container to /app. Everything after this happens in that directory.

  • COPY requirements.txt . — Copy your dependency list into the container. This is done separately from the rest of the code so Docker can cache this step — if your dependencies haven't changed, Docker skips reinstalling them.

  • RUN pip install -r requirements.txt — Install the Python dependencies.

  • COPY . . — Copy the rest of your application code into the container.

  • CMD ["python", "app.py"] — Define what happens when the container starts. In this case: run the app.

Six lines. That's a fully defined, reproducible environment. Anyone with Docker installed can build and run this, regardless of what operating system they're on, what Python version they have locally, or what other software is on their machine.

Why This Matters

Containers solve several problems at once:

Consistency. The "works on my machine" problem disappears. Development, staging, and production all run the same container. If it works in one place, it works everywhere.

Deployment simplicity. Instead of writing deployment scripts that install dependencies, configure environments, and pray nothing conflicts, you ship a container. The server runs it. Done.

Scalability. Need to handle more traffic? Start more containers. Containers are small and fast to start, which makes scaling straightforward. This is the foundation that orchestration tools like Kubernetes build on.

Isolation. Each container runs independently. One app's dependencies can't conflict with another's. You can run Python 2 and Python 3 side by side without either one knowing the other exists.

Reproducibility. Your Dockerfile is a version-controlled, human-readable definition of your environment. No more "how was this server configured?" mystery. The answer is in the Dockerfile.

When NOT to Use Containers

Containers are not always the right tool. Don't use them reflexively.

Simple scripts. If you're writing a one-off script that runs on your machine, containerizing it adds complexity with no payoff. Just run the script.

Quick prototypes. When you're hacking on an idea and iterating fast, the overhead of building images slows you down. Get the concept working first. Containerize later if it survives.

When your team doesn't understand them. Adding containers to a workflow where nobody knows how to debug them creates a new category of problems. Learn the basics first, then adopt.

Desktop applications. Containers are built for server-side workloads. Trying to containerize a GUI application is technically possible but practically painful.

When the environment is already consistent. If you're deploying a single app to a single server that you fully control, and the environment never changes, containers may be unnecessary overhead. They solve the consistency problem — if you don't have that problem, you don't need the solution.

Takeaway

A container packages your application with its entire environment into a single portable unit. It eliminates the gap between "it works here" and "it works there." Docker made this practical by providing simple tools to build, run, and share containers.

You don't need to master Docker on day one. Understand the concept: an image is the blueprint, a container is the running instance, a Dockerfile is the recipe, and a registry is where you share them. That mental model covers 90% of what you need to start.

Next in the "DevOps Without the Jargon" learning path: CI/CD pipelines — what they actually do, how they work, and why automated testing and deployment aren't just for big teams.

Comments


bottom of page