Learning paths / Containers / Images and layers

Images are layers

Reading · 6 min · Module 2, lesson 1 of 535 min left in this module

Module 2 · Images and layersLesson 1 of 5

Goal: Explain how layer caching works, and why the order of Dockerfile instructions matters.

3:56 · captions and chapters · narrated with an AI-generated voice
Transcript

Narration uses an AI-generated voice.

[00:00] Where we're going

By the end of this video, you'll know why the order of lines in a Dockerfile decides how fast your builds are. It comes down to one idea. An image is a stack of read-only layers. And each instruction that changes files adds one layer, on top of the last.

[00:19] One instruction, one layer

Here's the small Node app from the lesson. Let's build its stack from the bottom up. First, the base image. Its layers come in just as they are. Then the working folder. That's a tiny layer: just the empty folder. Next, the package file. A layer with one file in it. The install adds a layer with all the node modules. And copying everything else adds a layer with your code. What about the command at the end? That's a setting. It adds no files at all. Stacked up, these layers are the file system the container sees. And each one only records what changed.

[01:01] Look at the layers

Here are those layers, as Docker lists them. Read from the bottom up: that's the order they were built. At the bottom sit the base image's layers. The biggest one installs Node itself. Above them are yours. The package file is twelve kilobytes. The installed modules are fifteen megabytes. Your code is fifty-three kilobytes. And the command on top is zero bytes, because it's only a setting.

[01:29] The build cache

Now, the build cache. Change one line of the code, and build again. The folder, the package file and the install all say cached. Only the last copy runs, because it's the first step whose input changed.

Picture the stack again. Everything below the change comes from the cache. The changed step runs again. And every step after it rebuilds too, even if its own input didn't change.

[01:59] Why order matters

So what if the install came after the code? Swap them, and copy everything before installing. Now the code sits below the install in the stack.

Build it once, change the same line, and build again. The copy runs, because the code changed. And now the install runs too. All sixty-eight packages, from scratch. Here it took under a second. On a real app, that's minutes, on every build.

So put what changes rarely at the top of the Dockerfile, and what changes often at the bottom. Copy the dependency list, install, and then copy the code. It's the same rule in every language. Node, Python or Go: the dependency list comes first.

[02:48] Shared, and never smaller

Layers explain two more things. First, they're shared. Two images built on the same base store that base once. And a download only fetches the layers you don't have yet. Second, deleting a file in a later step doesn't shrink the image. The earlier layer still holds it. That goes for secrets too. Anyone who pulls the image can read them from the old layer.

[03:15] Recap

To recap. Each instruction that changes files adds a layer. Docker reuses layers up to the first step whose input changed, and rebuilds everything after it. So copy the dependency list and install first, and your code last.

Here's a question to check yourself. You change one line in server dot js. Which steps run again? Only the last copy. It's the first step whose input changed, and everything above it comes from the cache. Next, tags and digests. I'll see you there.

Key idea

An image is a stack of read-only layers. Each instruction in a Dockerfile that changes files adds one layer on top of the last. When you build again, Docker reuses every layer whose inputs haven't changed, and rebuilds from the first one that has.

One instruction, one layer

A Dockerfile is the recipe an image is built from, one instruction per line. You'll only need four to read this lesson: FROM picks the image to start from, COPY copies files in, RUN runs a command while building, and CMD names what the container runs. Anatomy of a Dockerfile covers them all.

Here's a small Node app's Dockerfile:

# The base image's layers
FROM node:22-alpine
WORKDIR /app
# A layer with one file
COPY package.json ./
# A layer with node_modules
RUN npm install --omit=dev
# A layer with your code
COPY . .
# Settings only, no files
CMD ["node", "server.js"]

Stacked, the layers make the file system the container sees. A layer only records what changed: files added, changed or deleted.

The build cache

Build the image twice without changing anything, and the second build says CACHED for every step. Change server.js and build again:

#6 [2/5] WORKDIR /app
#6 CACHED
#7 [3/5] COPY package.json ./
#7 CACHED
#8 [4/5] RUN npm install --omit=dev
#8 CACHED
#9 [5/5] COPY . .

Only the last COPY runs again, because it's the first step whose input changed. Everything after a changed step rebuilds too, even if its own input didn't change.

Why order matters

Put what changes rarely at the top and what changes often at the bottom. Swap the order, and copy all the code before installing:

COPY . .
RUN npm install --omit=dev

Now every one-line code change invalidates the COPY, so npm install runs again from scratch on every build. On a real app that's minutes instead of seconds.

The rule is the same in every language: copy the dependency list (package.json, requirements.txt, go.mod) and install, then copy the code.

Layers are shared

Two images built on the same base share its layers, on disk and in a download: Docker only fetches layers it doesn't already have. Ten services on node:22-alpine store that base once. A new version of your app is a small download for the same reason: only the layers that changed.

Deleting doesn't shrink

A later layer can hide a file, but the earlier layer still holds it. Copy in a 500 MB file and delete it in the next step, and the image is still 500 MB bigger. The same goes for a secret: anyone who pulls the image can read it from the old layer, as How secrets leak warned.

When a container runs, it gets one thin writable layer of its own on top of the stack. That layer goes away with the container, which The container filesystem is temporary picks up.

Check yourself

You change one line in server.js. Which steps of the Dockerfile above run again?
A Dockerfile copies in a .env file, uses it, then deletes it with RUN rm .env. Is the secret gone from the image?