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)
- Pinpoint the line
- Run: docker compose config
- The error usually points at the exact field (e.g., services.app.command).
- Escape literal dollars
- If you want the dollar to reach the container shell (e.g., $HOME, $(date)), write $$HOME, $$(date) in compose.
- Fix default/required syntax
- Use ${VAR:-default} (not ${VAR:default}).
- Use ${VAR:?message} (not ${VAR? message}).
- Use valid variable names
- Replace dots/hyphens: ${app.port} → ${APP_PORT}.
- Remove foreign templating
- ${{ ... }} (GitHub Actions) or $(...) intended for runtime must be escaped as $${{ ... }} or $$(...).
- 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 pattern | Why it fails | Fix |
|---|---|---|
| ${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
- Reproduce the error with details
- docker compose config or docker compose up will show the field that faulted.
- Search for $ in suspect sections
- command, entrypoint, environment, labels, healthcheck, volumes, and URLs often contain dollars.
- 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 $.
- Validate defaults and required forms
- Defaults: ${VAR:-value} or ${VAR-value}.
- Required: ${VAR:?message} or ${VAR?message}.
- Sanitize names
- Only A–Z, a–z, 0–9, and underscore; cannot start with a digit.
- 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.