Learning bite
Multi-stage builds and runtime boundaries
Separate build tools from runtime needs and verify what the final image actually contains.
Docker
Predict before you try
Checkpoint: why is copying only the binary sometimes insufficient?
Try it: Try the transfer with a deliberately small example →On this page
Build tools and runtime tools serve different jobs
A frontend may need Node to compile browser files but only a file server to serve the result. A Java service needs a compiler during a build and a compatible runtime afterward. Python dependencies may need compilation tools even when the running service does not. A multi-stage Dockerfile lets one stage produce files and another stage copy only what it needs.
This can reduce unnecessary runtime contents, but small is not synonymous with correct or secure. Native libraries, certificates, writable paths, runtime user, and architecture still matter. A minimal image without a shell also changes how you troubleshoot it.
Try the transfer with a deliberately small example
Create a new docker-stages directory so the earlier fixture remains intact. Save Dockerfile:
FROM python:3.12-slim AS build
WORKDIR /build
RUN python -c 'from pathlib import Path; Path("status.json").write_text("{\"status\":\"ok\"}\n")'
FROM python:3.12-slim AS runtime
WORKDIR /app
COPY --from=build /build/status.json ./status.json
USER 10001:10001
EXPOSE 8000
CMD ["python", "-m", "http.server", "8000", "--bind", "0.0.0.0"]
The first stage generates a file. The second starts from a fresh base and copies that one output from the named stage. It does not inherit every file in the first stage. Both stages deliberately use Python here so you can learn the transfer without another compiler or dependency manager; this example makes no image-size reduction claim.
From this directory:
docker build -t learnwithsk-stages:dev .
docker run --rm learnwithsk-stages:dev python -c 'from pathlib import Path; print(Path("/app/status.json").read_text(), end=""); print(Path("/build").exists())'
Expected output is the ok JSON and then False. The final image has the copied file at /app/status.json, not the build stage's /build directory. This command overrides the default server command to inspect the runtime filesystem.
Run the default command with a temporary loopback port mapping, request status.json, then stop and remove only this fixture. You can use the same docker run --rm --name ... -p 127.0.0.1:8765:8000 ... pattern after confirming the earlier server is stopped. Remove learnwithsk-stages:dev when finished.
Read the final process settings
CMD provides the default command or default arguments; ENTRYPOINT can define the executable they accompany. For an image with ENTRYPOINT ["python", "tool.py"] and CMD ["--help"], default execution runs python tool.py --help; arguments after the image name replace the default CMD arguments. --entrypoint explicitly overrides the entrypoint.
Exec-form arrays avoid an extra shell and help the main process receive signals directly. They do not solve every shutdown problem: the application still needs to handle termination and child processes appropriately. A shell wrapper that fails to use exec may become the process receiving shutdown instead of the application.
Apply the idea to a real service
For each MicroBank service, write a table of build inputs, build-only tools, files copied to runtime, runtime command, user, port, and writable directories. Verify native dependencies and target architecture rather than choosing an image solely by size. Keep credentials, state, .local/, and private evidence out of the build context.
Checkpoint: why is copying only the binary sometimes insufficient? It may depend on runtime libraries, configuration, certificates, or files not embedded in it. Why must the final stage be tested? A successful compilation proves less than a successful runtime operation.
Next, use the Compose lab to observe the complete local lifecycle before moving to CI/CD and the guided MicroBank profile.
References: multi-stage builds↗, Dockerfile command behavior↗, context and dockerignore↗, and cache invalidation↗.
Prove what you learned
Inspect the result
Retain image identity, selected profile, readiness/operation evidence and scoped cleanup.
Explain it in your own words
Explain why your observation supports the stated outcome, and name one thing it does not prove.
Try a small variation
Change one declared runtime input or recreate only the disposable container; predict which state persists.
Your evidence should show
- Record the context/revision or label the work as a paper or prepared fixture.
- Compare the prediction with the actual result from “Try the transfer with a deliberately small example”.
- Retain image identity, selected profile, readiness/operation evidence and scoped cleanup.
- Explain the transfer challenge and retain the same verification question.
Hint
Separate build inputs, process lifecycle, dependency readiness, network and persistent storage.
Answer guide: compare after predicting
It may depend on runtime libraries, configuration, certificates, or files not embedded in it. Why must the final stage be tested
Compare this with your actual observation. A paper answer is useful reasoning practice; record execution separately.
Recall the process/kernel boundary and whose localhost a request uses. Revisit the previous lesson.
Save the prediction, result and explanation ↓Your notes and evidence
Record observations, questions, or links to your work. Keep credentials out of your notes.
Back up or restore this path
Progress and notes stay in this browser. A backup contains only this learning path.