skip to content

Environment & Interpolation

A .env file substitutes ${VAR} while the file is parsed, whereas env_file and environment inject variables into the running container, and the two are read at different times. Interviewers hand you a wrong value and expect you to trace where it came from.

on this pageshow

explore

questions

5

In Docker Compose, what is the difference between the project `.env` file and a service's `env_file` and `environment` attributes?

level: juniorimportance: must knowfreq 66%

answer

  1. two moments, two consumers
  2. file parsing versus container creation
  3. ${VAR} placeholders in compose.yaml
  4. nothing crosses over unless listed

basics

~20 s

The project .env file only feeds ${VAR} substitution while Compose parses compose.yaml. A service's env_file and environment attributes set variables inside its container; nothing in .env reaches a container unless one of them passes it on.

solid answer

~40 s

Compose reads variables at two different moments for two different consumers. When it loads the project, it builds an interpolation environment from your shell plus the `.env` file in the project directory and uses it to replace `${VAR}` placeholders in `compose.yaml` — image tags, published ports, volume paths. That is all `.env` does. A container's environment is set only by the service's `environment` attribute (inline pairs) and its `env_file` attribute (files whose `KEY=value` lines are loaded into the container), and `environment` wins when both set the same key. So an app that works on a laptop because `DB_PASSWORD` sits in `.env` only works because the service also says `DB_PASSWORD: ${DB_PASSWORD}` or lists a file in `env_file`. `docker compose config` shows the resolved result of both.

code

ini · 3 lines
ini
# .env next to compose.yaml, read by Compose for interpolation only
APP_TAG=dev
DB_PASSWORD=local-only-secret

go deeper

for a junior

Recall the two roles: .env fills ${VAR} placeholders in the compose file, while environment and env_file put variables into the container. Be ready to write the one line that passes a .env value through.

for a middle

Explain when each is read: interpolation happens as Compose loads the project, container variables are applied when the container is created, and environment beats env_file for the same key.

for a senior

Show you have debugged the laptop-to-CI gap: a gitignored .env, the blank-string warning, a required env_file missing on the runner, and docker compose config as the proof.

for a principal

Frame the team convention: which values live in a committed example file, which come from the CI job's environment, and how a missing one fails loudly instead of defaulting to blank.

