17.2.2.5 Baked Config Avoidance
A focused guide to Baked Config Avoidance, connecting core concepts with practical Docker and container operations.
Baked config avoidance focuses specifically on the Dockerfile-level mistake of using ENV to set an environment-specific value that becomes permanently fixed into the image itself, as distinct from the broader application-level discipline of config externalization, addressing the precise mechanical difference between ARG, ENV, and a value genuinely supplied only at container run time.
How ENV differs from a runtime-supplied value
An ENV instruction in a Dockerfile sets a value that becomes part of the image itself, present in every container started from it unless explicitly overridden at run time, which means a value intended to differ by environment should never be set this way as its only source:
ENV DATABASE_URL=postgres://prod-db.internal:5432/app
docker run my-api
docker run -e DATABASE_URL=postgres://dev-db.internal:5432/app my-api
The second command does correctly override the baked-in value, since an explicit -e flag at run time takes precedence over an image's own ENV default, but the underlying problem remains: the image's default value is specifically the production database, which means anyone running the image without remembering to override it gets production configuration by default, an easy, dangerous mistake to make, and one that also means the image's own content is not actually environment-agnostic.
ENV is appropriate only for genuine, safe defaults
ENV is appropriate for setting a value that represents a genuinely safe, sensible default applicable across every environment, not for any value that differs meaningfully or dangerously between environments:
ENV LOG_LEVEL=info
ENV PORT=3000
ENV DATABASE_URL=postgres://prod-db.internal:5432/app
ENV API_KEY=sk_live_abc123
The first two examples set reasonable, safe defaults that work correctly regardless of environment and can still be overridden when genuinely needed; the second two bake environment-specific, and in one case sensitive, values directly into the image, which is exactly the pattern to avoid.
ARG versus ENV for build-time-only values
ARG values exist only during the build itself and do not persist into the running container's environment unless explicitly also set as ENV, which makes ARG the correct choice for a value genuinely needed only during the build process, such as selecting a build variant, while never being appropriate for anything the running application needs to read later:
ARG BUILD_ENV=production
RUN if [ "$BUILD_ENV" = "production" ]; then npm run build:prod; else npm run build:dev; fi
This is a legitimate use of ARG, selecting which build script runs, since the value only matters during the build itself and the running container has no need to know which build path was taken; conflating this with ENV for a value the application also needs at runtime is where confusion and accidental baking typically creep in.
Detecting baked configuration through image inspection
Directly inspecting a built image's own ENV instructions surfaces exactly what has been baked into it, which is a useful, direct verification step for confirming no environment-specific value has been set this way:
docker inspect my-api:1.4.2 --format '{{json .Config.Env}}'
["PATH=/usr/local/bin", "LOG_LEVEL=info", "DATABASE_URL=postgres://prod-db.internal:5432/app"]
A value like the DATABASE_URL shown here, baked directly into the image's own configuration, is immediately visible through this kind of inspection and should prompt removing the ENV instruction entirely, relying instead on the value being supplied genuinely only at container run time with no image-level default at all for anything this sensitive or environment-specific.
Removing the unsafe default entirely versus relying on override discipline
For genuinely sensitive or dangerous values, the safer fix is removing any baked-in value entirely, forcing the application to require it explicitly at startup rather than silently falling back to a baked-in default that happens to be production configuration:
# no ENV DATABASE_URL at all
if (!process.env.DATABASE_URL) {
throw new Error('DATABASE_URL must be explicitly provided');
}
This removes the dangerous failure mode entirely: rather than silently defaulting to production configuration if someone forgets to override a baked-in value, the application now fails loudly and immediately if the value was never supplied at all, which is a considerably safer failure mode than a silent, incorrect default.
Auditing existing Dockerfiles for this specific pattern
A targeted review of every ENV instruction across a project's Dockerfiles, specifically asking whether each one represents a genuinely safe, environment-agnostic default versus something that should never have a baked-in value at all, catches this category of issue directly:
grep -n "^ENV" Dockerfile
Reviewing each result individually against the question "would this value be correct and safe in every environment this image might run in" is the concrete test for distinguishing an appropriate ENV default from one that represents accidentally baked, environment-specific configuration.
Common mistakes
- Using
ENVto set a value that genuinely differs or carries different risk between environments, relying entirely on remembering to override it at run time in every context. - Conflating
ARGandENV, usingARGfor a value the running application actually needs to read, whenARGvalues do not persist into the container's runtime environment at all. - Baking a sensitive credential directly into an image through
ENV, leaving it permanently recoverable throughdocker inspectregardless of whether it happens to be overridden at run time in practice. - Not auditing existing Dockerfiles specifically for this pattern, leaving accidentally baked, environment-specific values undiscovered until they cause an actual incident.
- Relying on override discipline rather than removing a dangerous, baked-in default entirely and requiring the application to fail loudly if the value was never genuinely supplied.
Baked config avoidance is a narrow but consequential Dockerfile-level discipline: using ENV only for genuinely safe, environment-agnostic defaults, using ARG only for values needed strictly during the build itself, and removing any baked-in default entirely for sensitive or environment-specific values, forcing an explicit, required runtime value rather than risking a silent, incorrect fallback.