Skip to main content
RunBook Academy

← All labs in Docker & Containers

Lab · intermediate · ~45 min

Lab 7: Build a multi-stage image with BuildKit cache mounts

B · Nested virtualisationC · Simulation

Objectives

  • Author a multi-stage Dockerfile
  • Use BuildKit cache mounts
  • Verify the runtime image runs as non-root and is small

Prerequisites

  • Lab 5: install Docker

Objective

Build a small, reproducible, non-root image for a Python web service. Use multi-stage, BuildKit cache mounts for pip, and a distroless or slim runtime image.

Tasks

Task 1: Create the project

mkdir -p ~/multi-stage-lab && cd ~/multi-stage-lab

cat > app.py <<'EOF'
from http.server import BaseHTTPRequestHandler, HTTPServer
class H(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-Type','text/plain')
        self.end_headers()
        self.wfile.write(b'ok')
HTTPServer(('0.0.0.0', 8080), H).serve_forever()
EOF

cat > requirements.txt <<'EOF'
EOF

Task 2: Author the Dockerfile

# syntax=docker/dockerfile:1.7
FROM python:3.12-slim AS build
WORKDIR /src
COPY requirements.txt ./
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install --user --no-cache-dir --upgrade pip && \
    pip install --user --no-cache-dir httpie 2>/dev/null || true
COPY app.py ./

FROM gcr.io/distroless/python3-debian12:nonroot
WORKDIR /app
COPY --from=build /root/.local /root/.local
COPY --from=build /src/app.py ./
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["python3", "/app/app.py"]

Task 3: Build and inspect

DOCKER_BUILDKIT=1 docker build -t myorg/handson:1.0.0 .
docker image ls myorg/handson:1.0.0
docker history myorg/handson:1.0.0 --no-trunc

Note the size: a distroless/python3 runtime image with only your app is typically ~100 MB; a full python:3.12 runtime would be ~1 GB.

Task 4: Run and verify

docker run --rm -d --name hands-on -p 8080:8080 myorg/handson:1.0.0
sleep 1
curl -s http://localhost:8080
# ok

docker exec hands-on id
# uid=65532(nonroot) gid=65532(nonroot) groups=65532(nonroot)

docker rm -f hands-on

Task 5: Reproducibility check

Run the build again with no source changes. The image digest should be identical:

docker image inspect myorg/handson:1.0.0 --format '{{.Id}}'
DOCKER_BUILDKIT=1 docker build -t myorg/handson:1.0.0 .  # no-op
docker image inspect myorg/handson:1.0.0 --format '{{.Id}}'

Cleanup

docker rmi myorg/handson:1.0.0

Verification status

Last reviewed
2026-08-09
Executed end to end
not yet run on hardware

The commands and configuration here have been reviewed against the verified software versions, but nobody has run this lab start to finish on a system meeting its prerequisites. Treat the Expected Outcome as the intended result rather than an observed one, and keep the Cleanup section to hand.