Docker Compose Interpolation Tester

See what Compose fills in. Put the .env values above a line of three dashes and the compose text below it.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Test. Nothing leaves this tab.

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

An unset variable

Becomes an empty string, and the error you get is about the image name

---
image: myapp:${TAG}

An empty value with the dash form

Set but empty counts as set, so the default is skipped

TAG=
---
image: myapp:${TAG-latest}

A required variable

Compose stops with your message rather than starting something wrong

---
image: myapp:${TAG:?TAG must be set}

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Relying on a variable being set without a default or a required marker

    Compose warns and substitutes an empty string, so the failure appears somewhere else and the warning is off-screen by then.

    Instead:Use ${VAR:-default} where a default makes sense and ${VAR:?message} where it does not.

  2. Using ${VAR-default} and finding the default ignored

    Without the colon the default applies only when the variable is unset. An .env line reading VAR= sets it to empty, which counts as set.

    Instead:Use ${VAR:-default}, which treats empty and unset the same.

  3. Expecting .env to populate the container's environment

    It fills in ${ } in the compose file. Nothing from it reaches the container on its own.

    Instead:Add the variable under environment: or point env_file: at the file.

Compose warns about an unset variable and carries on

It does not stop. The variable becomes an empty string, so image: app:${TAG} becomes app: and the error you get is about the image name.

What an empty substitution actually breaks

image: app: fails with a confusing reference error. ports: "${PORT}:80" becomes ":80", which binds a random host port rather than failing. An environment value becomes empty and the application starts with a blank credential. The warning about the unset variable scrolled past several lines above whatever went wrong.

The colon changes what counts as missing

${VAR:-default} uses the default when the variable is unset OR empty. ${VAR-default} uses it only when UNSET, so a variable set to nothing, which is what an .env line reading NAME= gives you, keeps its empty value. That pair is a common source of a default that mysteriously does not apply.

Make it required rather than defaulting it

${VAR:?message} stops Compose with your message instead of continuing with an empty value. For anything where there is no sensible default, that is a better failure than a container that starts wrong.

The .env file feeds the compose file, not the container

Compose reads .env from the project directory and uses it to fill in ${ } while parsing the YAML. Nothing in it reaches the container unless the compose file also passes it through environment: or env_file:. Both files look like lists of environment variables, which is exactly why this is confusing.

$$ passes a literal dollar through

Which is how a shell variable, a cron entry or an awk script survives being written in a compose file. A command that works in a terminal and fails under Compose is usually missing these.

The bare $NAME form works too

And is interpolated identically. The braces are only required when the name is followed by characters that could be part of it.

More docker tools

Docker Container Image Reference Parser Is that a registry or a Docker Hub user? Dockerfile Linter What this image will do to you later docker run to Kubernetes YAML And the flags that have no equivalent Docker Compose to Kubernetes And why depends_on has no equivalent Docker Logging Driver Generator json-file never rotates Dockerfile Non-Root USER Generator Numeric, or Kubernetes rejects it Docker OCI Image Labels Generator One of them actually does something Docker Hub Pull Rate Limit Calculator Counted per IP, not per person Docker Registry Storage Calculator Deleting a tag frees nothing Docker Build Context Analyzer All of it uploads before anything runs Docker Compose to docker run Converter Names stop resolving Docker Compose Version Migrator version: is obsolete now Docker Compose Network and Port Viewer Everything reaches everything Docker Command Explainer Which flags hand over the host Docker BuildKit Cache Mount Generator apt deletes the cache you just mounted Docker BuildKit Secret Mount Generator ARG is in docker history forever Docker .dockerignore Pattern Tester *.log does not match logs/app.log Docker Image Manifest Viewer unknown/unknown is not broken Docker Image Config Viewer Anyone who can pull can read your Env Docker Registry Token Decoder Check the scope before the credentials Docker HEALTHCHECK Generator Docker does nothing when it fails Docker Network Subnet Calculator 31 networks, then it stops Docker History Analyzer The rm did not save anything Docker Run to Compose Converter Some flags have no equivalent Docker CMD and ENTRYPOINT Tester Why docker stop takes ten seconds Docker Port Mapping Tester -p reaches past your firewall Docker -v to --mount Converter -v invents missing directories Docker Restart Policy Simulator The backoff resets after ten seconds Docker Exit Code Explainer 137 is two different problems Docker CPU Limit Converter --cpu-shares limits nothing Docker Memory Limit Calculator -m 512m allows a gigabyte Docker Environment Variable Precedence Tester Your shell does not reach the container Docker Compose Override Merge Previewer An override can add a port, never remove one Dockerfile Multi-Stage Build Analyzer Which stages reach the image Dockerignore Validator It is not .gitignore

Elsewhere on the site