6.1 Containers and Docker

Checked against Docker's and Kubernetes' own reference documentation, August 2026

What this is and why it exists

A container is a way of shipping an application together with everything it needs to run, so that the machine it lands on stops mattering. That promises less than it sounds like and delivers more: most of the deployment problems people spend years on are differences between environments, and a container removes the category. Learn this properly before Kubernetes — most teams need containers and never need a cluster.

The vocabulary

  • Image — a read-only filesystem plus the metadata saying how to start it; the thing you build and ship.
  • Layer — one step of the build, stacked on the one below; images are made of layers and layers are cached and shared.
  • Container — one running instance of an image, with a thin writable layer of its own.
  • Registry — where images are stored and fetched from. Amazon has ECR, Azure has Container Registry, Google has Artifact Registry.
  • Dockerfile — the file of instructions that builds an image.
  • Tag — a human-readable name for a version of an image; digest is its content hash, which cannot move.
  • Volume — storage attached to a container that outlives it.
  • Build context — the directory sent to the builder, from which files can be copied.

The mental model

An image is a stack of read-only layers and a container is that stack with one writable layer on top. Two consequences you will meet on the first day. Anything written inside a running container lives in that thin top layer and disappears when the container does, so persistent data goes in a volume or, better, in a database or object storage. And identical layers are stored once and downloaded once, which is why ten containers from the same image do not cost ten times the disk.

The Dockerfile is where those layers come from, and the reference is precise about each instruction. FROM "initializes a new build stage and sets the base image", and a valid Dockerfile must start with one. WORKDIR "sets the working directory" for the instructions that follow. COPY copies files from the build context into the image. RUN "will execute any commands to create a new layer on top of the current image". ENV sets an environment variable for the rest of the build stage. EXPOSE "informs Docker that the container listens on the specified network ports at runtime" — the reference is careful to add that "it functions as a type of documentation between the person who builds the image and the person who runs the container", so it does not publish anything by itself. CMD "sets the command to be executed when running a container from an image".

Order is the whole art, and it follows from the layer cache: a layer is rebuilt when it or anything above it changes. So put the things that change rarely first and the things that change constantly last. Copy the dependency manifest and install dependencies before copying the source, and a change to one line of code rebuilds one layer instead of reinstalling everything. Get that order wrong and every build reinstalls the world — the same Dockerfile, ten times slower.

Size follows from the same kind of care. Start from a small base image, do not install build tools you only needed during the build, and combine the fetch-and-clean steps that would otherwise leave a cache in a layer forever. Remember that deleting a file in a later layer does not remove it from the image — the earlier layer still holds it, and anyone who pulls the image still gets it. That is a security fact as much as a size one: a secret copied in and deleted later is still in the image.

Configuration comes in from outside, never baked in. The same image runs in development, staging and production, and what differs is the environment it is given: the database address, the log level, the credentials fetched at start-up. An image that contains its own configuration is an image that has to be rebuilt to move, which throws away the property you built it for.

And the sentence in the checkpoint — "works on my machine" stops being one — deserves an honest reading. It is true for the application and its dependencies, which is a great deal. It is not true for the things outside the image: the architecture the image was built for, the kernel underneath it, the network it lands in, the volumes attached to it, and the resources it is given. Containers make the software identical. They do not make the world identical.

Portable operations

Checked against Docker's own reference. A small, cache-friendly Dockerfile has this shape:

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["python", "-m", "myapp"]

The dependency file is copied and installed before the source is copied, so editing your code rebuilds only the last three layers. And the running side:

docker build -t myapp:1.4.0 .
docker run -p 8000:8000 -e LOG_LEVEL=info myapp:1.4.0
docker run -v mydata:/var/lib/app myapp:1.4.0
docker push registry.example/myapp:1.4.0

docker run "runs a command in a new container, pulling the image if needed and starting the container"; -t on the build gives the image its name and tag in the form registry, repository and tag. Of the run options, -p is "publish a container's port(s) to the host" — the step EXPOSE documents but does not perform — -e is "set environment variables", and -v attaches a volume so data survives the container.

One habit worth building now: prefer a digest or an immutable version tag over a moving one. python:3.12-slim moves as the base is rebuilt; a digest does not. Reproducible builds are the point, and a tag that changes underneath you is the quietest way to lose it.

What you should now be able to explain or do

Explain what an image is made of and where a running container's writes go. Order a Dockerfile so that a code change does not reinstall dependencies, and say why the order matters. Explain why deleting a file in a later layer does not remove it from the image, and what that means for a secret. Say what EXPOSE does and does not do. Pass configuration into a container without rebuilding it. State two things a container does NOT make identical.

Check yourself

It was in the container's thin writable layer, which is discarded with the container. Anything that must survive belongs in a volume, or better in a database or object storage.

The source is copied before the dependencies are installed, so the install layer is invalidated every time any file changes. Copy the dependency manifest and install first, then copy the source.

No. The earlier layer still contains it and travels with the image. Rotate the credential and rebuild without it ever entering.

No — the reference calls it documentation between the person who builds the image and the person who runs it. Publishing the port is -p at run time.

The processor architecture and kernel underneath, and the surroundings — the network it lands in, the volumes attached, the resources it is given. Identical software, different world.

Go deeper

These videos are on YouTube. Opening the link takes you to YouTube's page. Pressing "Watch here" loads YouTube's player into this page — nothing loads from YouTube until you do. Either way the video comes from Google and uses much more mobile data than a page of text. Something wrong with a link here?

Back to Containers and Docker: work through the checklist