Package the app
Your app runs fine on your laptop. Copy just the code to a server and it crashes: the server lacks the flask library your laptop happens to have. Code depends on its surroundings, and surroundings differ from machine to machine.
Docker is a popular container tool. A Dockerfile is a short text recipe for the app and its surroundings: start from a ready-made image with Python 3.12, install flask, copy in the code, and name the command that starts it.
docker build follows the recipe line by line. FROM brings the base image; RUN and COPY each add a layer, a read-only set of file changes. The finished stack is the image: the app plus everything it needs. CMD adds no layer.
Ship and run
In a real project a CI pipeline builds the image on every push, gives it a tag such as v1, and pushes it to a registry: a shared store of images, like Docker Hub or GitHub Container Registry.
The server pulls web:v1 and runs it. A running image is a container: the app's process, sealed off with its own files, plus a thin writable layer on top. It now runs here exactly as on your laptop.
Two containers now run on server 1. They share its kernel, the core of the operating system. A virtual machine instead carries a whole guest operating system of its own. So containers are smaller, start faster, and more fit on one server.
One image should run anywhere, so settings such as the database address are not baked in. They are passed in as environment variables when the container starts, and the code reads them. Same image, different settings.
Run a fleet
With many servers, an orchestrator such as Kubernetes does the running. You declare what you want, three copies of web:v1, and it chooses servers and starts them. Here server 2 pulls the image and starts the third copy.
A container on server 1 crashes. Nobody steps in: the orchestrator keeps comparing what is running with what you asked for, sees only two copies, and starts a replacement. Three copies again.
More visitors arrive, so you ask for four copies instead of three. The orchestrator starts one more on server 2, which already has the image. Scaling down works the same way: ask for fewer.
Release safely
CI pushes web:v2. The orchestrator swaps copies a few at a time, starting v2 copies and stopping v1 copies as the new ones become ready. Most copies keep serving throughout, so users never see the app go down.
v2 has a bug, and errors climb. The orchestrator remembers the previous version, and v1 is still in the registry and on the servers, so going back is one command. It swaps the copies back to v1, a few at a time.
What happens
;; step 1/12 · my-machine
laptop $ python app.py
Python 3.12, flask installed runs
server $ python app.py
ModuleNotFoundError: No module named 'flask'
same code, different surroundings
Kubernetes runs containers inside pods: a pod is one or more containers that share a network address. The figure shows one container per pod. Linux containers share the host's Linux kernel, and kernel features called namespaces and cgroups keep them apart. On Windows and macOS, Docker Desktop runs Linux containers inside a small Linux virtual machine that provides that kernel (Windows can also run Windows containers natively).