Chapter 07Lesson 04~110 minutes

Dockerfile Fundamentals: FROM, RUN, COPY, ADD, WORKDIR, ENV, ARG, USER, CMD, and ENTRYPOINT: Diagnostics, Failure Modes, Security, and Performance

A Dockerfile can build successfully and still embed secrets, depend on a moving base, launch the wrong process, run unnecessarily as root, or destroy cache efficiency. This lesson diagnoses those failures from build output and image evidence before applying the smallest corrective change.

DiagnosticsBuild checksSecretsCacheFailure evidence

Learning objectives

  • Preserve build output, Dockerfile text, base identity, image ID, history, and inspect evidence before rebuilding.
  • Diagnose secret leakage, mutable base references, JSON/shell-form mistakes, non-root failures, and cache-hostile instruction order.
  • Use Docker build checks as evidence without confusing lint success with a secure or reproducible image.
  • Explain why deleting a sensitive file in a later layer does not erase it from an earlier layer.
  • Apply the smallest source-controlled correction and prove the resulting image/config change independently.
Chapter 07 evidence baseline — verified 2026-09-21. Mandatory exercises use synthetic source, local disposable images/containers, exact chapter labels/names, and no real credentials. Docker Engine 29.8.1 is the current Engine 29 patch baseline at verification time, but each lab records the learner's actual Engine/CLI/Buildx/BuildKit state. Current Docker documentation recommends # 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. Diagnose the build as an evidence chain

Before changing the Dockerfile, preserve the exact file, builder versions, base reference/digest, build command, build output, image ID, history, and Config fields. If a container fails, also preserve its ID, image ID, command, user, exit code, logs, and runtime overrides. Rebuilding first can replace the evidence needed to explain the failure.

docker context show
docker version
docker buildx version
sha256sum Dockerfile
docker image inspect IMAGE --format '{{.Id}} {{json .Config}}'
docker history --no-trunc IMAGE

2. Failure: secret-like data in ARG, ENV, or copied layers

Use only the synthetic value below. It is intentionally fake.

# BAD DEMO — fake value only
FROM busybox:1.37.0
ARG DEMO_TOKEN=not-a-secret
ENV API_TOKEN=not-a-secret
RUN printf '%s\n' "$DEMO_TOKEN" > /tmp/token.txt
RUN rm -f /tmp/token.txt
CMD ["true"]

Current Docker build checks can flag secret-like ARG/ENV keys. More importantly, removing the file in a later layer does not erase the earlier filesystem content from the image's layer history. The correct repair is architectural: never provide the real secret through ARG/ENV/COPY; use a secret mount only for the RUN instruction that needs it, and ensure the command does not persist or print it.

# Correct pattern when a build command truly needs a secret
RUN --mount=type=secret,id=api_token \
    some-tool --token-file /run/secrets/api_token

3. Failure: a mutable base reference hides input drift

FROM some-image:latest can select different content on two dates. A green rebuild therefore does not prove byte-for-byte source equivalence. Preserve the human tag, resolve and record the digest, and decide explicitly how updates are reviewed. If the image is rebuilt to adopt a patched base, that should create a new artifact identity—not be described as “the same image.”

4. Failure: incorrect CMD/ENTRYPOINT quoting and shell assumptions

Consider a JSON-form command that expects shell expansion:

ENV APP_MODE=prod
CMD ["echo $APP_MODE"]

Exec/JSON form does not invoke a shell to expand $APP_MODE; Docker tries to execute a program literally named echo $APP_MODE. If shell expansion is required, invoke a shell explicitly. If the application can read the environment itself, the stronger pattern is to execute the application directly and let it consume APP_MODE.

Build checks also flag shell-form ENTRYPOINT/CMD patterns because of signal/process implications. A lint warning is a starting hypothesis, not permission to mechanically rewrite commands without understanding behavior.

5. Failure: final process runs as root without need

If docker image inspect IMAGE --format '{{.Config.User}}' is empty, Docker normally uses the image's default, commonly root. Do not “fix” the problem with host permission broadening such as chmod 777. Determine which application paths require reads/writes, set ownership during the build, switch USER, and run a smoke test that exercises those paths.

6. Failure: cache-hostile instruction order

A common pattern copies the entire source tree before installing rarely changing dependencies. Every source edit then invalidates the dependency layer. Preserve --progress=plain output, identify where cache reuse stopped, and reorder inputs only when the application's dependency model supports it. Do not chase cache hits by hiding inputs or using stale package metadata.

7. Failure: package update and install split across cache boundaries

On package managers where metadata update and installation must remain coherent, putting apt-get update in one cached RUN and apt-get install in a later RUN can reuse stale metadata. The safer pattern is a single well-scoped RUN that updates, installs exact intended packages, and removes package-manager cache files when appropriate. The exact command is distro-specific; do not paste Debian guidance into Alpine or vice versa.

8. Controlled broken example: detect, preserve, repair

Create this synthetic Dockerfile in a disposable directory:

# syntax=docker/dockerfile:1
FROM busybox:1.37.0
ENV DEMO_PASSWORD=not-a-secret
WORKDIR relative/path
CMD echo "mode=$DEMO_PASSWORD"
docker build --check -f Dockerfile.broken . 2>&1 | tee broken-check.txt || true

Expected current checks can include secret-like ENV, relative WORKDIR, and JSON-argument guidance. Keep the raw output. Then repair the model rather than merely silencing rules: remove the fake credential pattern entirely, use an absolute workdir, and choose an exec-form application command. Re-run the check and preserve both before/after outputs.

9. Why “delete it later” is not erasure

Image layers are content-addressed build results. If one layer adds a sensitive file and a later layer deletes that pathname, the final merged filesystem may hide the file, but the earlier layer blob can still contain the data. The only safe fix for a real leak is to treat the secret as exposed, rotate it, remove it from build inputs/history, and rebuild clean artifacts from a history that never included the secret.

10. Performance diagnosis without cargo-cult optimization

Measure context transfer, build duration, cache hits/misses, resulting image size, and frequently invalidated steps. A smaller number of Dockerfile lines is not automatically faster. A smaller image is not automatically more secure. Optimize measured bottlenecks while preserving readable ownership of build inputs and runtime configuration.

11. Evidence-first diagnostic sequence

  1. Capture context, Engine/CLI/Buildx state and the exact Dockerfile/source hashes.
  2. Capture the base tag plus resolved digest/platform.
  3. Run docker build --check and preserve output.
  4. Run the build with plain progress and preserve the first failure.
  5. If an image exists, inspect ID, Config, history, and relevant filesystem evidence.
  6. If a container exists, inspect command/user/exit/log state separately.
  7. Change one source hypothesis.
  8. Rebuild and compare identities/evidence; do not delete the original evidence packet.
Next lesson

Next: Checkpoint Lab

Build a final small image whose inputs, configuration, runtime behavior, and cleanup are independently verifiable.

Knowledge check

A fake token copied into one layer and deleted in the next is absent from docker run. Is the image history clean?

Why can CMD ["echo $APP_MODE"] fail?

What should you inspect before changing permissions to fix a non-root failure?

What does a cache miss prove?

Should you delete first-failure build logs after a successful repair?

Official references and version notes

Version and compatibility note

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.