## Two moments, two consumers Docker Compose touches variables at two separate points, and most confusion about `.env` comes from mixing them up: - **Interpolation** happens while Compose *parses* `compose.yaml`. Every `${VAR}` or `$VAR` placeholder in a YAML value is replaced with text before Compose builds its model of the project. The consumer is the **compose file**. - **Container environment** is applied when Compose *creates* a service's container. The consumer is the **process inside the container**. The project `.env` file belongs to the first moment. The `environment` and `env_file` attributes belong to the second. ## The project `.env` file: input to interpolation When you run `docker compose up` without `--env-file`, Compose looks for a file named `.env` in the **project directory** (by default the directory of the first compose file) and loads its `KEY=value` lines into the *interpolation environment*, next to the variables already exported in your shell. - It is read by Compose itself, not by any container. - It controls text such as `image: "api:${APP_TAG}"` or `ports: ["${API_PORT}:8080"]`. - It can also carry Compose's own `COMPOSE_*` settings. - A key in `.env` that nothing in `compose.yaml` refers to, by placeholder or by a bare pass-through key, has no effect unless it is one of Compose's own `COMPOSE_*` settings. ## `environment` and `env_file`: input to the container These are **service attributes** in `compose.yaml`: - `environment` lists variables inline, as a map (`LOG_LEVEL: info`) or a list (`- LOG_LEVEL=info`). - `env_file` names one or more files whose lines are loaded into that service's container. Relative paths are resolved from the compose file's directory, and later files in the list override earlier ones. - When both set the same key, **`environment` wins**. | Mechanism | Read by | When | Reaches the container? | |---|---|---|---| | project `.env` | Compose | while parsing `compose.yaml` | only through a placeholder in `environment` or `env_file` | | `env_file:` | Compose, for one service | when the container is created | yes, every key in the file | | `environment:` | Compose, for one service | when the container is created | yes, every listed key | ## One syntax, two readers The project `.env` and the files named in `env_file` share one line format, and Compose parses both itself — no shell is involved: - One `KEY=value` pair per line; lines starting with `#` and blank lines are ignored. - An optional leading `export ` is accepted, so the same file can also be sourced by a shell script. - Unquoted and double-quoted values are **interpolated**, so a value may reference another variable; single-quoted values are taken **literally**. - An inline comment after an unquoted value needs a space before the `#`; `VAL# text` keeps the `#` as part of the value. Sharing the syntax is what makes the two easy to confuse. The file format says nothing about *who reads the file*: the same lines placed in `.env` shape the compose file, and placed in a file listed under `env_file` they become container variables. ## Passing a `.env` value into a container To get a value from `.env` into the process, reference it from a container-facing attribute: ```yaml services: api: image: "api:${APP_TAG:-dev}" environment: DB_PASSWORD: ${DB_PASSWORD} env_file: - ./api.env ``` Here `APP_TAG` and `DB_PASSWORD` are interpolated from the shell or `.env`; `DB_PASSWORD` then reaches the container because `environment` names it, while `APP_TAG` only shapes the image reference. Everything in `api.env` reaches the container directly. A shorter pass-through form is a bare key, `- DB_PASSWORD`, which takes the value from Compose's environment without the placeholder. ## Laptop-to-CI traps The split explains the classic "works on my machine" failures when the same stack moves to a CI job: 1. **`.env` is gitignored.** The runner has no `.env`, so `${DB_PASSWORD}` resolves to nothing. Compose prints a warning such as `The "DB_PASSWORD" variable is not set. Defaulting to a blank string.` and starts the database with an empty password rather than failing. 2. **`env_file` names a missing file.** Each `env_file` entry is required by default, so a missing file stops Compose with an `env file ... not found` error. Since Compose 2.24 the long syntax `- path: ./api.env` with `required: false` makes the entry optional. 3. **One file, two roles.** Pointing `env_file` at `.env` is legal: the same file then feeds interpolation *and* is copied into the container, which can leak every key, including ones meant only for Compose. ## Checking it `docker compose config` prints the model after interpolation, with each service's `env_file` contents folded into its `environment`. It is the quickest way to see what a container will actually receive.

  • The CI runner has no api.env and the service lists env_file: ./api.env. What happens, and how do you make that file optional?
    Compose fails while loading the project with an `env file ... not found` error, because every `env_file` entry is required by default. Since Compose 2.24 you can write the entry in long form — `path: ./api.env` with `required: false` — and a missing file is skipped silently. The CI job then supplies the values through its own environment, interpolated into `environment` entries.
  • Can a value inside the project .env file reference another variable?
    Yes. Compose interpolates unquoted and double-quoted values in `.env`, so `COMPOSE_DEBUG=${DEV_MODE:-false}` works and a shell variable can feed it. Single-quoted values are taken literally, so `'${OTHER}'` stays exactly as typed.

Interpolation is a mail-merge done before the letter is posted: .env fills the blanks in the template on your desk. Whatever you want the recipient to read has to be written into the letter itself, which is what environment and env_file do.

saying these in an interview costs you the question

  • Every variable in the project .env file is set inside every container automatically.
  • env_file and the project .env file are one mechanism under two names.
  • Relative env_file paths are resolved from the directory where you run the command.
  • When env_file and environment set the same key, the file wins because it loads later.
  • After editing environment, docker compose restart is enough to apply the new values.
open as a page

In a compose.yaml, how do `${VAR:-default}`, `${VAR-default}` and `${VAR:?err}` differ, and when would you use each one?

level: middleimportance: must knowfreq 52%

basics

~20 s

${VAR:-default} uses the default when VAR is unset or empty, ${VAR-default} only when it is unset, and ${VAR:?err} makes Compose stop with err when VAR is unset or empty. Use defaults for tunables and :? for values that must be supplied.

open as a page

How do you use `docker compose config` to trace where a service's wrong variable value came from, and what do `--environment`, `--variables` and `--no-interpolate` add?

level: middleimportance: should knowfreq 42%

basics

~20 s

docker compose config prints the merged, interpolated model, with env_file contents folded into each service's environment. --no-interpolate shows which placeholder feeds a value, --environment lists the variables Compose resolved, and --variables lists every placeholder with its default.

open as a page

With Docker Compose, if LOG_LEVEL is set by `docker compose run -e`, the service's `environment`, its `env_file` and the image's `ENV`, which value does the container process see?

level: seniorimportance: should knowfreq 31%

basics

~20 s

docker compose run -e wins, then the service's environment, then env_file, and the image's ENV only applies when Compose sets nothing for that name. Any ${VAR} inside environment or env_file is interpolated first, from the shell or env files.

open as a page

With Docker Compose, a stack renders one image tag on your laptop and another in CI: which sources feed compose.yaml interpolation, and in what order do they win?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Variables exported in the shell win first. Next come files passed with --env-file, later ones overriding earlier ones. Only when no --env-file is given does Compose read .env from the project directory, which defaults to the first compose file's folder.

open as a page