Sunday, 06 September 2026
Advertisement Advertise Your advert could be here Reach thousands of learners and ICT professionals across Rwanda. Contact us
Advertisement Opportunity Jobs, scholarships & hackathons Fresh openings from Rwandan job boards are pulled in every hour. See openings

Layers, caching and builds that are not slow

Docker and CI/CD · lesson 3 of 12

In this lesson: Order a Dockerfile for cache reuse and use a multi-stage build.

Every instruction in a Dockerfile creates a layer — a recorded set of filesystem changes. Layers stack to form the image, and Docker caches them. Understanding the cache is the difference between a build you run happily on every push and one you avoid.

The cache rule

For each instruction Docker asks: have I built this exact step, on this exact parent layer, before? If yes it reuses the result instantly. The moment one step misses, every step after it must be rebuilt — the cache is a chain, not a set.

The mistake everyone makes once

# Slow
COPY . .
RUN composer install

COPY . . includes every source file. Change one line of a template and that layer changes, so composer install reruns — downloading every dependency again because you edited some HTML.

# Fast
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader
COPY . .

Now the dependency layer only rebuilds when the lock file actually changes. Editing source code rebuilds one cheap layer at the end.

The general rule: order by rate of change. Things that change rarely (system packages, dependencies) go near the top. Things that change on every commit (your source) go at the bottom. This one habit typically turns a three-minute rebuild into a five-second one.

Layers are additive — deleting does not shrink

# The build tools are still in the image
RUN apk add --no-cache build-base
RUN make
RUN apk del build-base

Layer three records a deletion, but layers one and two are still in the image and still downloaded by anyone pulling it. To actually keep something out, it must never be committed to a layer:

RUN apk add --no-cache --virtual .build build-base \
 && make \
 && apk del .build

Multi-stage builds

The proper answer. Build in one image, then copy only the finished artefact into a clean one. The compilers, the source, the caches — none of it reaches the final image.

# Stage 1: build the front-end assets
FROM node:20-alpine AS assets
WORKDIR /build
COPY package.json package-lock.json ./
RUN npm ci
COPY resources/ resources/
COPY webpack.mix.js ./
RUN npm run production

# Stage 2: PHP dependencies
FROM composer:2 AS vendor
WORKDIR /build
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --optimize-autoloader

# Stage 3: the image that actually ships
FROM php:8.2-fpm-alpine
WORKDIR /app
COPY --from=vendor /build/vendor ./vendor
COPY --from=assets /build/public/asset ./public/asset
COPY . .
RUN adduser -D -u 1000 app && chown -R app:app /app
USER app
CMD ["php-fpm"]

Node and Composer never appear in the shipped image. It is smaller, it starts faster, and it has far less in it that could be exploited.

Multi-stage builds also fix the secrets problem. If a build stage needs a private registry token, that token stays in the discarded stage. It never reaches a layer anyone can pull and inspect.

Checking your work

docker images                          # how big is it?
docker history yanjye:latest           # what did each layer cost?
docker build --progress=plain -t x .   # see which steps hit the cache
docker build --no-cache -t x .         # prove a clean build still works
Run a no-cache build regularly. A cached build can keep succeeding long after the real one would fail — a package that no longer exists, a repository that moved. Your pipeline usually builds cold, so find out on your own machine rather than at 6pm on a Friday.

Try it yourself

Take a Dockerfile with COPY . . before the dependency install. Time docker build. Change one character in a source file and time it again. Now reorder so the manifest is copied first, and repeat both timings. Write the four numbers down — that difference runs on every push for the life of the project.

Create a free account to save progress

All lessons in this track

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
    Networking and ports ~25 min account needed
  7. 7
  8. 8
  9. 9
    Making the pipeline deploy ~30 min account needed
  10. 10
    Infrastructure as code ~30 min account needed
  11. 11
    Releasing without downtime ~30 min account needed
  12. 12
Advertisement Yanjye Learn a new digital skill this week ICT, programming and professional courses with graded weekly assignments. Start free