An unset variable
Becomes an empty string, and the error you get is about the image name
---
image: myapp:${TAG} See what Compose fills in. Put the .env values above a line of three dashes and the compose text below it.
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.
Nothing else to flag.
No formatting problems, and nothing the rules object to. Worth remembering what that covers: this reads the file you pasted, not the account or cluster it will be applied to.
No finding matches that filter.
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.
Becomes an empty string, and the error you get is about the image name
---
image: myapp:${TAG}Set but empty counts as set, so the default is skipped
TAG=
---
image: myapp:${TAG-latest}Compose stops with your message rather than starting something wrong
---
image: myapp:${TAG:?TAG must be set}These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.
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.
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.
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.
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.
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.
${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.
${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.
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.
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.
And is interpolated identically. The braces are only required when the name is followed by characters that could be part of it.