Skip to main content

Images & registries: packaging containers

Before Kubernetes can run your containers, you need to build and distribute them. This is the supply chain of the container world: a Dockerfile describes how to build an image, the image is pushed to a registry, and machines pull it down to run as containers. Getting these three terms straight — image, container, registry — and understanding why images are immutable is foundational to everything that follows.

Image vs container: the crucial distinction

These two words are constantly confused, so nail them now:

  • An image is a static, read-only package — a blueprint. It contains your application code plus everything it needs to run (runtime, libraries, system tools), frozen into a single file-like artifact. An image does nothing on its own; it just sits there.
  • A container is a running instance of an image. When you "run an image," you start a container from it. One image can spawn many identical containers, the same way one class can create many objects, or one recipe can make many cakes.
Dockerfile\n(buildinstructions)Image\n(staticblueprint)Container 1(running)Container 2(running)Container 3(running)docker builddocker rundocker rundocker run

That one-image-many-containers relationship is why containers scale so well: to run 50 copies of your app, you don't build 50 things — you pull one image and start 50 containers from it.

The Dockerfile: a recipe for an image

You build an image from a Dockerfile — a plain-text file with step-by-step instructions for assembling the image. A minimal one for a Python app reads almost like English:

FROM python:3.12-slim # start from an official Python base image
WORKDIR /app # set the working directory inside the image
COPY requirements.txt . # copy the dependency list in
RUN pip install -r requirements.txt # install dependencies
COPY . . # copy the rest of the app code in
CMD ["python", "app.py"] # the command to run when a container starts

Each line is a build step. Run docker build against this file and you get an image: a self-contained package with Python, your dependencies, and your code, ready to run anywhere.

:::note Layers — why builds and pulls are fast Each Dockerfile instruction creates a layer, and layers are cached and shared. If you change only your app code, the build reuses the cached layers for the base image and dependencies, rebuilding just the last layer — fast. And when many images share the same base layer, machines download it once. Ordering your Dockerfile so rarely-changed steps (installing dependencies) come before often-changed steps (copying code) makes builds dramatically faster — a durable, practical optimization. :::

Registries: where images live and travel

You've built an image on your machine — but Kubernetes runs on other machines that need it. The image has to be stored somewhere central they can fetch it from. That place is a container registry: a storage and distribution service for images, the equivalent of a package repository (like npm or PyPI) but for container images.

The workflow has two verbs:

  • Push — upload your built image to the registry (docker push).
  • Pull — download an image from the registry to run it (docker pull), which Kubernetes does automatically on every machine that needs to run your container.
Build image\n(yourmachine / CI)ContainerRegistry\n(centralstore)Cluster node 1Cluster node 2Cluster node 3pushpullpullpull

Registries come in public and private flavors. Docker Hub is the best-known public registry (home of official base images like python and nginx). For your own private images, each cloud offers a registry — recall the Chapter 1 translation table: ECR (AWS), Artifact Registry (GCP), ACR (Azure). Same concept, three brand names.

Tags and immutability: the reliability backbone

Images are identified by a name and a tag, written name:tag — for example my-app:1.4.0 or nginx:latest. The tag usually marks a version. This is where a durable best practice lives:

  • An image should be immutable. Once you build and push my-app:1.4.0, that exact tag should always refer to that exact, unchanging image. You don't modify it; to ship a change, you build a new image with a new tag (my-app:1.4.1). This immutability is what makes deploys reliable and rollbacks instant: "deploy 1.4.0" means the same precise thing today, tomorrow, and on every machine. Rolling back is just "run 1.4.0 again," and it's guaranteed identical.
  • Avoid relying on latest. The latest tag is mutable — it moves to point at whatever was pushed most recently. Two machines pulling my-app:latest an hour apart can get different images, which silently breaks the "runs the same everywhere" promise. Use specific, immutable version tags in production.

:::tip Why immutability matters so much The whole value of containers — "it runs the same everywhere" — depends on images being immutable and precisely tagged. A specific tag like 1.4.0 is a contract: this exact code, these exact dependencies, frozen forever. That contract is what lets Kubernetes confidently run 200 identical copies, lets you roll back in seconds, and lets staging genuinely match production. Mutable tags like latest quietly violate the contract. This is a durable discipline worth adopting from day one. :::

Why it matters

An image is a static, read-only blueprint (your app + everything it needs); a container is a running instance of one — and one image spawns many identical containers, which is why containers scale. You build images from a Dockerfile (a recipe whose cached layers make builds fast), then push them to a registry (a central store, like a package repo for containers) from which every machine pulls them. Images are identified by name:tag, and the durable discipline is immutability: a version tag like 1.4.0 must always mean the exact same image — that contract is what makes "runs the same everywhere," reliable deploys, and instant rollbacks actually work. With images understood, we can finally meet Kubernetes' own building blocks for running them: pods, deployments, and services.

Next: Pods, deployments & services →