KhueApps
Home/DevOps/Fix ENV interpolation error: invalid interpolation format in Compose

Fix ENV interpolation error: invalid interpolation format in Compose

Last updated: October 07, 2025

What this error means

You run docker compose (or docker-compose) and see something like:

  • error: invalid interpolation format for "...": "..."

Compose tried to substitute an environment variable and found a malformed pattern. It scans YAML scalars for $-expressions. Valid forms include:

  • ${VAR}
  • ${VAR:-default}
  • ${VAR-default}
  • ${VAR?err}
  • ${VAR:?err}
  • $VAR (next char must not be a valid name char)
  • $$ to emit a literal dollar

A variable name must match [A-Za-z_][A-Za-z0-9_]*.

Quickstart (fast fixes)

  1. Pinpoint the line
    • Run: docker compose config
    • The error usually points at the exact field (e.g., services.app.command).
  2. Escape literal dollars
    • If you want the dollar to reach the container shell (e.g., $HOME, $(date)), write $$HOME, $$(date) in compose.
  3. Fix default/required syntax
    • Use ${VAR:-default} (not ${VAR:default}).
    • Use ${VAR:?message} (not ${VAR? message}).
  4. Use valid variable names
    • Replace dots/hyphens: ${app.port} → ${APP_PORT}.
  5. Remove foreign templating
    • ${{ ... }} (GitHub Actions) or $(...) intended for runtime must be escaped as $${{ ... }} or $$(...).
  6. Validate again
    • docker compose config should print the resolved config without errors.

Minimal working example (MWE)

This example shows a previous failure and the corrected compose.

Bad (will error):

services:
  app:
    image: alpine:3.20
    command: ["sh", "-c", "echo env=$ENV:default && echo date=$(date)"]

Error reasons:

  • ${ENV:default} is missing the hyphen: should be ${ENV:-default}.
  • $(date) starts with $, which Compose tries to interpolate; $( is invalid.

Good (fixed):

services:
  app:
    image: alpine:3.20
    environment:
      - PORT=${PORT:-8080}
      - SECRET=literal$$dollar
    command: ["sh", "-c", "echo env=${ENV:-default} && echo date=$$(date) && echo secret=$SECRET && echo port=$PORT"]

Optional .env file (no interpolation happens inside .env):

# .env
ENV=prod
PORT=9090
SECRET=super$ecret

Try it:

# Validate
docker compose config

# Run
docker compose run --rm app

You should see ENV=prod, a printed date, SECRET value including a dollar, and PORT=9090.

Common invalid patterns and fixes

Invalid patternWhy it failsFix
${VAR:default}Missing hyphen in default${VAR:-default}
${VAR? error}Space breaks parser${VAR:?error} or ${VAR?error}
${app.port}Dots not allowed in names${APP_PORT}
${{ SECRET }}Not a Compose syntax$${{ SECRET }} or move templating elsewhere
$(date)Not a var name; starts with $($$(date) to defer to container shell
$Trailing lone dollar$$
${123ABC}Name cannot start with digit${ABC123}

Step-by-step diagnosis

  1. Reproduce the error with details
    • docker compose config or docker compose up will show the field that faulted.
  2. Search for $ in suspect sections
    • command, entrypoint, environment, labels, healthcheck, volumes, and URLs often contain dollars.
  3. Decide intent of each $ occurrence
    • Variable to resolve at compose time: use ${VAR}.
    • Literal dollar to be passed into the container: escape as $$.
    • Shell substitution in the container (e.g., $(date)): escape as $$ so the container’s shell sees a single $.
  4. Validate defaults and required forms
    • Defaults: ${VAR:-value} or ${VAR-value}.
    • Required: ${VAR:?message} or ${VAR?message}.
  5. Sanitize names
    • Only A–Z, a–z, 0–9, and underscore; cannot start with a digit.
  6. Rerun validation
    • docker compose config must succeed before you run up.

Practical patterns

  • URLs with credentials
    • postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/app
  • Passing regex or passwords containing $
    • Put them in .env or env_file, or escape in YAML: SECRET=pa$$word.
  • Commands needing runtime expansion
    • command: ["sh", "-lc", "echo home=$$HOME && echo now=$$(date)"]

Using .env, environment, and env_file correctly

  • .env
    • Automatically loaded from the project directory.
    • Compose does not interpolate values inside .env.
  • environment: in the service
    • Compose substitutes ${...} in the YAML value; also accepts KEY=VALUE pairs.
  • env_file:
    • Points to files with KEY=VALUE lines; values are not interpolated.
    • Good for secrets/values containing $ without needing $$.

Example with env_file:

services:
  api:
    image: alpine
    env_file:
      - app.env
    command: ["sh", "-c", "echo token=$TOKEN"]

app.env:

TOKEN=p@$$w0rd   # no escaping needed here

Pitfalls to avoid

  • Assuming YAML quotes disable substitution: they do not; Compose still interpolates.
  • Mixing Windows %VAR% style with Compose’s $VAR syntax.
  • Accidentally committing ${{ ... }} placeholders from CI/CD into compose.yml.
  • Using hyphens or dots in variable names.
  • Forgetting to escape $ in labels, healthcheck test, or volume strings.

Performance notes

  • Interpolation happens once during config resolution; there’s effectively no runtime cost.
  • Very large compose files or deep includes can slow docker compose config slightly.
  • Prefer env_file or .env for many variables to simplify YAML and reduce parse errors.

Tiny FAQ

  • Q: How do I print a literal $ in container output?
    • A: Write $$ in compose; it becomes a single $ at runtime.
  • Q: Can I stop Compose from substituting in a specific field?
    • A: No field-level toggle; escape dollars or move values to env_file/.env.
  • Q: Does quoting with 'single' or "double" prevent interpolation?
    • A: No. Compose interpolates regardless of YAML quoting.
  • Q: How can I check everything resolves correctly?
    • A: Run docker compose config and inspect the output before up.
  • Q: Is ${VAR:-default} portable across Compose v1/v2?
    • A: Yes; it’s the supported default syntax in both.

Series: Docker

DevOps