https://github.com/vaguul/compose-config-env-lint
CLI lint for Docker Compose configs.content runtime environment interpolation mistakes.
https://github.com/vaguul/compose-config-env-lint
cli compose coolify docker-compose env lint self-hosted typescript vaguul zemiax
Last synced: 24 days ago
JSON representation
CLI lint for Docker Compose configs.content runtime environment interpolation mistakes.
- Host: GitHub
- URL: https://github.com/vaguul/compose-config-env-lint
- Owner: vaguul
- License: mit
- Created: 2026-06-01T18:43:38.000Z (24 days ago)
- Default Branch: main
- Last Pushed: 2026-06-01T18:49:43.000Z (24 days ago)
- Last Synced: 2026-06-01T20:27:31.275Z (24 days ago)
- Topics: cli, compose, coolify, docker-compose, env, lint, self-hosted, typescript, vaguul, zemiax
- Language: TypeScript
- Homepage: https://vaguul.github.io/portfolio/#case-studies
- Size: 15.6 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Security: SECURITY.md
- Roadmap: ROADMAP.md
Awesome Lists containing this project
README
# compose-config-env-lint


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`.
## Example
```yaml
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:
```yaml
configs:
roles:
content: |
\set pgpass `echo "$$POSTGRES_PASSWORD"`
```
## Usage
```bash
npm install
npm run validate
npx tsx src/cli.ts compose.yml
npx tsx src/cli.ts --json compose.yml
```
Try the fixtures:
```bash
npx tsx src/cli.ts examples/bad-compose.yml
npx tsx src/cli.ts examples/good-compose.yml
```
Output:
```text
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:
```json
[
{
"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."
}
]
```
## What It Checks
- top-level `configs`
- each config with a `content` value
- unescaped `$VAR` and `${VAR}` references inside that content
- escaped `$$VAR` references are allowed
## Compose Interpolation Notes
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`:
```yaml
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 `$$`:
```yaml
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.
## Why This Exists
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.
## License
MIT