Env Files

Environment Files and Variable Interpolation

Before Compose 514 parses compose.yaml, it interpolates every ${...} from the shell environment and from a file named .env in the project directory. The shell wins over .env, and .env wins over defaults written in the file:

Compose interpolation syntax
Syntax Result
${VAR} or $VAR The value, or an empty string with a warning
${VAR:-default} default if VAR is unset or empty
${VAR-default} default only if VAR is unset
${VAR:?message} Stops with message if VAR is unset or empty
${VAR:+other} other if VAR is set and not empty
$$ A literal $

BookNest's defaults make docker compose up work on a fresh clone. On this shared machine a .env file moves the ports and renames the project with COMPOSE_PROJECT_NAME, which prefixes every container, network, volume and image, so this chapter's stack runs as l3-booknest beside others. .gitignore and .dockerignore already exclude .env, so it never reaches Git 1,932 or an image. Delete the legacy file, then write .env:

Retiring docker-compose.yml, writing .env and checking the resultYAML
git rm -q docker-compose.yml
printf '%s\n' COMPOSE_PROJECT_NAME=l3-booknest API_PORT=33000 DB_PORT=33432 \
  POSTGRES_PASSWORD=bn-dev-2026 > .env
docker compose config | grep -E '^name|PASSWORD|published'
Output
name: l3-booknest
      PGPASSWORD: bn-dev-2026
        published: "33000"
      POSTGRES_PASSWORD: bn-dev-2026
        published: "33432"

docker compose config prints the file exactly as Compose will use it, which makes it the first debugging step for any interpolation surprise. Do not confuse .env with the env_file: attribute of a service: .env feeds interpolation of the YAML, while env_file: [api.env] loads variables into that container's environment. --env-file prod.env swaps the interpolation file, and ${POSTGRES_PASSWORD:?} would make a missing production password fail loudly.