Small CLI lint for Docker Compose files that use configs.*.content.
It catches a subtle mistake: $POSTGRES_PASSWORD inside a Compose configs content block is interpolated by Compose before the container starts. If the generated file should keep a runtime environment reference, use $$POSTGRES_PASSWORD.
configs:
roles:
content: |
\set pgpass `echo "$POSTGRES_PASSWORD"`The generated config may receive a blank or host-side value. Prefer this when the target file should read the variable inside the container:
configs:
roles:
content: |
\set pgpass `echo "$$POSTGRES_PASSWORD"`npm install
npm run validate
npx tsx src/cli.ts compose.yml
npx tsx src/cli.ts --json compose.ymlTry the fixtures:
npx tsx src/cli.ts examples/bad-compose.yml
npx tsx src/cli.ts examples/good-compose.ymlOutput:
compose.yml:12:27 Compose will interpolate $POSTGRES_PASSWORD inside configs.roles.content before the container starts.
config: roles
suggestion: Use $$POSTGRES_PASSWORD when the generated file should keep the runtime environment reference.
JSON output:
[
{
"filePath": "compose.yml",
"configName": "roles",
"variable": "$POSTGRES_PASSWORD",
"line": 12,
"column": 27,
"message": "Compose will interpolate $POSTGRES_PASSWORD inside configs.roles.content before the container starts.",
"suggestion": "Use $$POSTGRES_PASSWORD when the generated file should keep the runtime environment reference."
}
]- top-level
configs - each config with a
contentvalue - unescaped
$VARand${VAR}references inside that content - escaped
$$VARreferences are allowed
Docker Compose interpolates variables while it reads the Compose file. That is useful for values that should come from the host shell or a local .env file, but it is surprising when configs.*.content is used to generate a file that should read environment variables later inside the container.
These values are host-side Compose interpolation and should be treated as suspicious inside configs.*.content:
configs:
app-config:
content: |
token=$APP_TOKEN
database=${DATABASE_URL}
fallback=${APP_MODE:-development}If the generated file should keep the literal runtime reference, escape the dollar sign with $$:
configs:
app-config:
content: |
token=$$APP_TOKEN
database=$${DATABASE_URL}Use unescaped $VAR only when the Compose host should resolve the value before the container is created. Use $$VAR when the generated config file, shell script, SQL file, or entrypoint fragment should resolve the value inside the running container.
This is a narrow maintenance utility for self-hosted Docker and Coolify workflows. It is intentionally small, testable, and safe to run in CI before publishing Compose snippets in docs.
MIT