Learning bite
Images and Dockerfiles
Build a small runtime image and distinguish build-time from runtime behavior.
On this page
A Dockerfile is a build recipe
The previous lesson ran an existing image. Now package a file and a runtime command into your own image. Building creates an image; running creates a container from it. A Dockerfile instruction such as RUN executes during the build, whereas CMD supplies the default command for later containers.
Create a new docker-status directory. Save status.json:
{"status":"ok"}
Save this Dockerfile beside it:
FROM python:3.12-slim
WORKDIR /app
COPY status.json ./status.json
USER 10001:10001
EXPOSE 8000
CMD ["python", "-m", "http.server", "8000", "--bind", "0.0.0.0"]
Read it in order. FROM chooses a base filesystem and metadata. WORKDIR sets the working directory. COPY places our file there from the build context. USER selects a non-root numeric UID and GID for subsequent instructions and runtime. This fixture only reads a public file. EXPOSE documents the intended port; it opens no host port. The JSON-array command starts Python directly without an extra shell wrapper.
The last instruction runs a development file server, not a production API. Binding 0.0.0.0 means listen on the container's interfaces; host exposure is chosen separately when running it.
Control the inputs sent to the builder
The final . in a build command names the current directory as its context. COPY can use files from that context, subject to .dockerignore. Save .dockerignore containing:
.git
.env
.venv
__pycache__
*.tfstate*
*.tfplan
.local
Keep secret files out of the context deliberately; do not assume a selective COPY makes a broad context harmless. ARG, ENV, and copied files are not suitable ways to hide build credentials. Use supported build-secret mechanisms when a build truly needs authentication.
Build, run, and read the response
Check the local context from the first lesson. In docker-status, run:
docker build -t learnwithsk-status:dev .
docker run --rm --name sk-status -p 127.0.0.1:8765:8000 learnwithsk-status:dev
Use another terminal for curl --fail --max-time 3 http://127.0.0.1:8765/status.json. Expect {"status":"ok"}. The mapping means host loopback port 8765 forwards to container port 8000. If 8765 is occupied by the earlier Python fixture, stop that fixture before running this one. EXPOSE 8000 alone would not create this mapping.
Stop the container with docker stop sk-status. Because it was started with --rm, Docker also removes the stopped container. Keep the image and source for later lessons.
Images have layers and names
A build produces reusable layers and image metadata. Containers add a writable layer without modifying the shared image. Build caching can reuse unchanged work; changing an early build input can invalidate later steps. Put stable dependency declarations before frequently changing source when that reflects the real dependency relationship.
Change status.json to {"status":"learning"}, rebuild, and run again. Expect the new response. Editing the host file alone does not change an already built image, because COPY captured it during the build. Restore ok and rebuild before the next exercise.
A tag such as python:3.12-slim or learnwithsk-status:dev is a name that can be reassigned. Record resolved identities when comparing runs; later we distinguish local image IDs from registry digests. A cache hit improves speed, not proof of current or trustworthy dependencies.
Checkpoint: why does RUN python -m http.server ... belong in neither this build nor its success criterion? A server is the intended runtime process, not a build step that should wait forever. Next, connect containers and give data an explicit lifetime.
References: Dockerfile reference↗, build context↗, and build best practices↗.
Additional primary references: OrbStack Docker↗.
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.