Environment and Configuration
The same driftwood/web image runs in development, staging, and production — what changes between them is configuration, and configuration is passed into the container at run time, never baked into the image. Docker's two mechanisms are environment variables (-e, --env-file) and mounted config files (-v), with secrets being the case you must handle differently from both. The naive approach used in this chapter — a .env file and -e for everything, including the database password — is deliberate, and Chapter 10 replaces it.
Keeping config out of the image is what makes the single-promotable-artifact model from Chapter 1 hold. Bake a value in and you have tied one image to one environment; pass it in and the same bytes run everywhere with different inputs. The skill is knowing which mechanism fits which kind of value — and which value should never travel as either.
Why Config Lives Outside the Image
Baking the database URL or the SECRET_KEY into the image ties that one image to one environment and forces a rebuild to change a single value. Passing config at run time keeps one promotable image and lets the same driftwood/web bytes run anywhere with different -e values pointed at different databases. The image becomes a constant; the environment around it becomes the variable. That is the inversion the whole build-once model depends on.
-e and --env-file
-e DATABASE_URL=… sets one variable; --env-file driftwood.env loads many from a file at once. The app reads them as ordinary environment variables — this is how Flask in Driftwood gets its DATABASE_URL pointing at the db container and its SECRET_KEY in this chapter's naive setup. For more than two or three values, the file is cleaner than a wall of -e flags on the command line.
# development
$ docker run -d --name driftwood-web \
-e DATABASE_URL=postgresql://db:5432/driftwood_dev \
-e LOG_LEVEL=debug \
driftwood/web
# production — same image, an env-file instead of inline flags
$ docker run -d --name driftwood-web \
--env-file ./prod.env \
driftwood/web
Precedence and Image Defaults
A Dockerfile ENV sets a default value baked into the image; an -e at run time overrides it. Values from --env-file and -e combine, with an inline -e winning on a conflict. Knowing this order prevents the "I set it but it didn't take" confusion — and its mirror, "the value I baked into the image is being ignored," which happens precisely because a run-time -e is supposed to win over the image default.
Config Files via Mounts
Some config is too structured for environment variables — the nginx templated config for the Driftwood proxy, a YAML settings file, a TLS certificate. Bind-mounting or using a config mount delivers a whole file into the container at run time, kept out of the image so it can differ per environment. A flat value belongs in an env var; a structured document belongs in a mounted file. Volumes and bind mounts get their full treatment in Chapter 6.
Environment Variables Are the Wrong Place for Secrets
Environment variables leak. They are visible in docker inspect, they bleed into docker history if set with ENV in the Dockerfile, they show up in child-process environments and crash dumps, and they are trivially dumped from a compromised container. Passing the Postgres password with -e POSTGRES_PASSWORD=… is the explicit naive choice this chapter makes so the example runs end to end — and it is wrong for anything real. Chapter 10 replaces it with file-based Docker and Compose secrets and BuildKit build secrets, which never enter the environment or the image history.
-e KEY=val--env-file-e flags once you have more than two or three.inspect and history (Chapter 10).- Environment variables (
-e,--env-file) — for small, flat, non-sensitive values likeLOG_LEVEL, a hostname, a feature flag. Easy to set and read, and visible to anyone who can inspect the container. - Mounted config files (
-v) — for large or structured config like the nginx template or a YAML settings file. Delivered whole at run time and kept out of the image so it varies per environment. - Secrets (passwords, keys, tokens) — belong in neither an env var nor the image. They go through file-based secrets (Chapter 10), because env vars and
ENVlayers both leak them throughinspectandhistory.
- Setting
ENV SECRET_KEY=…in the Dockerfile to "configure" the app — the value is baked into the image, survives indocker history, and ships to anyone who pulls the image; secrets must never be anENVor buildARG(Chapter 10). - Passing the Postgres password with
-e POSTGRES_PASSWORD=…and treating it as private — it's plainly readable indocker inspectand in the process environment of anyone who can reach the daemon; fine as the explicit teaching step here, wrong for production. - Hardcoding the
dbhost orDATABASE_URLinto the image and then needing a rebuild to pointwebat a different database — config that changes per environment must come in at run time, not be compiled in. - Expecting a Dockerfile
ENVdefault to win over a run-time-e—-eoverrides the image default; getting the precedence backwards leads to "the value I baked in is being ignored." - Stuffing a large structured config like the nginx template into a giant
-estring instead of mounting it as a file — env vars are for flat values, files for structured config.
- Pass per-environment configuration at run time with
-e/--env-fileso onedriftwood/webimage runs unchanged in dev, staging, and prod. - Mount structured or large config (the
proxynginx template) as a file rather than encoding it into environment variables. - Keep secrets out of both
ENV/ARGand run-time env vars — use file-based Docker and Compose secrets and BuildKit build secrets (Chapter 10), since env vars leak throughinspect, history, and the process environment. - Set safe, non-sensitive defaults with Dockerfile
ENV(a defaultLOG_LEVEL, a defaultPORT) and override them at run time, so the image is usable out of the box but still configurable.
-e/--env-file and file-mount mechanics
Docker/Compose secrets · BuildKit --secret the file-based secret path (Ch10)
Kubernetes ConfigMaps · Secrets the multi-host generalization of the env-vs-file split (Ch12)
HashiCorp Vault · cloud secret managers external stores backing the production secret case
Knowledge Check
Why is configuration passed into the container at run time rather than baked into the image?
- It keeps one promotable image that runs unchanged everywhere — baking config in ties the image to one environment
- Run-time configuration makes the resulting image significantly smaller and therefore much faster to push and pull
- Environment variables are encrypted in transit to the container while values baked into the image stay plaintext
- A container reads its run-time variables noticeably faster than values that were compiled into the image at build time
A Dockerfile sets ENV LOG_LEVEL=info and you run with -e LOG_LEVEL=debug. Which wins?
- The run-time
-ewins — the app seesdebug, because-eoverrides the image'sENVdefault - The image
ENVwins — a value baked into the image always takes priority over a run-time flag - Both values apply and concatenate — the app receives
info,debugas a single combined value - Neither wins — the conflict between the two causes the container to fail to start with an error
When should structured config like the nginx template go in a mounted file rather than an environment variable?
- Whenever it's large or structured — env vars suit small flat values, files suit whole structured documents
- Never — any config, no matter how large or structured, is always cleaner passed as a single big
-estring - Only when the config happens to contain secrets, since a mounted file is the only secret-safe delivery mechanism
- Only after first rebuilding the image to embed the file, since a mount needs the file baked into the image first
Why are environment variables the wrong place for the Postgres password, and what replaces them?
- They leak through
inspect,history, and child environments — file-based Docker/Compose and BuildKit secrets replace them - They don't persist long enough for the app — the variable is cleared from the environment seconds after the container starts
- A password is simply too long to fit inside an environment variable's value, so it has to be delivered as a file instead
- Environment variable names are case-insensitive, which silently corrupts a mixed-case password value when it is read back
You got correct