Docker / Compose and reliable runtime contracts / Lesson 8
Configuration, interpolation, and secret boundaries
Separate values used to render a model, runtime configuration, and build-only credentials.
On this page
Lesson goals
- Explain .env, environment, env_file, runtime secrets, and build secrets.
- Prevent credentials from entering layers, logs, or public configuration output.
A value belongs to a particular stage
Compose interpolation substitutes variables while constructing the model. A .env file can supply interpolation values, but its presence does not automatically inject every variable into a container. environment and env_file define runtime environment variables through their documented precedence rules. Build arguments are a separate build-time channel. Name each value's consumer and lifetime before choosing a mechanism; otherwise a missing runtime value can be misdiagnosed as a networking or image problem.
Keep ordinary configuration explicit
The following model places a nonsecret mode value into the runtime environment and supplies a loopback host-port default. The current fixture does not read PARCEL_MODE; the example teaches value delivery rather than inventing a feature. Quote mappings that contain colons and distinguish a default substitution from a required variable. Review the resolved result with a synthetic environment before relying on an operator's shell history.
services:
api:
image: parcel-api:lab
environment:
PARCEL_MODE: "${PARCEL_MODE:-demo}"
ports:
- "127.0.0.1:${HOST_PORT:-18080}:8080"
PARCEL_MODE=demo
Host publication: 127.0.0.1:18080 -> container 8080
When both host variables are absent, the model supplies mode demo and host port 18080. This is a logical substitution result, not a claimed Compose run. A variable present in the operator environment may change the outcome, so record inputs or require explicit values for important deployment choices.
Do not bake credentials into images
ARG and ENV are unsuitable for secrets because values can persist in image metadata, history, provenance, or resulting files. BuildKit secret mounts expose a supplied credential only to a selected RUN instruction. The process using it must still avoid copying it into output, logs, or artifacts. Secret mount availability is not permission to transmit credentials to arbitrary endpoints; credential scope and destination trust remain part of the build policy.
# syntax=docker/dockerfile:1
FROM python:3.13-slim
RUN --mount=type=secret,id=repo_token,required=true \
test -s /run/secrets/repo_token
This standalone instructional Dockerfile requires a nonempty secret named repo_token supplied through BuildKit. The test produces no normal output and does not copy the file into an image layer. It does not fetch dependencies or reveal a token. Missing input should fail rather than fall back to an embedded credential.
Runtime secrets need a delivery contract
Compose secrets can grant selected services access to file-backed material, commonly under /run/secrets. Ordinary local Compose secret files remain host-managed files; this does not imply an encrypted external secret manager or identical Swarm behavior. The application must read the supplied path, permissions must allow its runtime identity, and rotation needs a documented reload or replacement behavior. Do not assume changing a host file causes every application to reload credentials safely.
Review observability and caches for leaks
Rendered configuration, shell tracing, exception messages, image history, and build logs can all expose secrets. A successful secret mount prevents one persistence path, not every leak. Cache keys do not automatically track secret-value changes, so a credential rotation may require a separate nonsecret cache invalidation input when the build result should change. Redact diagnostic evidence and rotate any credential whose exposure cannot be ruled out through the established incident process.
Common errors and fixes
A variable exists in .env but is absent from the container.
- Why it happens
- Interpolation input was mistaken for automatic runtime environment injection.
- How to fix it
- Declare the intended environment or env_file mapping explicitly.
- Prevent it next time
- Document each variable's consumer, stage, and precedence.
A credential appears in image history.
- Why it happens
- It was passed through a build argument, environment field, or copied file.
- How to fix it
- Rotate the exposed credential and rebuild through a nonpersisting secret channel.
- Prevent it next time
- Review history, logs, and artifact contents using synthetic secret canaries.
A rotated secret does not change the cached build output.
- Why it happens
- Secret contents are not automatically a cache-invalidation signal.
- How to fix it
- Use a separate appropriate nonsecret revision input when output must change.
- Prevent it next time
- Separate credential identity from dependency versioning and cache policy.
Check your understanding
1. Where should a private package token go during image construction?
Show worked solution
Use an authorized BuildKit secret mount only for the instruction that needs it. Configure the package tool to read that file without echoing the value or leaving credential files in copied artifacts. ARG or ENV may expose the token beyond that one build step and should not be used as a substitute.
2. What does the first model resolve to when neither variable exists?
Show worked solution
The mode becomes demo and host port becomes 18080, still bound to 127.0.0.1. That follows the explicit default expressions. It does not prove parcel-api understands the mode or that the host port is available; configuration delivery and application behavior require separate checks.
Key takeaways
- Interpolation, runtime environment, and build secrets are different channels.
- Prevent secret persistence and review secondary leak paths.