Skip to content
Laviyx Learn

Try

Docker and Kubernetes course menu

Docker Layers, Build Cache and .dockerignore Explained

Understand image layers and the build cache, order Dockerfile instructions for fast rebuilds, reduce image size, and use .dockerignore to keep files out of images.

Beginner8 min readUpdated
On this page

Each instruction in a Dockerfile creates a layer. Docker saves these layers, and reuses them when nothing has changed. Understanding this is the key to fast builds and small images.

The build cache#

Terminal
mkdir app
cd app
echo "v1" > code.txt
echo "deps" > deps.txt
cat > Dockerfile <<'EOF'
FROM alpine:3.24
COPY deps.txt .
RUN sleep 2 && echo "installing" > installed.txt
COPY code.txt .
EOF
docker build -q -t cachedemo . > /dev/null
echo "Built"
Output
Built

Build again, with no changes:

Terminal
docker build --progress=plain -t cachedemo . 2>&1 | grep -E '^#[0-9]+ (CACHED|\[[0-9])'
Output
#4 [1/4] FROM docker.io/library/alpine:3.24
#6 [2/4] COPY deps.txt .
#6 CACHED
#7 [3/4] RUN sleep 2 && echo "installing" > installed.txt
#7 CACHED
#8 [4/4] COPY code.txt .
#8 CACHED

Every step after FROM was CACHED, so the build was nearly instant.

What breaks the cache#

Docker checks each step in order. When one step changes, that step and all those after it run again. Change code.txt, which is copied last:

Terminal
echo "v2" > code.txt
docker build --progress=plain -t cachedemo . 2>&1 | grep -E '^#[0-9]+ (CACHED|\[[0-9])'
Output
#4 [1/4] FROM docker.io/library/alpine:3.24
#6 [2/4] COPY deps.txt .
#6 CACHED
#7 [3/4] RUN sleep 2 && echo "installing" > installed.txt
#7 CACHED
#8 [4/4] COPY code.txt .

The slow install step stayed cached, because it comes before the change.

Order matters#

The rule is that things which change rarely go first, and things which change often go last. A typical app changes its code all the time, and its dependencies only occasionally:

DOCKERFILE
# Slow: any code change reinstalls everything
COPY . .
RUN pip install -r requirements.txt
DOCKERFILE
# Fast: dependencies are reinstalled only when requirements.txt changes
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .

Layers and size#

Layers only add. Deleting a file in a later step hides it, but the earlier layer still contains it:

Terminal
cd ~
mkdir size
cd size
cat > Dockerfile <<'EOF'
FROM alpine:3.24
RUN head -c 20000000 /dev/urandom > /big.bin
RUN rm /big.bin
EOF
docker build -q -t size-bad . > /dev/null
cat > Dockerfile <<'EOF'
FROM alpine:3.24
RUN head -c 20000000 /dev/urandom > /big.bin && rm /big.bin
EOF
docker build -q -t size-good . > /dev/null
docker images --format '{{.Repository}} {{.Size}}' | grep size
Output
size-good 8.42MB
size-bad 28.4MB

Both images contain the same files, but one is about 20 MB bigger. So clean up in the same RUN that created the mess:

DOCKERFILE
RUN apt-get update \
    && apt-get install -y --no-install-recommends curl \
    && rm -rf /var/lib/apt/lists/*

.dockerignore#

docker build first sends the whole folder, the build context, to the daemon. A .dockerignore file excludes things you do not want, which makes builds faster, and keeps secrets out of images:

Terminal
cd ~
mkdir ctx
cd ctx
mkdir node_modules
echo "x" > node_modules/lib.js
echo "x" > debug.log
echo "API_KEY=secret" > .env
echo "code" > app.js
cat > .dockerignore <<'EOF'
node_modules
.git
.env
*.log
Dockerfile
EOF
cat > Dockerfile <<'EOF'
FROM alpine:3.24
WORKDIR /app
COPY . .
CMD ["ls", "-A"]
EOF
docker build -q -t ctx . > /dev/null
docker run --rm ctx
Output
.dockerignore
app.js

Without .dockerignore, COPY . . would have put your .env file inside the image.

Force a rebuild#

--no-cache ignores the cache, and --pull fetches a newer base image. Use them to pick up security updates:

Terminal
docker build --no-cache --pull -t app:1.0 .

Common mistakes#

Practice#

Practice exercise

Print the layers of size-bad with docker history, showing only the sizes of its top two lines, to see where the size came from.

Show solution
Terminal
docker history --format '{{.Size}}' size-bad | head -n 2
Output
0B
20MB

The rm layer is empty, and the layer before it holds the 20 MB.

Key takeaway#

Next: Containerising a Python App.

Finished this lesson?

Some links are affiliate links. If you buy through them, Laviyx Learn may earn a commission at no extra cost to you. As an Amazon Associate, we earn from qualifying purchases.