Docker Images & Layers
BeginnerDocker images are built from stacked read-only layers stored in a Union File System. Understanding layers is the key to writing efficient Dockerfiles and keeping image sizes small.
Overview
Every Dockerfile instruction (FROM, RUN, COPY, ADD) creates a new layer. Layers are content-addressed by SHA256 hash and shared across images — if two images share the same base layer, it is only stored once on disk. When a container starts, Docker adds a thin writable layer on top of the read-only image layers (Copy-on-Write semantics). This is why containers start instantly — the image layers are already on disk. Understanding layer ordering unlocks the most impactful Dockerfile optimisation: cache invalidation. Once a layer changes, all subsequent layers must be rebuilt.
Layer Visualisation
Think of layers as transparent acetate sheets stacked on top of each other. The final image is what you see through all the sheets combined. Each RUN, COPY, ADD instruction adds one sheet.
Layer 5 (COPY . .) ↠writable, specific to your app
Layer 4 (RUN npm ci) ↠node_modules, cached until package.json changes
Layer 3 (COPY package*.json ./) ↠package files
Layer 2 (WORKDIR /app) ↠just sets a directory pointer
Layer 1 (FROM node:20-alpine) ↠~120 MB base: Linux Alpine + Node runtime
────────────────────
All layers = final image
# Inspect layers with:
docker history my-app:latest
IMAGE CREATED SIZE COMMENT
7f2a1b3c9d4e 2 min ago 1.2MB COPY . .
<missing> 2 min ago 45.1MB RUN npm ci
<missing> 2 min ago 1.5KB COPY package*.json ./
<missing> 2 min ago 0B WORKDIR /app
<missing> 3 days ago 121MB node:20-alpineCache Invalidation — The Critical Optimisation
Docker checks if a layer's instruction + context has changed. If yes, that layer and ALL layers below it are rebuilt from scratch. This is why dependency installation must come BEFORE source code copy — your source changes every build, but package.json changes rarely.
# ⌠BAD: cache always invalidated (source changes → npm ci reruns every build)
FROM node:20-alpine
WORKDIR /app
COPY . . # copies everything including src — changes every commit
RUN npm ci # reruns even if package.json didn't change
# ✅ GOOD: npm ci only reruns when package.json changes
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./ # only these two files — stable
RUN npm ci # cached until package.json changes 🚀
COPY . . # src changes here, but npm ci is already cachedReducing Image Size
Every layer adds to the final image size. Use Alpine base images (~5 MB vs Ubuntu ~80 MB), combine RUN commands to merge layers, and clean up in the same RUN step (not a later one, as the previous layer already captured the files).
# ⌠BAD: cache and disk wasteful
RUN apt-get update
RUN apt-get install -y curl git
RUN rm -rf /var/lib/apt/lists/* # too late — previous layers captured the cache
# ✅ GOOD: single layer, clean up in same step
RUN apt-get update && \
apt-get install -y --no-install-recommends curl git && \
rm -rf /var/lib/apt/lists/*
# Check image size
docker images
# REPOSITORY TAG SIZE
# my-app bad 389MB
# my-app good 142MB
# Dive tool — interactive layer explorer
# docker run --rm -it wagoodman/dive my-app:latestKey Points to Remember
- 1Each Dockerfile instruction creates an immutable, cached layer.
- 2Layers are shared across images via content-addressed SHA256 hashes.
- 3Cache invalidation: once a layer changes, all subsequent layers rebuild.
- 4Put stable instructions (base image, dependencies) before volatile ones (source code).
- 5Combine RUN commands and clean up in the same layer to avoid bloat.
- 6Alpine-based images are 10-20x smaller than Debian/Ubuntu-based ones.
Interview Questions
Sign in to ask AriaWhy should you copy package.json before copying source code in a Node.js Dockerfile?
How does Docker's Union File System work?
How do you reduce a Docker image size?
Ask Aria about Docker Images & Layers
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.