Chapter 28Lesson 01~130 minutes

Secrets and Configuration Patterns, Runtime Injection, Environment Risk, Build Secrets, and External Secret Stores: Concepts, Architecture, and Mental Model

Separate configuration from secrets and trace secret data from authorized source through build-time or runtime injection without persisting it in image history, broad environment scope, or logs.

SecretsConfigurationBuildKitRuntime injectionLeast exposure

Learning objectives

  • Separate ordinary configuration, build-time secrets, runtime secrets, and bootstrap identities by lifecycle and exposure.
  • Trace a credential from owner/provider through BuildKit or runtime injection to the consuming process without assuming that “inside a container” means “secret”.
  • Explain why Dockerfile ARG/ENV, copied files, command-line arguments, and debug logs are unsafe default secret channels.
  • Inspect image metadata, container configuration, mounts, logs, and provider evidence without printing a credential.

1. The practical problem: configuration wants visibility; secrets do not

Configuration and secrets both change application behavior, but they have opposite operational needs. A port number, feature flag, or log level is useful when operators can inspect it. A password, private key, signing token, or registry credential should have a named owner, narrow scope, controlled delivery path, rotation policy, and revocation evidence. Treating both as “environment variables” collapses two different trust problems into one broad mechanism.

Earlier chapters established immutable image identity, build contexts, BuildKit, Compose, mounts, least privilege, and user namespaces. Those controls become meaningful here because secret safety depends on where the bytes exist, which process can read them, and which artifact could retain them.

2. Mental model: source → authorized delivery → use → non-persistence

A secret starts outside the image. Its source might be a local test file, a CI secret variable, an SSH agent, a Swarm secret, or an external provider. The source authenticates or authorizes a narrow operation. BuildKit can pass build-only material through a secret or SSH mount for one RUN step. At runtime, a Compose secret-compatible file or external provider can deliver a value only to the service that needs it.

Secret and configuration lifecycle
flowchart TD
  A[Secret or configuration source] --> B{Phase?}
  B -->|Build| C[BuildKit session]
  C --> D[RUN secret or SSH mount]
  D --> E[Build command]
  E --> F[Image result without secret]
  B -->|Runtime| G[Compose file mount or provider client]
  G --> H[Authorized application process]
  H --> I[Use credential]
  I --> J[Logs: no secret value]
            

The secure design decision happens before the value crosses a phase boundary: build credentials should not become runtime state, and runtime credentials should not be baked into an image.

The arrow that matters most is the arrow that does not exist: there should be no normal path from a build secret into final image configuration, and no normal path from a runtime secret into application logs. If a command explicitly copies the mounted secret into a persistent path, Docker cannot magically undo that mistake.

3. State inventory before touching a credential

State Questions Safe evidence
Owner / scope Who issues it? What action/repository/database can it access? Policy name, fake lab owner, provider audit ID; never the value
Phase Build or runtime? Build command/secret ID versus service/mount path
Image Did metadata or a layer retain it? docker image inspect, docker history --no-trunc, controlled filesystem search for the fake value
Container Is it broad environment state or a narrow file/provider read? docker inspect configuration and mounts; do not dump real values
Logs Could app/build/daemon output disclose it? Presence/absence tests using synthetic markers only
Rotation Which version is active, and how is old access revoked? Version ID, creation timestamp, reconcile event, provider revocation record

4. Why ARG, ENV, copied files, and command lines are different risks

ENV is image configuration and remains visible to later containers. A Dockerfile ARG is a build input, not a secret primitive; Docker’s build checks explicitly flag secret-like ARG/ENV usage. Copying a private file into the context creates a second problem: the build now has a normal source path from which layers, cache, debugging tools, or accidental COPY instructions can retain it.

Command-line arguments and logs are also dangerous secret surfaces because process inspection, shell history, CI transcript capture, and monitoring systems can copy them beyond Docker. The safer pattern is to pass a reference or narrow file descriptor/mount while keeping the value out of broad metadata.

5. BuildKit secret and SSH mounts

A BuildKit secret is supplied by the client and mounted only for the relevant build instruction, normally under /run/secrets/<id>. An SSH mount exposes an agent socket or key material for operations such as cloning a private repository. The build command can require the mount so that a missing credential fails clearly rather than silently using a public/anonymous path.

# syntax=docker/dockerfile:1
FROM alpine:3.22.1
RUN --mount=type=secret,id=repo_token,required=true     test -s /run/secrets/repo_token &&     echo "authenticated build step completed" > /build-status

The output file is non-secret evidence that the step ran. Do not write the secret itself, a reusable transformed token, or an authorization header into the image.

6. Runtime file injection and provider boundaries

Compose can grant a secret to selected services and expose it as a file such as /run/secrets/db_password. On Linux, local Compose implements this with a single-file bind mount. That improves per-service scope and avoids broad environment exposure, but it is not an encrypted external secret store and it is not the same mechanism as Swarm secret management.

An external manager introduces another boundary: the workload authenticates using some bootstrap identity, fetches only the secret it is authorized to read, and may cache it for a defined time. The secret manager does not remove the need to prevent logging, control process privileges, protect memory, rotate credentials, and handle provider outages.

7. Read-only baseline inspection

docker version
docker context show
docker info --format 'Security={{json .SecurityOptions}} Root={{.DockerRootDir}}'
docker buildx version
docker buildx inspect --bootstrap
docker compose version

# Safe metadata checks against an existing non-sensitive image/container
# Do not run env-dumping commands against production workloads containing secrets.
docker image inspect alpine:3.22.1 --format 'ImageEnv={{json .Config.Env}}' 2>/dev/null || true

The evidence goal is to identify the component and exposure path, not to render credential values onto your terminal.

8. DevOps contract: a secret has a lifecycle, not just a value

A reproducible secret design records owner, purpose, scope, source/provider, build-versus-runtime phase, consumer, delivery path, rotation version, revocation/cleanup procedure, and known residual exposures. The image digest proves the application artifact; it does not prove that today’s external secret value is correct or still authorized.

Knowledge check

Why is an ordinary environment variable a poor default channel for a runtime secret?

What is the main distinction between a build secret and a runtime secret?

Does a secret mount become an image layer?

Why must “not present in docker history” not be your only leak test?

What does least exposure mean here?

Next lesson

Next: Secrets and Configuration Patterns, Runtime Injection, Environment Risk, Build Secrets, and External Secret Stores: Guided Hands-On Workflow and Core Operations

Continue with the next lesson in the course sequence and carry forward the evidence-first Docker operating model.

Official references and version notes

Version/platform baseline, verified 2026-09-22.

Docker Engine 29.8.1 is the current Engine release; Buildx 0.37.1 and BuildKit 0.33.0 are the current upstream baselines used for feature notes; Compose v5.5.1 is the current Compose release. Docker documents build arguments and Dockerfile environment variables as inappropriate for secrets because sensitive values can persist in image metadata/history, while BuildKit secret/SSH mounts expose credentials only to the build step. Compose secrets are granted per service and delivered as files (on Linux, local Compose uses a single-file bind mount); that is not the same storage/trust model as Swarm secrets or an external secret manager. Labs therefore record the actually installed Engine, Buildx, BuildKit, Compose, context, platform, and secret-delivery path before drawing conclusions.

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
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.