···33description: >-
44 The missing TLDR for how Compose parses ENV variables and passes them to containers
55author: FoxxMD
66-date: 2025-04-03 09:00:00 -0400
66+date: 2025-04-03 13:00:00 -0400
77categories: [Tips and Tricks]
88tags: [docker, compose, env, environmental variables, interpolation]
99mermaid: true
1010pin: false
1111-published: false
1111+published: true
1212---
13131414+This guide aims to clear up any ambiguity around how Docker Compose handles environmental variables for its own configuration (`compose.yaml`) and providing ENVs to services.
1515+1616+**The key takeaway is that, even though these args/attributes all use the term `env` they are NOT all related to each other: `.env` `--env-file` `env_file:` and `environment:`.**
1717+1818+> **Practical Examples** of the scenarios found below can be found and run from [https://github.com/FoxxMD/compose-env-interpolation-example](https://github.com/FoxxMD/compose-env-interpolation-example)
1919+{: .prompt-tip }
2020+2121+## Compose Up Flow
2222+2323+Let's start at a high level by describing the different steps Compose takes when `up`ing a stack so that we can see where the disconnect between all of these features takes place.
2424+1425```mermaid
1526flowchart TD
1627···19302031 A[[docker compose up]] --> dockerenv
2132 subgraph dockerenv [compose.yaml 'black box']
2222- Par(["Parse <code>.env</code>, <code>--env-file</code> args, and host ENVs"]) --> B("**Interpolates** key-values from previous step into <code>${KEY_LIKE}</code> strings in compose.yaml **file only**")
3333+ Par(["Parse <code>.env</code>, <code>--env-file</code> args, and host ENVs"]) --> B(["**Interpolates** key-values from previous step into <code>${KEY_LIKE}</code> strings in compose.yaml **file only**"])
2334 end
2435 B -->|"None of the parsed key-values in the 'black box' above are _automatically_ available below. To re-use them they must either
2525- 1. have been **interpolated** as string literals into compose file or
2626- 2. be re-defined in attributes mentioned below"| C
3636+ 1 have been **interpolated** as string literals into compose file or
3737+ 2 be re-defined in attributes mentioned below"| C
2738 C[[Start service creation]] --> servicefoo
2839 C --> servicebar
2940 subgraph servicefoo [Service Foo]
···3243 subgraph servicebar [Service Bar]
3344 barenv(["Parses <code>env_file:</code> and <code>environment:</code> attributes in service Bar definition"]) --> barser[[Creates Bar with ENVs from previous step **only**]]
3445 end
3535-```4646+```
4747+4848+Much of the confusion regarding what ENVs are passed to containers stems from a user mistakenly believing that the functionality associated with `.env` and `--env-file` applies to containers, when *by default* it only applies to the literal contents of `compose.yaml`.
4949+5050+As noted in the flow above, all of the ENV key-values we parsed from `.env`/`--env-file` for use in [`compose.yaml` variable interpolation](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation) are not automatically passed to any created services. They can still be used but to do this we must explicitly define this **again** in each our service definitions.
5151+5252+To reiterate **because this is the most important point:** Think of each "parent" box in the flow above as an isolated process that does not know about anything happening in the other boxes. `.env`/`--env-file` apply **only** to the *literal string contents* of `compose.yaml`. Each service cares **only** about its own `env_file:`/`environment:`
5353+5454+## ENV Parsing
5555+5656+Now, we will cover how each "part" of the above flow actually handles the functionality for parsing/interplating ENVs.
5757+5858+### Interpolating into `compose.yaml`
5959+6060+When `docker compose up` is run, docker
6161+6262+* parses host environmental variables and `.env`/`--env-file` into a list of key-values
6363+* these key-values are then used these to [interpolate (subtitute)](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/#interpolation-syntax) `${KEY_LIKE}` strings into values **only in the literal string contents of `compose.yaml`**.
6464+6565+The order of operations for using these two sources of key-values:
6666+6767+* When no `--env-file` arg is given to `compose up` then docker uses a [`.env` file](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/#env-file) located next to `compose.yaml`, if one is present.
6868+* If an [`--env-file`](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/#substitute-with---env-file) arg is given then it uses that **instead of `.env`**
6969+ * Multiple `--env-file` args can be used to provide multiple files
7070+ * To use both `.env` and `--env-file` simply add `--env-file .env`
7171+7272+### Providing ENVs to Containers/Services
7373+7474+In **each service** in `compose.yaml` environmental variables need to be **explicitly defined**. None of the key-values we parsed in the above section are automatically passed.
7575+7676+To define what ENVs the service will have access to we can use:
7777+7878+* `environment:` list with key-values like `MY_KEY: myVal`
7979+* and/or `env_file:` attribute where you can define a file (or list of files) with key-values that are all loaded into the service
8080+8181+Both of these can be used but be aware there is an [order or precedence](https://docs.docker.com/compose/how-tos/environment-variables/envvars-precedence/#how-the-table-works) if the same key is defined more than one place.
8282+8383+Additionally, the same files used for `.env`/`--env-file` can be used for `env_file:` **BUT they must be explicitly defined!** To spell this out:
8484+8585+If you want
8686+8787+```
8888+MY_VAR=xyz
8989+```
9090+{: file='test.env'}
9191+9292+To be available to both `compose.yaml` AND the `serviceA` container defined in the stack then you must:
9393+9494+`docker compose up --env-file test.env` AND
9595+9696+```yaml
9797+services:
9898+ serviceA:
9999+ env_file: test.env
100100+ # ...
101101+```
102102+103103+## Examples
104104+105105+The below examples can all be run using the repository [https://github.com/FoxxMD/compose-env-interpolation-example](https://github.com/FoxxMD/compose-env-interpolation-example) which has all the compose/env files provided.
106106+107107+When a container is run from our sample image (used for the examples below and available in the repository) it runs a single command that prints ENVs available in the container:
108108+109109+```
110110+echo "This is a variable: $FOO $BAR $HARD"
111111+```
112112+113113+### `compose.yaml` with `.env` {#compose-with-dot-env}
114114+115115+Interpolate key-values from `.env` into a compose file. No environmental variables are passed to the service.
116116+117117+<details markdown="1">
118118+119119+```yaml
120120+services:
121121+ test:
122122+ build:
123123+ context: .
124124+```
125125+{: file='compose.yaml'}
126126+127127+```
128128+FOO=aSecret
129129+```
130130+{: file='.env'}
131131+132132+```shell
133133+docker compose up
134134+```
135135+136136+Output is
137137+138138+```
139139+This is a variable:
140140+```
141141+{: file='Container output'}
142142+143143+144144+* Compose file does not define any env attributes for the service so nothing from `.env` is inserted into container
145145+146146+</details>
147147+148148+### `environment:` in service
149149+150150+#### With default `.env`
151151+152152+Interpolate key-values from `.env` into a compose file. Environmentable variables are passed to the service using `environment:` where some come from interpolated `.env`
153153+154154+<details markdown="1">
155155+156156+```yaml
157157+services:
158158+ test:
159159+ build:
160160+ context: .
161161+ environment:
162162+ FOO: ${FOO}
163163+ BAR: ${BAR}
164164+ HARD: alwaysHere
165165+```
166166+{: file='compose-environment.yaml'}
167167+168168+```
169169+FOO=aSecret
170170+```
171171+{: file='.env'}
172172+173173+```shell
174174+docker compose -f compose-environment.yaml up
175175+```
176176+177177+Output is
178178+179179+```
180180+This is a variable: aSecret alwaysHere
181181+```
182182+{: file='Container output'}
183183+184184+185185+* Uses `.env` for Compose file since no other `--env-file` args
186186+* Compose file has `environment:` where...
187187+ * `FOO` (container ENV) is set to `${FOO}` from `.env`
188188+ * `HARD` (container ENV) is hardcoded to `alwaysHere` in compose file
189189+190190+</details>
191191+192192+#### Using explicit `add.env`
193193+194194+Interpolate key-values from `add.env` into a compose file. Environmentable variables are passed to the service using `environment:` where some come from interpolated `add.env`
195195+196196+<details markdown="1">
197197+198198+```yaml
199199+services:
200200+ test:
201201+ build:
202202+ context: .
203203+ environment:
204204+ FOO: ${FOO}
205205+ BAR: ${BAR}
206206+ HARD: alwaysHere
207207+```
208208+{: file='compose-environment.yaml'}
209209+210210+```
211211+BAR=someBar
212212+```
213213+{: file='add.env'}
214214+215215+216216+```shell
217217+docker compose -f compose-environment.yaml --env-file add.env up
218218+```
219219+220220+Output is
221221+222222+```
223223+This is a variable: someBar alwaysHere
224224+```
225225+{: file='Container output'}
226226+227227+* Uses `add.env` for Compose file because of `--env-file` args
228228+ * Does NOT use `.env` because `--env-file` is given
229229+* Compose file has `environment:` where...
230230+ * `BAR` (container ENV) is set to `${BAR}` from `add.env`
231231+ * `HARD` (container ENV) is hardcoded to `alwaysHere` in compose file
232232+233233+</details>
234234+235235+#### Using multiple explicit env files
236236+237237+Interpolate key-values from `add.env` into a compose file. Environmentable variables are passed to the service using `environment:` where some come from interpolated `add.env`
238238+239239+<details markdown="1">
240240+241241+```yaml
242242+services:
243243+ test:
244244+ build:
245245+ context: .
246246+ environment:
247247+ FOO: ${FOO}
248248+ BAR: ${BAR}
249249+ HARD: alwaysHere
250250+```
251251+{: file='compose-environment.yaml'}
252252+253253+```
254254+FOO=aSecret
255255+```
256256+{: file='.env'}
257257+```
258258+BAR=someBar
259259+```
260260+{: file='add.env'}
261261+262262+```shell
263263+docker compose -f compose-environment.yaml --env-file add.env --env-file .env up
264264+```
265265+266266+Output is
267267+268268+```
269269+This is a variable: aSecret someBar alwaysHere
270270+```
271271+{: file='Container output'}
272272+273273+* Uses `add.env` for Compose file because of `--env-file` arg
274274+* Uses `.env` for Compose file because of `--env-file` arg
275275+* Compose file has `environment:` where...
276276+ * `FOO` (container ENV) is set to `${FOO}` from `.env`
277277+ * `BAR` (container ENV) is set to `${BAR}` from `add.env`
278278+ * `HARD` (container ENV) is hardcoded to `alwaysHere` in compose file
279279+280280+</details>
281281+282282+### Using `env_file:` in service {#ex-env-file-in-service}
283283+284284+Uses [`env_file:`](https://docs.docker.com/compose/how-tos/environment-variables/set-environment-variables/#use-the-env_file-attribute) to load all key-values found in the given file into the service's container.
285285+286286+> Compare this to the [`compose.yaml` with `.env` example](#compose-with-dot-env)
287287+{: .prompt-tip }
288288+289289+<details markdown="1">
290290+291291+```yaml
292292+services:
293293+ test:
294294+ build:
295295+ context: .
296296+ env_file: .env
297297+```
298298+{: file='compose-envfile.yaml'}
299299+300300+```
301301+FOO=aSecret
302302+```
303303+{: file='.env'}
304304+305305+```shell
306306+docker compose -f compose-envfile.yaml up
307307+```
308308+309309+Output is
310310+311311+```
312312+This is a variable: aSecret
313313+```
314314+{: file='Container output'}
315315+316316+* Uses `.env` for Compose file since no other `--env-file` args
317317+* Compose file has `env_file:` which inserts everything found in the attribute value (`.env`) into the container
318318+319319+</details>
320320+321321+### Using `.env` for general variable interpolation
322322+323323+This guide and examples have so far focused on using `.env` for interpolating environmental variables into containers but [interpolation can be used *anywhere*](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation) in `compose.yaml`.
324324+325325+This example interpolates `.env` key-values into `${VERSION}` to define what tag to use for a theoretical image.
326326+327327+<details markdown="1">
328328+329329+```yaml
330330+services:
331331+ test:
332332+ image: myFakeImage:${VERSION}
333333+```
334334+{: file='compose.yaml'}
335335+336336+```
337337+VERSION=v1.2.3
338338+```
339339+{: file='.env'}
340340+341341+```shell
342342+docker compose up
343343+```
344344+345345+The **interpolated** `compose.yaml` contents
346346+347347+```yaml
348348+services:
349349+ test:
350350+ image: myFakeImage:v1.2.3
351351+```
352352+{: file='compose.yaml'}
353353+354354+* Uses `.env` for Compose file since no other `--env-file` args
355355+* `${VERSION}` is substituted for `v1.2.3` found in `.env`
356356+ * `VERSION` is NOT passed to the service `test` since no env attributes are defined
357357+358358+</details>
359359+360360+## Komodo and ENVs
361361+362362+> Not familiar with Komodo? Check out my [Introduction to Komodo](../migrating-to-komodo) and [FAQ](../komodo-tips-tricks).
363363+{: .prompt-tip }
364364+365365+[Komodo](https://komo.do/) adds one more layer to this process that occurs **before `docker compose up`**.
366366+367367+Komodo takes the contents of the **Environment** section in your Komodo Stack and:
368368+369369+* Interpolates any [Variables](https://komo.do/docs/variables) into the contents like `[[MY_VAR]]`
370370+* The final content is written to the file specified by **Env File Path** in your Stack (defaults to `.env`)
371371+ * The file specified by Env File Path is the **ONLY** file that Variable interpolation occurs in
372372+373373+After this is done it then runs `docker compose up --env-file .env` and includes any additional files you specified in the **Additional Env Files** section of your stack.
374374+375375+<details markdown="1">
376376+377377+<summary>Variables Example</summary>
378378+379379+Assume you have this [**Variable**](https://komo.do/docs/variables) already in Komodo (in Settings -> Variables)
380380+381381+* `MY_SECRET_1` : `aCoolSecret`
382382+383383+And your **Stack** config looks like this:
384384+385385+```
386386+MY_NORMAL_ENV=abc
387387+MY_API_KEY=[[MY_SECRET_1]]
388388+```
389389+{: file='Stack Environment Section'}
390390+391391+```yaml
392392+services:
393393+ test:
394394+ image: myFakeImage
395395+ environment:
396396+ AN_ENV: ${MY_NORMAL_ENV}
397397+ API: ${MY_API_KEY}
398398+```
399399+{: file='compose.yaml'}
400400+401401+The **interpolated** `compose.yaml` will look like:
402402+403403+404404+```yaml
405405+services:
406406+ test:
407407+ image: myFakeImage
408408+ environment:
409409+ AN_ENV: abc
410410+ API: aCoolSecret
411411+```
412412+{: file='compose.yaml'}
413413+414414+The different names for all the keys in the above example are to help convey that each "section" needs to have keys defined, they aren't automatically passed. Though you CAN pass them from Komodo **Environment** section straight to container by using [`env_file:`](#ex-env-file-in-service) like `env_file: .env`
415415+416416+</details>
417417+418418+<details markdown="1">
419419+420420+<summary>Komodo Compose Up Flow</summary>
421421+422422+```mermaid
423423+flowchart TD
424424+425425+ Z@{ shape: text, label: "Nested Box = Docker action
426426+ Rounded box = ENV-related" }
427427+428428+ Ko[Komodo Stack Deploy] -->|Interpolates Variables into Environment contents| Y[Final Environment Contents]
429429+ Y -->|writes to ENV File Path| A
430430+ A[[docker compose up --env-file .env + Additional Env Files section]] --> dockerenv
431431+ subgraph dockerenv [compose.yaml 'black box']
432432+ Par(["Parse <code>.env</code>, <code>--env-file</code> args, and host ENVs"]) --> B("**Interpolates** key-values from previous step into <code>${KEY_LIKE}</code> strings in compose.yaml **file only**")
433433+ end
434434+ B --> C
435435+ C[[Start service creation]] --> servicefoo
436436+ C --> servicebar
437437+ subgraph servicefoo [Service Foo]
438438+ fooenv(["Parses <code>env_file:</code> and <code>environment:</code> attributes in service Foo definition"]) --> D[[Creates Foo with ENVs from previous step **only**]]
439439+ end
440440+ subgraph servicebar [Service Bar]
441441+ barenv(["Parses <code>env_file:</code> and <code>environment:</code> attributes in service Bar definition"]) --> barser[[Creates Bar with ENVs from previous step **only**]]
442442+ end
443443+```
444444+445445+</details>