Checkpoint Lab — Dockerfile Fundamentals: FROM, RUN, COPY, ADD, WORKDIR, ENV, ARG, USER, CMD, and ENTRYPOINT
The checkpoint turns the chapter into an evidence packet: a tiny non-root image is built from a digest-resolved base, its declared inputs and runtime defaults are inspected, fake-secret checks are demonstrated safely, command semantics are verified, and only chapter-owned resources are cleaned up.
Learning objectives
- Produce a chapter-owned Dockerfile project using only synthetic inputs and a digest-resolved base.
- Predict which instructions change filesystem layers and which change image configuration, then verify the predictions.
- Prove non-root runtime identity, working directory, environment, entrypoint, command, and declared file contents.
- Create a compact evidence packet linking Dockerfile/source inputs to final local image identity and container behavior.
- Perform exact cleanup without broad prune or deletion of unrelated Docker state.
# syntax=docker/dockerfile:1 for most users
who want the latest stable Dockerfile 1.x frontend, documents
ARG/ENV as inappropriate secret channels,
and supports docker build --check for Dockerfile build
checks. The labs resolve the base tag to a repository digest before
building and never require a registry push, paid service, privileged
mode, Docker socket mount, or broad prune.
1. Checkpoint mission and acceptance criteria
Create one small local image whose Dockerfile expresses a clear build/runtime contract. The base must be recorded by digest, the final process must run non-root, no secret may be embedded, command semantics must be intentional, the final image configuration must be inspectable, and cleanup must target only chapter-owned objects.
- Five core evidence classes exist: tool/context, source/base, build/check, image config/identity, runtime/cleanup.
-
The running process reports UID 65532 and working directory
/app. - Default ENTRYPOINT/CMD behavior and one CMD override are both demonstrated.
- No real secret, registry credential, Docker socket, privileged mode, or broad prune is used.
2. Create the evidence workspace
mkdir -p ch07-checkpoint/evidence
cd ch07-checkpoint
date -u +%Y-%m-%dT%H:%M:%SZ | tee evidence/00-started-at.txt
docker context show | tee evidence/01-context.txt
docker version | tee evidence/02-version.txt
docker info | tee evidence/03-info.txt
docker buildx version | tee evidence/04-buildx.txt
3. Resolve the base identity
BASE_TAG='busybox:1.37.0'
docker pull "$BASE_TAG"
BASE_IMAGE=$(docker image inspect "$BASE_TAG" --format '{{index .RepoDigests 0}}')
printf 'base_tag=%s\nbase_digest_ref=%s\n' "$BASE_TAG" "$BASE_IMAGE" | tee evidence/05-base.txt
If RepoDigests is unexpectedly empty, stop and
investigate the local/registry state instead of substituting a
guessed digest.
4. Create declared source and hash it
cat > app.sh <<'EOF'
#!/bin/sh
set -eu
printf 'name=%s mode=%s uid=%s cwd=%s\n' \
"${1:-academy}" "${APP_MODE:-unset}" "$(id -u)" "$PWD"
printf 'content='; cat /app/content.txt
EOF
chmod 0755 app.sh
printf 'chapter-07-checkpoint\n' > content.txt
sha256sum app.sh content.txt | tee evidence/06-source-hashes.txt
5. Author the checkpoint Dockerfile
# syntax=docker/dockerfile:1
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
ARG BUILD_CHANNEL=checkpoint
ENV APP_MODE=academy
WORKDIR /app
COPY --chmod=0555 app.sh /app/app.sh
COPY --chmod=0444 content.txt /app/content.txt
RUN printf 'build-channel=%s\n' "$BUILD_CHANNEL" > /app/build-channel.txt
USER 65532:65532
ENTRYPOINT ["/app/app.sh"]
CMD ["student"]
sha256sum Dockerfile | tee evidence/07-dockerfile-hash.txt
Before building, predict: COPY and RUN will change image filesystem
content; ENV/WORKDIR/USER/ENTRYPOINT/CMD will be visible in image
configuration; ARG BUILD_CHANNEL itself will not be a
runtime environment variable, although the RUN instruction persists
its value into a file.
6. Demonstrate the secret rule without a real secret
Create a separate, intentionally broken file using only a fake value. Do not merge it into the checkpoint Dockerfile.
cat > Dockerfile.secret-demo <<'EOF'
FROM busybox:1.37.0
ARG DEMO_API_TOKEN=not-a-secret
ENV DEMO_PASSWORD=not-a-secret
CMD ["true"]
EOF
docker build --check -f Dockerfile.secret-demo . 2>&1 | tee evidence/08-secret-check-demo.txt || true
The point is to capture the check behavior and then discard the unsafe pattern. Never replace the fake strings with real credentials.
7. Validate the real Dockerfile
docker build --check \
--build-arg BASE_IMAGE="$BASE_IMAGE" \
--build-arg BUILD_CHANNEL=checkpoint \
. 2>&1 | tee evidence/09-build-check.txt
If the check reports a violation, preserve the file and output, repair the Dockerfile source, hash it again, and rerun. Do not suppress checks merely to produce a green result.
8. Build and capture the result
IMAGE='devops-academy-ch07-checkpoint:local'
docker build --progress=plain \
--build-arg BASE_IMAGE="$BASE_IMAGE" \
--build-arg BUILD_CHANNEL=checkpoint \
--label devops-academy.lab=ch07-checkpoint \
-t "$IMAGE" . 2>&1 | tee evidence/10-build.txt
docker image inspect "$IMAGE" --format 'ImageID={{.Id}} RepoTags={{json .RepoTags}} RepoDigests={{json .RepoDigests}}' \
| tee evidence/11-image-identity.txt
docker history --no-trunc "$IMAGE" | tee evidence/12-history.txt
9. Verify configuration predictions
docker image inspect "$IMAGE" \
--format 'User={{.Config.User}} Workdir={{.Config.WorkingDir}} Entrypoint={{json .Config.Entrypoint}} Cmd={{json .Config.Cmd}} Env={{json .Config.Env}}' \
| tee evidence/13-config.txt
Verify that the values correspond to the source Dockerfile. Do not infer runtime health from this inspect result; it proves only the image's configuration.
10. Verify default runtime behavior and effective identity
DEFAULT='devops-academy-ch07-checkpoint-default'
docker run --name "$DEFAULT" --label devops-academy.lab=ch07-checkpoint "$IMAGE" \
| tee evidence/14-default-run.txt
docker inspect "$DEFAULT" --format 'ID={{.Id}} Image={{.Image}} User={{.Config.User}} Path={{.Path}} Args={{json .Args}} Exit={{.State.ExitCode}}' \
| tee evidence/15-default-inspect.txt
docker run --rm --entrypoint sh "$IMAGE" -c 'id; pwd; ls -ln /app; printf "BUILD_CHANNEL=%s\n" "${BUILD_CHANNEL-unset}"; cat /app/build-channel.txt' \
| tee evidence/16-runtime-proof.txt
The first run should use CMD student. The second proves
effective identity and that BUILD_CHANNEL is not automatically a
runtime environment variable even though the build persisted its
value into build-channel.txt.
11. Verify an intentional CMD override
docker run --rm --label devops-academy.lab=ch07-checkpoint "$IMAGE" reviewer \
| tee evidence/17-cmd-override.txt
The output should show name=reviewer while ENTRYPOINT
stays /app/app.sh. This is the behavior the Dockerfile
contract intended.
12. Evidence packet review
Your evidence directory should independently answer:
- Which context, Engine/CLI and Buildx handled the build?
- Which human-readable base tag and immutable repository digest were used?
- What were the Dockerfile and source hashes?
- What did build checks report?
- What was the final local image ID and did a registry digest exist?
- What are Config.User, WorkingDir, Env, Entrypoint and Cmd?
- What effective UID and working directory were observed at runtime?
- Did CMD override behavior match the design?
- Did the fake secret check remain isolated from the real image?
13. Exact cleanup and final verification
docker ps -a --filter label=devops-academy.lab=ch07-checkpoint --format 'table {{.ID}}\t{{.Names}}\t{{.Status}}'
docker rm "$DEFAULT"
docker image rm "$IMAGE"
docker ps -a --filter label=devops-academy.lab=ch07-checkpoint
date -u +%Y-%m-%dT%H:%M:%SZ | tee evidence/18-finished-at.txt
Keep the evidence directory. Do not run broad image/container/system prune commands. The pulled BusyBox base can remain because it was not uniquely created by this chapter and may be shared by other labs.
14. Operational review and Chapter 08 handoff
Chapter 07 established the Dockerfile as a source-controlled contract between build inputs and runtime defaults. You can now separate filesystem-producing instructions from image configuration, use non-root runtime identity, distinguish ARG from ENV, reason about ENTRYPOINT/CMD, and prove base/output identities instead of relying on tags alone.
Chapter 08 narrows the next major uncertainty:
which files and remote sources the build is allowed to
see. You will measure and minimize build contexts, use
.dockerignore, reason about remote and named contexts,
and tie those inputs to deterministic evidence.
Knowledge check
Which evidence proves the checkpoint base content, not merely its human-readable name?
The resolved repository digest reference captured before the build.
Which checkpoint field proves the image's default runtime UID?
Config.User in image inspection, corroborated by
runtime id output.
Why is the fake secret demo in a separate Dockerfile?
It demonstrates detection without contaminating the real checkpoint image or encouraging actual secret embedding.
If RepoDigests is empty for the locally built final image, is the build invalid?
No. A local image can have an image ID/tag without a registry digest. Record the absence honestly; registry publication is a separate state.
What is the next reproducibility boundary after the Dockerfile itself?
The build context and other source inputs: exactly what files or remote revisions BuildKit can read.
Official references and version notes
- Dockerfile reference — current syntax and semantics for FROM, RUN, COPY, ADD, WORKDIR, ENV, ARG, USER, CMD, ENTRYPOINT, parser directives, and instruction options.
- Building best practices — guidance on reusable images, cache-aware instruction order, package installation, pinning, and non-root design.
- Build variables — lifecycle and scope of ARG and ENV.
- Build secrets — secret and SSH mounts for sensitive build-time input.
- Build checks reference — Dockerfile checks including SecretsUsedInArgOrEnv, JSONArgsRecommended, and WorkdirRelativePath.
-
Checking build configuration
— running checks with
docker build --checkand interpreting results. - Docker Engine 29 release notes — current Engine 29 behavior and packaging updates.
- BuildKit releases — current builder/frontend changes that can affect Dockerfile features.
Version-sensitive statements were rechecked against primary
documentation on 2026-09-21. Docker Engine 29.8.1
is the current Engine 29 patch release at this checkpoint. Docker
currently recommends # syntax=docker/dockerfile:1 for
most users when an external stable Dockerfile frontend is desired;
that reference follows the latest stable 1.x frontend rather than
being an immutable artifact. Executable labs therefore record the
learner's actual Engine/CLI/Buildx/BuildKit state and resolve the
application base image to a repository digest before building.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.