Prefer to click through the interactive model?
Containers — study guide
The same fragments the interactive model serves, read in order. One source, two views.
Ship the whole environment, not just the code
A container packages an application together with everything it needs to run — its libraries, its runtime, its configuration files — into one isolated unit you can move from machine to machine unchanged. The problem it solves is the oldest excuse in software: the code runs on the author's laptop and nowhere else, because that laptop happened to have the right version of some library the server does not. A container ends the argument by shipping the environment along with the code, like a standardized shipping container that any ship, train, or truck can carry without knowing or caring what is packed inside, so the thing that ran in testing is the identical thing that runs in production.
Isolated means the packaged program runs walled off from the rest of the machine: its own view of the filesystem, its own processes, its own network, so it neither trips over other software nor is tripped by it. Portable means that walled-off unit is a single artifact you can copy, store, and launch anywhere the container tool is installed.
The tool used throughout here is podman. You build a container image with podman build, start a container from it with podman run, and list what is running with podman ps. Every command in this concept is a podman command, and the payoff is consistency: one unit, built once, behaving the same everywhere it lands.
The template and the running thing
Two words get used loosely and mean very different things: an image and a container. An image is a read-only template — a frozen filesystem plus the metadata that says how to start the program inside it. It does nothing on its own; it just sits there, complete and unchanging. A container is what you get when you run that image: a live instance, an actual running process with the image's files beneath it.
Because the image never changes, one image can produce many containers at once, like a cookie cutter and its cookies: one unchanging template stamps out many identical pieces. Run the same image five times and you have five independent containers, each isolated from the others, all stamped from the identical template. podman run is the verb that turns the template into a running thing; do it again and you get another.
The distinction is worth holding onto, because almost everything else follows from it. Building, tagging, pushing, and pulling all act on images — the durable, shareable artifact. Starting, stopping, and inspecting all act on containers — the live, disposable instances. Confusing the two is the source of most early container trouble.
Built in stacked layers
An image is not one solid block. It is a stack of read-only layers, one laid down for each step in the build, like a stack of transparent sheets, where the lower sheets can be shared beneath several drawings and only the top sheet is written on. Each layer records only what changed at that step — a base of operating system files, then a layer that adds a language runtime, then one that copies your program in — and the finished image is all of them stacked in order.
Because each layer is identified by its exact content, identical layers are stored once and shared. Two images that both begin from the same base operating system layer point at that one copy on disk rather than duplicating it, which is why pulling a second image that shares layers with one you already have is fast and small. Rebuilding after a small change reuses every unchanged layer from cache and only redoes the layer that actually differs.
All of those layers are read-only. A running container adds one more layer on top — a thin writable layer, where anything the program creates or changes during its run is recorded, kept separate from the shared image beneath. That top layer belongs to the one container and lasts only as long as it does; keeping data beyond the life of a container is its own separate problem.
Isolated, but sharing the kernel
A container feels like its own little machine, but it is not one. It is just one or more ordinary processes running on the host, made to look isolated by two features of the Linux kernel. Namespaces give a process its own private view of things the system usually shares — process IDs, the filesystem, the network, users — so from inside, the container sees only itself. Cgroups (control groups) cap how much CPU, memory, and other resources it may consume. Together they are what isolation means here, the same namespaces and cgroups that resource isolation is built on.
The crucial part is what containers do not have: their own operating system kernel. Every container on a host shares that one host kernel and runs directly on it. A virtual machine is the heavier alternative — it runs a full guest operating system, kernel and all, on top of a hypervisor that emulates hardware, so each VM carries an entire OS of its own.
That single difference explains the whole reputation. Because a container adds no guest OS, it starts in a moment, weighs a fraction of what a full VM weighs, and packs many to a host. The tradeoff is that containers share a kernel and VMs do not, which is a real boundary — but for shipping and running applications, the lightness is usually what wins.
A recipe file becomes an image
An image is built from a plain text recipe called a Containerfile (also known as a Dockerfile — the same format under an older name). Each line is one build step: start from a base image, install a package, copy your code in, set the command that runs when the container starts. The file is read top to bottom, and the order matters, because each step builds on the one before it.
podman build reads that Containerfile and produces an image, turning each instruction into one of the image's layers. Because the layers are cached, a rebuild after editing a late step reuses the earlier layers untouched and only recomputes from the changed step onward — which is why putting the parts that rarely change near the top and your fast-moving code near the bottom keeps rebuilds quick.
The result is a single named image, built by a repeatable recipe rather than by hand. Anyone holding the Containerfile can rebuild the same image, and the recipe itself becomes the honest documentation of exactly what the environment contains.
Naming, pushing, and pulling images
An image needs a name to be shared, and the naming scheme is name:tag — a name for what the image is, and a tag, after the colon, for which version of it. The tag is how myapp:1.2 and myapp:latest refer to different builds under one name. Left without a tag, most tools assume latest, which is a moving label rather than a fixed one.
Images are stored and shared through a registry — a server that holds named images for anyone allowed to reach it. podman push uploads an image you built to a registry, and podman pull downloads one from it. Building an image populates only your own machine, so pushing is how it leaves and pulling is how another machine gets it.
This is the last piece that makes an image portable in practice. Build once, push to a registry, and any machine that can pull it runs the identical image — the same bytes, the same layers, the same behavior — without ever seeing your source code or your build steps.
Why podman, and where docker fits
If you have heard of any of this before, you have probably heard it called Docker. Docker is the tool that made containers mainstream, and its name is the word most people still use for the whole idea. Podman is the same idea with the same commands: it runs the same OCI-standard images, and its command line is compatible enough that many people simply set alias docker=podman and carry on.
The differences are in how each one runs. Podman is daemonless — there is no always-running background service that every command must go through; each command runs on its own. It is rootless-first, able to run containers as an ordinary unprivileged user rather than requiring root. And it is fully open source. Docker's engine is open source too, but Docker Desktop, the packaged product many people install, is a different matter: as of mid-2026, Docker Desktop requires a paid subscription for larger organizations, while staying free for personal use, education, and smaller companies. That is Docker's licensing policy, which has changed before and could change again, not a law of the tool.
So the honest summary is that Docker and podman do the same job, Docker is the name you will hear everywhere, and podman is what we use here: open source, daemonless, rootless, and command-compatible enough that everything you learn transfers straight back to Docker if you ever need it.
The unit you actually ship
The reason containers are worth learning is that they are the unit real systems ship in. When you build an agent pipeline or stand up a homelab service, you do not hand someone your source tree and a page of setup steps and hope. You build it into an image, and that image is what moves — to a teammate, to a server, to a registry and back down onto whatever runs it.
The image that ran on your machine is byte-for-byte the image that runs on the server, so "works on my machine" stops being a gamble and becomes a guarantee. Launching it is one command: podman run against the image, and the service is up with its whole environment intact, isolated from whatever else the host is doing.
That is what makes the container model load-bearing. Later concepts — keeping data across restarts, describing infrastructure as code, running many containers together — all assume this one is solid. The container is the brick; the rest is how you build with it.