Image Optimization & Layer Caching
๐ฆ Image Optimization & Layer Caching
How Docker Layers Work
Every instruction in a Dockerfile โ RUN, COPY, ADD โ creates a new layer. Docker caches each layer, reusing it on the next build as long as nothing about that instruction (or anything before it) has changed. Understanding this caching behavior is the foundation for everything else in this chapter.
Minimizing Layers
Combining related commands into a single RUN instruction โ using && and line continuations โ reduces the total layer count, and matters for more than just tidiness.
.dockerignore
A .dockerignore file excludes files from the build context sent to the Docker daemon โ the same idea as .gitignore, applied to what a build is even allowed to see.
This speeds up builds (a smaller context transfers faster) and, just as importantly, prevents accidentally COPYing sensitive files โ a local .env with real credentials, for instance โ into an image, the same "never let a secret leak into a built artifact" discipline this site has stressed repeatedly for source control and CI.
Choosing a Base Image: Alpine vs. Slim vs. Full
| Base Image Type | Size | C Library | Compatibility Risk |
|---|---|---|---|
Full (e.g. node:18) | Largest | glibc | Lowest โ includes broad tooling, "just works" |
Slim (e.g. node:18-slim) | Smaller | glibc | Low โ drops non-essential packages, keeps glibc compatibility |
Alpine (e.g. node:18-alpine) | Smallest | musl | Real โ native modules compiled against glibc can fail or behave subtly differently |
Alpine's small size comes from using musl libc instead of the more common glibc โ a genuinely different C library, not just a stripped-down version of the same one. Some native Node.js addons or Python packages compiled against glibc can fail to run, or behave subtly differently, on Alpine โ a well-known, real compatibility gotcha, not a theoretical edge case.
Build Cache Invalidation Order
The moment anything about an instruction changes, that layer's cache is invalidated โ and so is every layer after it, even if those later instructions themselves didn't change at all. Instruction order in the Dockerfile determines how much gets rebuilt on a typical change.
Bad Ordering vs. Good Ordering
Bad: COPY . . First
Any source file change invalidates the COPY layer โ and therefore forces npm install to re-run on every single build, even when dependencies never changed.
Good: Dependencies First
npm install's cache stays valid across source-only changes โ it only re-runs when package.json/package-lock.json actually change.
The general rule: order instructions from least likely to change to most likely to change โ dependency manifests before source code, since dependencies change far less often than application code does during normal development.
๐ป Coding Challenges
Challenge 1: Combine Layers With Cleanup
Rewrite this three-layer sequence into a single RUN instruction that never leaves the cleaned-up files in the image's history: RUN apt-get update, RUN apt-get install -y git, RUN rm -rf /var/lib/apt/lists/*.
Goal: Practice the combine-and-cleanup-in-one-layer pattern this chapter introduced.
Challenge 2: Reorder for Better Caching
A Python Dockerfile has COPY . . immediately followed by RUN pip install -r requirements.txt. Reorder it so source-code-only changes don't force a dependency reinstall.
Goal: Practice applying the dependency-manifest-first ordering rule to a Python project.
Challenge 3: Pick a Base Image
A team's Node.js app depends on a native addon compiled against glibc, and image size is a secondary concern to reliability. Which base image type should they choose, and why not the smallest option?
Goal: Practice weighing size against compatibility risk using this chapter's alpine/slim/full comparison.
Each layer is a diff against the previous one โ when a later RUN rm ... instruction deletes a file, Docker records that deletion as its own layer, but the file still physically exists in the earlier layer's diff, and the image still has to ship it. This is exactly why the three-separate-RUN example above doesn't actually save any space, despite the final command "removing" the apt cache โ the cache is still sitting in the layer created by apt-get update. The fix is always the same: cleanup has to happen in the same RUN instruction that created the mess, never a separate, later one.
๐ฏ What's Next
The next chapter is Docker Compose in Depth โ going beyond docker1's intro: override files, profiles, healthchecks, depends_on with conditions, and explicitly named networks/volumes.