ComposeVars
Docker Compose variable checker for VS Code. Catch the blank ${VAR} before docker compose up does.
Docker Compose fills ${VAR} in your compose file from your shell and the project .env
file. When a variable is missing, Compose does not fail - it substitutes an empty string and
prints one easy-to-miss line at runtime:
WARN[0000] The "DB_HOST" variable is not set. Defaulting to a blank string.
The result is an image tag of nginx:, a port mapping of :80, or a database URL with no
host - discovered after the stack is already up. A second, very common confusion is between
the project .env file (used only for interpolation inside the compose file) and
env_file: (variables passed into the container).
ComposeVars checks all of this statically, in the editor, against the files that Compose
itself would read.
Evidence that this bites people:
What it reads
For every docker-compose*.yml, compose*.yaml (including override files such as
compose.override.yaml) and every file pulled in through include: or extends: file::
| Source |
How it is used |
.env next to the compose file (the project directory) |
Interpolation values, as Compose loads them by default |
--env-file <path> and COMPOSE_ENV_FILES=... found in package.json scripts, Makefile or justfile |
Extra interpolation sources for compose files at or below that script's folder |
include: entries |
The included file uses its own folder's .env (or the env_file: given on the include), and the including project's variables take precedence, as documented |
.env.example, .env.sample, .env.template, .env.dist, example.env |
The "documented" set - variables your team expects to be provided |
env_file: paths |
Checked for existence (relative to the compose file) |
Interpolation syntax (as Compose implements it)
Behavior verified against the Compose interpolation reference,
the variable interpolation guide
and the compose-go template implementation.
| Syntax |
Meaning |
ComposeVars |
${VAR} / $VAR |
Value of VAR; blank with a runtime warning if unset |
CV001 if not set anywhere it can see |
${VAR:-default} |
default if VAR is unset or empty |
OK (CV006 if a secret has a default) |
${VAR-default} |
default only if VAR is unset |
OK (CV006 if a secret has a default) |
${VAR:?message} |
Error and stop if unset or empty |
OK - the recommended way to require a variable |
${VAR?message} |
Error and stop if unset |
OK |
${VAR:+alt} / ${VAR+alt} |
alt if set (and non-empty for :+), otherwise blank, no warning |
OK |
${A:-${B:-x}} |
Nested defaults are allowed |
Inner variables are only checked when the outer one would actually fall through |
$$ |
A literal $ (escape) |
Never reported |
$1, $@, $ |
Not a valid variable name - Compose leaves it as-is |
Never reported |
Only YAML values are interpolated, never keys.
Rules
| Code |
Severity |
Bad |
Good |
| CV001 unset variable |
Warning (Information if it is in .env.example but missing from .env) |
image: "app:${TAG}" with no TAG in .env |
TAG=1.4 in .env, or ${TAG:-latest}, or ${TAG:?TAG is required} |
CV002 .env key never used |
Information (in the .env file) |
.env: API_KEY=... and nothing references it |
Use env_file: .env or environment: [API_KEY] if the container needs it - the project .env only feeds ${...} in the compose file |
| CV003 pass-through env not set |
Information |
environment: [DEBUG] or DEBUG: with no value and no DEBUG in .env |
Add DEBUG=... to .env, or give it a value. Compose silently omits it otherwise |
CV004 missing env_file |
Error |
env_file: ./web.env that does not exist |
Create the file, fix the path, or - path: ./web.env + required: false (Compose 2.24+) |
CV005 host-side $VAR in a command |
Warning |
command: sh -c 'echo $HOME' - Compose substitutes the host's HOME when loading the file |
command: sh -c 'echo $$HOME' so the container shell expands it |
| CV006 committed secret default |
Warning |
${DB_PASSWORD:-hunter2} |
${DB_PASSWORD:?DB_PASSWORD is required} |
CV005 applies to command:, entrypoint: and healthcheck.test, including block scalars
(| / >), and only to names that are not defined in any env file you use (those are
clearly meant as Compose variables).
CV006 uses a name pattern (*_PASSWORD, *_SECRET, *_TOKEN, *_KEY and similar,
configurable). The default value is never shown in the message, hover or report.
Quick Fixes
- CV001: make the blank explicit (
${VAR:-}), require it (${VAR:?VAR is required}), or add VAR= to .env.example (created if missing; the value is left empty).
- CV003: add the key to
.env.example.
- CV005: escape as
$$, or add the name to .env.example if it really is a Compose variable.
- CV006: replace the committed default with
${VAR:?VAR is required}.
The same fixes are available from the lightbulb on each row of the ComposeVars view.
Activity bar
- Activity bar view. The ComposeVars icon in the activity bar opens a Findings view: one row per finding, labelled with the rule code and the exact variable (for example
CV001 DB_PASSWORD, CV004 ./web.env), grouped by compose file when there are several, with the rule and line next to it and the full message in the tooltip. Click a row to jump to the line; the lightbulb on a row applies a Quick Fix where one exists. The icon badge shows the number of findings, and the title bar has Show Resolved Variables and Rescan Workspace. Values are never shown in the view.
Hover and report
Hover any ${VAR} or $VAR to see where its value comes from - for example
.env line 4, --env-file (package.json line 3), default in compose file, or
unset - blank unless exported in your shell - and the resulting value. Secret-looking
values are always masked, and so is the password part of any URL value
(postgres://app:****@db/app).
ComposeVars: Show Resolved Variables opens a Markdown table per compose file:
variable, line, source and (masked) value, plus the interpolation sources in precedence
order. The status bar shows the number of findings; click it to open the same report.
- Docker DX (
docker.docker, built on the Docker Language Server) gives Compose files completion, hover, outline, navigation, rename, formatting, inlay hints for overridden values, and document links / path completion for env_file:. Its Compose diagnostics report YAML syntax errors. It does not report unset ${VAR} interpolation, unused .env keys, missing env_file files, or $ escaping.
- Container Tools / Docker extension by Microsoft (Docker Compose Language Service) offers hover docs, completions, formatting and CodeLens; its diagnostics validate YAML only. It does not resolve variables against
.env.
- dclint (Docker Compose Linter) has style, security and best-practice rules (ordering, quotes, ports, image tags, container names). None cover interpolation or env files.
- hadolint lints Dockerfiles, not compose files.
docker compose config resolves variables at the command line and prints the same runtime warning - useful, but only when you run it, and it prints secrets in clear text.
ComposeVars is complementary to all of these: it only does variables and env files, and
it does them in the editor.
Settings
| Setting |
Default |
Description |
composeVars.enable |
true |
Turn diagnostics on or off. |
composeVars.ignoreVariables |
HOME, USER, PATH, PWD, SHELL, LANG, TERM, LOGNAME, TMPDIR |
Variables every shell exports. Not reported by CV001/CV003; still reported by CV005 inside commands. |
composeVars.secretPattern |
(^\|_)(PASSWORD\|PASSWD\|PASS\|SECRET\|TOKEN\|KEY\|APIKEY\|CREDENTIALS?)$ |
Case-insensitive regex for secret-looking names (masked, CV006). |
composeVars.maskAllValues |
false |
Mask every value, not only secret-looking ones. |
composeVars.disabledRules |
[] |
Rule codes to turn off, e.g. ["CV002"]. |
composeVars.scanScripts |
true |
Read package.json, Makefile, justfile for --env-file / COMPOSE_ENV_FILES. |
composeVars.maxFiles |
200 |
Maximum compose files scanned per workspace. |
Commands
- ComposeVars: Show Resolved Variables - Markdown report of every variable, its source and masked value.
- ComposeVars: Rescan Workspace - re-read all compose and env files. Changes are also picked up on save, by a file watcher, and by a light check every 15 seconds while the window is focused (for file systems where watching does not work).
Privacy
Workspace-only file access. ComposeVars only ever reads, checks or edits files inside your open workspace folders. An env_file, include env file, --env-file or COMPOSE_ENV_FILES path that points outside the workspace (for example ../../.aws/credentials or /etc/passwd) is never opened, never probed for existence and never shown, and Quick Fixes never create files outside the workspace. Symlinks are resolved first, so a .env, compose file or folder inside the repository that links to a file outside it is ignored too. FIFOs, devices and files over 2 MB are never read. This holds in untrusted workspaces too.
No network access, no telemetry, no child processes. Everything is read from local files.
Works in untrusted workspaces, because it only reads files. There, a composeVars.secretPattern from the workspace's own settings is ignored, so a repository cannot switch masking off.
Secret-looking values are masked everywhere ComposeVars displays something: hovers, the report and diagnostic messages. Values are never written anywhere; the only edits are the Quick Fixes you choose.
Limitations
- Your shell environment is not visible to an editor extension. A variable you always
export before running Compose will be reported by CV001; add it to composeVars.ignoreVariables or to .env.
-f file lists, COMPOSE_FILE and --project-directory on the command line are not modeled; each compose file uses the .env in its own folder. --env-file values found in scripts are treated as always applied (a union), which can hide a variable that is only set for one script.
- The YAML reader is line-oriented. It handles block maps and sequences, flow lists on one line, comments and block scalars, but not multi-line flow collections or YAML anchors merged across services.
- Variables in
env_file contents are not checked (they are container env, not interpolation).
- Profiles are ignored: a variable used only by an inactive profile is still reported.
License
MIT - included with the extension.
| |