Skip to content

fix: preserve ${VAR} interpolation in .env values - #5197

Merged
Siumauricio merged 2 commits into
canaryfrom
fix/env-file-var-interpolation-5151
Aug 27, 2026
Merged

Siumauricio merged 2 commits into
canaryfrom
fix/env-file-var-interpolation-5151

Conversation

@Siumauricio

@Siumauricio Siumauricio commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #5151

prepareEnvironmentVariablesForFile unconditionally escaped every $ before writing .env, which broke Docker Compose's documented .env variable interpolation (ASSET_URL="${APP_URL}" never resolved). Now only escapes a $ that isn't part of a ${IDENTIFIER} reference, so braced variable references still interpolate while literal $ (e.g. in passwords) stays escaped as before.

Before

APP_URL=https://example.com
ASSET_URL="${APP_URL}"

written to .env as ASSET_URL="\${APP_URL}" → app receives literal ${APP_URL}.

After
ASSET_URL="${APP_URL}" is written unescaped → Compose interpolates it to https://example.com, matching plain docker compose behavior.

Added a regression case to env-file-literals.test.ts covering the ${VAR} reference alongside the existing literal-$ cases (e.g. pa$$word), confirming both keep working.

Greptile Summary

This PR changes Compose .env serialization so simple ${VAR} references remain available for Docker Compose interpolation while other dollar signs remain escaped.

  • Adds a Docker Compose integration regression case for ${APP_URL}.
  • Replaces unconditional dollar escaping with an identifier-specific negative lookahead.
  • The new rule omits Compose parameter-expansion forms and removes the prior ability to preserve literal ${VAR} text.

Confidence Score: 3/5

The PR should not merge until Compose parameter-expansion syntax and an explicit way to preserve literal ${VAR} values are handled.

The new regex silently changes valid environment values in two reachable cases: supported Compose expressions containing operators remain escaped, while literal simple references become active interpolation.

Files Needing Attention: packages/server/src/utils/docker/utils.ts and apps/dokploy/test/compose/env-file-literals.test.ts

Reviews (1): Last reviewed commit: "fix: preserve ${VAR} interpolation in .e..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

Context used:

Escaping in prepareEnvironmentVariablesForFile escaped every $,
including inside a deliberate ${VAR} reference, breaking Compose's
documented .env variable interpolation. Only escape $ that isn't
part of a ${IDENTIFIER} sequence.

Fixes #5151
@dosubot dosubot Bot added the size:XS This PR changes 0-9 lines, ignoring generated files. label Aug 27, 2026
.replace(/\\/g, "\\\\")
.replace(/"/g, '\\"')
.replace(/\$/g, "\\$");
.replace(/\$(?!\{[A-Za-z_][A-Za-z0-9_]*\})/g, "\\$");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Parameter expansion stays escaped

When a Compose environment value uses parameter expansion such as ${DB_HOST:-localhost}, this regex escapes its dollar sign because the expression is not exactly ${IDENTIFIER}, preventing Compose from applying the fallback and causing the deployed service to receive a literal or otherwise incorrect value.

Knowledge Base Used: Build and Compose workflows

.replace(/\\/g, "\\\\")
.replace(/"/g, '\\"')
.replace(/\$/g, "\\$");
.replace(/\$(?!\{[A-Za-z_][A-Za-z0-9_]*\})/g, "\\$");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Literal references become interpolation

When a password, template, command, or configuration is intended to contain literal ${VAR} text, this rule leaves the dollar sign unescaped with no way to distinguish that intent, causing Compose to substitute or remove the reference and deliver a changed value to the container.

Knowledge Base Used: Build and Compose workflows

@Siumauricio

Copy link
Copy Markdown
Contributor Author

Good catch @greptile-apps — fixed in b67519e. Broadened the negative lookahead to cover bash-style parameter expansion (${VAR:-default}, ${VAR-default}, ${VAR:?err}, ${VAR:+alt}) so those forms aren't escaped anymore, while bare $ (e.g. in passwords) still is. Added a Docker-verified test case for ${UNDEFINED_HOST:-localhost}.

@Siumauricio
Siumauricio merged commit 523e8f4 into canary Aug 27, 2026
4 checks passed
@Siumauricio
Siumauricio deleted the fix/env-file-var-interpolation-5151 branch August 27, 2026 06:54
@greptile-apps

greptile-apps Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Thanks — this closes the original gap for the four parameter-expansion operators, and the Docker-backed ${UNDEFINED_HOST:-localhost} regression test is useful.

One issue remains before the serializer is fully correct: the lookahead uses [^{}]*, so it still escapes valid nested Compose expressions such as ${HOST:-${DEFAULT_HOST}}; packages/server/src/utils/docker/utils.ts:551 therefore emits \${HOST:-${DEFAULT_HOST}} instead of leaving the outer expression available for interpolation. The documented $$ escape for a literal ${VAR} also is not preserved as written: input $${VAR} is rewritten to \${VAR}. Please add handling and regression coverage for nested expressions and the literal-reference escape, or explicitly scope this function to non-nested references.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

@imrja8

imrja8 commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Hey @Siumauricio, just letting you know that the env-file-literals.test.ts introduced here is currently causing the test CI check to fail on canary and all synced PRs.

The test spawns a real docker compose run command to verify the .env outputs, but it crashes on GitHub Actions with: failed to connect to the docker API at unix:///var/run/docker.sock. Similar to the monitoring tests, the CI runner environment doesn't seem to have the Docker socket available/accessible during the vitest run.

Maybe we should skip this test on CI or adjust the workflow runner?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XS This PR changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

${VAR} self-reference in .env values no longer interpolates — prepareEnvironmentVariablesForFile escapes every $, including inside ${VAR}

2 participants