Environment Variables in Docker: The Sticky Notes That Configure Your Containers
An in-depth, intermediate-level walkthrough of what environment variables are, how Docker uses them to configure containers without rebuilding images, and how to use them well — and know their limits.
Imagine a rental car. The car itself — its engine, its frame, its wheels — is built once, at the factory, the same for every customer. But before you drive off, the rental desk adjusts a few things specifically for your trip: which radio station is preset, what the GPS’s home address is set to, maybe which side mirror settings are saved. None of that requires rebuilding the car from scratch — it’s just a small set of adjustable settings layered on top of a fixed, unchanging vehicle. Environment variables in Docker work exactly like this. The image is the car, built once and shared by everyone. Environment variables are the adjustable settings applied when that car is actually handed over to a specific driver for a specific trip. This post is a deep, intermediate-level exploration of what environment variables are, how Docker handles them, and how to use them thoughtfully.
1What Exactly Is an Environment Variable?
Let’s build a precise, grounded definition before exploring how Docker specifically uses them.
A simple named value available to a running process
An environment variable is a simple piece of named information — a name paired with a value — made available to a running program by the system it’s running on, rather than being written directly into the program’s own code. A program can check the value of a specific environment variable while it runs, and adjust its own behavior based on whatever it finds.
Older and more general than Docker itself
Environment variables aren’t something Docker invented — they’re a long-standing, general concept in computing, used by all sorts of programs on all sorts of systems to receive small pieces of configuration from whatever’s launching them. Docker simply adopts this same familiar mechanism as one of its primary ways of configuring containers.
Think of environment variables like sticky notes left on a new employee’s desk on their first day — “your team’s Slack channel is X,” “your manager’s name is Y.” The employee (the program) doesn’t need to be retrained or rebuilt to know these things; they simply read the notes left for them and adjust accordingly.
An environment variable lets a container’s behavior be adjusted from the outside, at the moment it’s started, without needing to change or rebuild anything about the underlying image itself.
2Why Environment Variables Matter So Much for Containers
This connects directly back to one of the central ideas behind Docker images: build once, run anywhere, unchanged.
The same image, many different situations
As covered in earlier discussions of Docker images, one of the biggest benefits of images is that the exact same, unmodified image can be run in many different contexts — a developer’s laptop, a testing environment, production. But these different contexts often genuinely need different settings: a different database address, a different logging verbosity, different feature toggles.
Avoiding a separate image for every situation
Without a mechanism like environment variables, accommodating these differences would seem to require building an entirely separate image for every single context — a development image, a testing image, a production image — each subtly different, each needing to be separately built and maintained, directly undermining the “build once” benefit images are meant to provide.
Configuration external to the built artifact
Environment variables solve this by keeping configuration entirely separate from the image itself — the image stays exactly the same everywhere, and only the environment variables supplied when a container actually starts differ from one context to another, preserving a single, consistent, well-tested artifact across every environment it runs in.
Different Needs, Different Contexts
Development, testing, and production each need different settings.
Rebuilding Per Environment
Without externalized config, you’d need a separate image for each.
Environment Variables
One image, configured differently at start time for each context.
True “Build Once” Reality
The exact same, tested artifact runs consistently everywhere.
3Environment Variables vs. Baking Configuration Into the Image
Contrasting the two approaches directly makes the practical difference concrete.
Baked-in configuration
Baking a specific setting directly into an image — hardcoding a particular database address or a particular mode into the image’s own build process — means that setting becomes permanently fixed for every single container ever started from that exact image, requiring a full rebuild of the image itself to change it for even one deployment.
Environment-variable-based configuration
Supplying that same setting as an environment variable instead means the image itself makes no assumption about what the value should be — the application is simply written to read whatever value it’s given at runtime. The same image can then be started many times, each time with a different value supplied, without ever needing to be rebuilt.
| Aspect | Baked Into Image | Environment Variable |
|---|---|---|
| Where it’s set | During the image build | When the container starts |
| Changing it later | Requires rebuilding the image | Just start with a different value |
| Same image across environments | Not really possible for that setting | Fully preserved |
| Appropriate for | Genuinely fixed, never-changing values | Anything that varies by context |
Not everything needs to be an environment variable — genuinely fixed values that will never differ between environments can reasonably stay baked into the image; the key is recognizing which settings actually vary.
4Architecture: Where Environment Variables Actually Come From
Environment variables can enter a container from several distinct sources — understanding each one matters for predicting the final result.
Defaults baked into the image
An image’s own build process can define default values for specific environment variables, meant to apply automatically unless something later overrides them — useful for settings that have a sensible, common default but might occasionally need to be adjusted.
Values supplied at container start time
Individual environment variables can be explicitly supplied at the moment a container is started, directly specifying a name and value that should apply to that particular container’s run.
Values loaded from a dedicated environment file
Rather than specifying many individual variables one at a time, a whole collection of environment variables can be defined together in a dedicated file, which is then loaded as a group when the container starts — useful for keeping a larger set of related settings organized together.
Values defined within a multi-container application description
For multi-container applications, environment variables for each service can also be defined as part of that service’s own description within the overall application definition, keeping a service’s configuration directly alongside the rest of its definition.
| Source | Typical Use |
|---|---|
| Image-baked defaults | Sensible, common fallback values |
| Container start-time values | One-off or explicit overrides |
| Environment file | Larger, organized sets of related settings |
| Multi-container app description | Per-service configuration in a shared application definition |
5Internal Working: How Docker Actually Passes Values to a Process
A closer look at what actually happens, mechanically, between “you specify a variable” and “the application can read it.”
Assembling the final set before the process starts
Before a container’s main process actually begins running, Docker gathers every environment variable that applies to it from all the relevant sources described in Chapter 4, resolving any overlaps between them (covered in detail in Chapter 6), and assembles one final, complete set of name-value pairs.
Attaching that set to the process itself
This final, assembled set of environment variables is then attached directly to the container’s main process as it starts, using the same general mechanism any operating system uses to hand environment variables to any program it launches — nothing Docker-specific happens at this level; it’s the same well-established, general-purpose mechanism.
The application reads them like any other program would
From the application’s own point of view, reading these values works exactly as it would for any ordinary program running directly on a regular machine — it simply asks the system for the value associated with a specific variable name, with no awareness needed of where that value ultimately came from.
flowchart LR
A[Image Defaults] --> D[Final Assembled Set]
B[Start-Time Values] --> D
C[Environment File] --> D
D --> E[Attached to Container's Process]
E --> F[Application Reads Values Normally]
6Precedence and Override Order
Because multiple sources can potentially define a value for the same variable name, understanding which one actually wins is essential.
Why conflicts can happen
It’s entirely possible for the same variable name to be given a value in more than one place — perhaps a default baked into the image, and also an explicit override supplied at container start. Docker needs a clear, predictable rule for which of these competing values actually takes effect.
The general precedence pattern
In general, values supplied more explicitly and closer to the actual moment a container starts take precedence over more general, earlier-defined defaults. A value explicitly set at container start time will typically override a same-named default baked into the image, since it represents a more specific, deliberate choice made for that particular run.
Why understanding this order matters
Without a clear mental model of this precedence, it’s easy to be confused by a container that doesn’t seem to be picking up an expected value — often because a different source, higher in the precedence order, is quietly overriding what was set elsewhere.
Assuming an environment file’s values will always apply, without realizing an explicit start-time value for the same variable name will typically take precedence over it.
7The Full Lifecycle of an Environment Variable
Defined at some source
A value is defined for a given variable name at one or more of the sources described in Chapter 4 — an image default, a start-time value, an environment file, or an application description.
Resolved according to precedence
If more than one source defines the same variable name, Docker resolves which value actually applies, following the precedence order from Chapter 6.
Attached to the process at startup
The final, resolved set of environment variables is attached to the container’s main process the moment it starts.
Read by the application during its run
Throughout the container’s lifetime, the running application can read these values whenever it needs to, adjusting its own behavior accordingly.
Fixed for that container’s lifetime
Once a container has started, its environment variables generally remain fixed for that specific container’s entire run — changing a value typically means starting a new container with the updated value, rather than modifying a value inside an already-running one.
Environment variables are resolved once, at container startup — they’re not a live, continuously updated channel for changing a running container’s configuration on the fly.
8Environment Variables Across Multi-Container Applications
In applications built from several containers working together, environment variables take on some additional, useful patterns.
Per-service configuration, kept alongside each service
As mentioned in Chapter 4, each service within a multi-container application description can define its own specific set of environment variables, keeping that service’s configuration clearly scoped to it, rather than mixed together with every other service’s settings.
Sharing common values across services
Some values genuinely need to be consistent across multiple services at once — for instance, several services all needing to know the same shared address for a common dependency. Careful organization of environment variables (often through a shared environment file referenced by multiple services) helps avoid the same value being defined inconsistently in several different places.
Referencing values defined elsewhere
Multi-container application definitions often support referencing a value defined once, elsewhere, within the environment variable values themselves — letting one central definition be reused across multiple services, reducing the risk of the same conceptual value silently drifting apart if it were instead duplicated by hand in several places.
flowchart TD
E[Shared Environment File] --> S1[Service A Config]
E --> S2[Service B Config]
E --> S3[Service C Config]
9Environment Variables and Secrets: Why They’re a Poor Fit
This is one of the most important practical cautions around environment variables, and deserves careful attention.
The tempting convenience
Because environment variables are such a simple, universally supported way to pass configuration into a container, it’s tempting to use them for absolutely everything a container needs to know — including passwords, API keys, and other genuinely sensitive credentials.
Why this convenience comes with real risk
Environment variables are, by design, relatively easy to inspect — visible through various inspection commands, often captured in logs or error messages if an application isn’t careful, and sometimes even visible to other processes under certain conditions. None of this is a flaw in environment variables themselves; it’s simply a consequence of them being designed for ordinary, non-sensitive configuration, not for securely handling secrets.
Purpose-built alternatives exist for a reason
Dedicated secret-management mechanisms exist specifically to address these gaps, offering more careful handling, more restricted visibility, and better auditability than environment variables were ever designed to provide. Reaching for a purpose-built secrets mechanism, rather than an environment variable, for genuinely sensitive values is a meaningfully safer default.
Passing database passwords, API keys, or other sensitive credentials as ordinary environment variables purely out of convenience, without considering a purpose-built secrets mechanism instead.
10Build-Time vs. Run-Time Environment Variables
A subtle but important distinction exists between environment variables used while an image is being built versus those used once a container is actually running.
Build-time values, used only during the build itself
Some values are needed only temporarily, during the image’s own build process — perhaps to select a particular version of something to install, or to configure some aspect of how the build itself proceeds. These values can be supplied specifically for use during the build, without necessarily persisting into the final image or any container started from it.
Run-time values, used by the actual running application
Separately, run-time environment variables are the ones actually read by the application itself while it’s running as a container — the ordinary, everyday use case discussed throughout most of this post.
Why the distinction matters
Confusing these two categories can lead to accidentally leaving build-time-only values (which might include something sensitive, like credentials used solely to fetch a private dependency during the build) unintentionally present in the final image, or conversely, expecting a value only ever supplied at container run time to somehow already be available during the build process, when it was never part of that context at all.
11Advantages and Trade-offs
Advantages
- Preserves the “build once, run anywhere” benefit of images by externalizing what genuinely varies by context.
- Simple, universally understood mechanism supported by essentially every programming environment.
- Easy to override for a specific run without needing to modify or rebuild anything.
- Works cleanly across multi-container applications, with per-service and shared configuration patterns.
- Doesn’t require any special tooling or infrastructure to use at a basic level.
Disadvantages / Trade-offs
- Poorly suited to sensitive data, despite how tempting and convenient that use can seem.
- Values are fixed once a container starts, not a mechanism for dynamic, live reconfiguration.
- Overlapping sources and precedence rules can cause confusion if not clearly understood.
- Large or complex configuration is sometimes awkwardly represented as flat, simple name-value pairs.
12Security Considerations
Beyond the specific secrets caution in Chapter 9, environment variables carry a few other security-relevant considerations worth understanding.
Visibility to anyone who can inspect the container
Anyone with sufficient access to inspect a running (or even stopped) container can typically see its environment variables, which means anything genuinely sensitive placed there is effectively visible to that same broad set of people — an important consideration when deciding who should have that level of access in the first place.
Accidental exposure through logs and error output
Applications that log their own startup configuration, or that produce error messages including environment details for debugging purposes, can inadvertently leak sensitive values placed in environment variables into logs — a much wider and harder-to-control exposure path than the variables’ original, intended use.
Environment files deserve careful handling too
A dedicated environment file, especially one containing anything sensitive, needs the same careful handling as any other file holding sensitive material — appropriate access restrictions, careful consideration of whether it should be included in version control, and awareness of who can read it.
Committing an environment file containing sensitive values directly into a shared code repository, making it visible to everyone with access to that repository’s history.
13Monitoring, Logging & Metrics
Environment variables benefit from some dedicated visibility too, particularly around configuration correctness and drift.
Auditing effective configuration
Periodically reviewing what environment variables a container is actually running with — the final, resolved set after precedence has been applied — helps confirm the running configuration genuinely matches what was intended, especially in setups with several overlapping sources.
Detecting configuration drift across environments
Comparing the environment variables used across different environments (development, testing, production) for the same underlying image helps surface unintended, undocumented differences that might otherwise explain confusing behavioral differences between those environments.
Watching for sensitive values in the wrong places
Scanning logs, error outputs, and environment files for accidentally-exposed sensitive values that should have been handled through a dedicated secrets mechanism instead is a valuable, ongoing security check, not just a one-time review.
14Design Patterns and Anti-Patterns
Good pattern — externalize genuinely variable settings, keep truly fixed ones baked in
Thoughtfully distinguishing between settings that genuinely differ across environments (good candidates for environment variables) and settings that will realistically never change (reasonable to leave baked into the image) keeps configuration clear and appropriately scoped, rather than externalizing everything indiscriminately.
Good pattern — sensible, safe defaults with explicit overrides
Providing a sensible default value baked into the image for non-sensitive settings, while still allowing that default to be explicitly overridden when needed, offers a convenient “it just works” experience out of the box without sacrificing the flexibility to adjust it for specific contexts.
Problem
Using ordinary environment variables to pass genuinely sensitive credentials, purely for convenience.
Why It’s Harmful
Environment variables are relatively easy to inspect and can end up inadvertently exposed through logs or error output, offering meaningfully weaker protection than mechanisms specifically designed for handling secrets.
Correct Approach
Reach for a purpose-built secrets mechanism for genuinely sensitive values, reserving ordinary environment variables for non-sensitive configuration.
Problem
Defining the same conceptual value separately, in slightly different places, across multiple services or environments, instead of referencing one shared, central definition.
Why It’s Harmful
Independently duplicated values tend to silently drift apart over time as one copy is updated without the others being remembered, leading to confusing, inconsistent behavior that’s hard to trace back to its source.
Correct Approach
Where a value genuinely needs to be consistent across multiple places, define it once and reference that single source, rather than duplicating it independently.
15Best Practices and Common Mistakes
Externalize what genuinely varies
Keep truly fixed values baked in; externalize the rest.
Keep secrets out entirely
Use a purpose-built mechanism for sensitive credentials.
Understand precedence clearly
Know which source wins when values overlap.
Reference shared values once
Avoid independently duplicating the same conceptual value.
Committing sensitive env files
Accidentally exposing secrets through shared version control.
Confusing build-time and run-time
Mixing up values meant only for the build with values the running app needs.
Expecting live updates
Assuming a running container will pick up a changed variable without restarting.
16Real-World and Industry Examples
Pointing an application at the right dependency
Applications commonly use environment variables to know where to find a database or other dependency they rely on, letting the exact same image connect to a different instance of that dependency depending on which environment it’s running in.
Toggling application behavior
Feature toggles, logging verbosity, and similar behavioral switches are commonly controlled through environment variables, letting the same image behave slightly differently — more verbose logging in development, for instance — without any code change.
Consistent configuration across a multi-container application
Multi-container applications commonly use a shared environment file or centrally referenced values to keep related services consistently configured, reducing the risk of one service quietly drifting out of sync with the rest.
Automated deployment pipelines
Deployment automation frequently supplies environment-specific values at container start time as part of the deployment process itself, letting one single, already-tested image be deployed consistently across multiple distinct environments.
17Frequently Asked Questions
No, that’s precisely the point — a value supplied at container start time, or through an environment file, can be different every time you start a container from the same, unmodified image.
Docker resolves the conflict using a defined precedence order — generally, a value supplied more explicitly and closer to the actual moment the container starts takes priority over more general, earlier-defined defaults.
Not in the ordinary sense. Environment variables are resolved once, when the container starts, and generally remain fixed for that container’s lifetime — changing a value typically means starting a new container with the updated value.
It’s a common but risky practice. Environment variables are relatively easy to inspect and can end up in logs or error output. A purpose-built secrets mechanism offers meaningfully better protection for genuinely sensitive values like passwords.
A build-time variable is used only while the image is being built, and doesn’t necessarily persist afterward. A run-time variable is read by the actual application once it’s running as a container — the two serve genuinely different purposes and contexts.
18Summary and Key Takeaways
Environment variables are what let a single, unchanged Docker image behave correctly across many different contexts — development, testing, production — without needing to be rebuilt for each one. By externalizing configuration that genuinely varies, while leaving truly fixed values baked into the image, they preserve the core “build once, run anywhere” promise that makes images so valuable in the first place. Understanding where environment variables can come from, how precedence resolves conflicts between overlapping sources, how they behave across multi-container applications, and — critically — why they’re a poor fit for genuinely sensitive data, is what allows them to be used well: as a simple, effective configuration mechanism, applied deliberately rather than as a catch-all for absolutely everything a container might need to know.
Key Takeaways
- Environment variables externalize configuration — keeping the underlying image itself unchanged across contexts.
- They can come from several sources — image defaults, start-time values, environment files, and application descriptions.
- A clear precedence order resolves overlaps — more explicit, start-time values generally override earlier defaults.
- They’re resolved once, at container start — not a mechanism for live, ongoing reconfiguration.
- They’re a poor fit for secrets — relatively easy to inspect, and prone to accidental exposure through logs.
- Build-time and run-time variables serve different purposes — confusing the two can lead to real, sometimes sensitive, mistakes.
- Used deliberately, they preserve “build once, run anywhere” — the exact same, tested image, correctly configured for every environment it runs in.