Skip to content

Stop deleting runtime env vars when booting from a build-baked snapshot - #1055

Merged
theoephraim merged 1 commit into
mainfrom
fix-baked-env-delete-runtime-vars
Sep 1, 2026
Merged

Stop deleting runtime env vars when booting from a build-baked snapshot#1055
theoephraim merged 1 commit into
mainfrom
fix-baked-env-delete-runtime-vars

Conversation

@theoephraim

Copy link
Copy Markdown
Member

Fixes a destructive silent failure reported from production: a Next.js standalone container passing env vars at boot (docker run -e REDIS_URL=...) had those vars deleted from process.env by varlock at startup, taking the service down.

Replaces #1053, which grew a warning path, a next-env-compat change, and an unrelated vite decrypt fix on top of the actual regression fix. This is the regression fix alone.

Root cause

In a container where the varlock CLI is unreachable, the standalone server falls back to the env blob baked into the bundled runtime at build time. initVarlockEnv treated that blob like a fresh resolution, so items that resolved to undefined at build time (REDIS_URL= in the schema, no value in the build env) triggered the stale-echo cleanup from #1038, deleting the runtime-provided value. That cleanup assumes a resolution happened in this process (a genuine ambient value would have acted as an override and resolved to it), which is false for a blob resolved on a build machine.

The deletion shipped only in varlock@1.17.1; 1.17.0 and earlier are unaffected.

The fix

The injection preludes (nextjs webpack + turbopack, vite resolved-env SSR entry) bake an injectedAtBuild: true flag INSIDE the serialized payload, before encryption where applicable. initVarlockEnv skips the stale-echo cleanup for a flagged blob.

Provenance lives in the payload rather than beside it, so it travels with the blob to child/worker processes and through encryption round-trips, and can never outlive it: any fresh resolution produces an unflagged blob, so no marker clearing is needed anywhere.

What is deliberately unchanged

Baked values stay authoritative for ENV, and a runtime value is still not applied to a baked snapshot (it cannot be validated or coerced against one, since the blob carries no schema info). That silent-ignore predates this bug and exists in every released version. Surfacing or enforcing it is a product change, not a patch fixing a destructive regression, and it belongs with varlock freeze (#1049) where baking becomes explicit and the contract can be enforced honestly.

Consequence worth naming: after this fix a runtime-provided REDIS_URL stays in process.env but ENV.REDIS_URL is still undefined. That split is exactly the 1.17.0 behavior, and restoring it is the point.

Testing

  • Unit coverage for the no-delete fix, blob-only boots, the globalThis (vite) blob path, and unflagged blobs keeping existing fresh-resolution semantics.
  • One framework-test scenario per bundler booting a real standalone build copied outside the project tree with a stripped environment (mimicking a container image), asserting the boot-provided value survives in process.env.

Follow-ups

Land varlock freeze (#1049) as the single explicit pinning mechanism, then rework the integrations so baking is explicit or platform-automatic, and enforce the frozen contract there. injectedAtBuild is a bridge and should collapse into the seal at that point.

…napshot

When a server boots from the env blob baked into build output (e.g. a
Next.js standalone container where the varlock CLI is unreachable),
initVarlockEnv treated the blob like a fresh resolution. Items that
resolved to undefined at build time triggered the stale-echo cleanup
from #1038, DELETING the corresponding runtime-provided values from
process.env. A schema item like `REDIS_URL=` would actively clear a
`docker run -e REDIS_URL=...` value at boot, silently breaking the
service. Shipped only in varlock@1.17.1.

That cleanup assumes a resolution happened in this process (a genuine
ambient value would have acted as an override and resolved to it),
which is false for a blob resolved on a build machine.

The injection preludes (nextjs webpack + turbopack, vite resolved-env
SSR entry) now bake an `injectedAtBuild: true` flag INSIDE the
serialized payload, before encryption where applicable, and
initVarlockEnv skips the cleanup for a flagged blob. Provenance lives
in the payload so it travels with the blob to child processes and
through encryption round-trips, and can never outlive it: any fresh
resolution produces an unflagged blob, so no marker clearing is needed
anywhere.

Otherwise unchanged: baked values stay authoritative for ENV, and
runtime values are still not applied to a baked snapshot (they cannot
be validated or coerced against it). Making that contract explicit and
enforceable belongs with `varlock freeze` (#1049).
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

bumpy-frog

The changes in this PR will be included in the next version bump.

patch Patch releases

  • @varlock/astro-integration 1.4.0 → 1.4.1 (cascade)
  • @varlock/cloudflare-integration 1.5.0 → 1.5.1 (cascade)
  • @varlock/native-helper-darwin 1.17.1 → 1.17.2
  • @varlock/native-helper-linux-arm64 1.17.1 → 1.17.2
  • @varlock/native-helper-linux-x64 1.17.1 → 1.17.2
  • @varlock/native-helper-win32-x64 1.17.1 → 1.17.2
  • @varlock/nextjs-integration 1.2.1 → 1.2.2
  • @varlock/nuxt-integration 0.1.0 → 0.1.1 (cascade)
  • @varlock/vite-integration 1.5.0 → 1.5.1
  • varlock 1.17.1 → 1.17.2

Bump files in this PR

Click here if you want to add another bump file to this PR


This comment is maintained by bumpy.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle size

⚠️ grows the bundle by 2.7 KB (+0.1%)

Metric main This PR Δ
Total dist 4289.5 KB 4292.2 KB +2.7 KB (+0.1%)
JS 1645.4 KB 1645.6 KB +0.2 KB (+0.0%)
Sourcemaps 2548.4 KB 2549.8 KB +1.4 KB (+0.1%)
Type defs 95.7 KB 96.8 KB +1.1 KB (+1.2%)
Other 0.0 KB 0.0 KB

dist/ only; native binaries are versioned separately and not counted here.

@pullfrog pullfrog Bot left a comment

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.

✅ No new issues found.

Reviewed changes across the build-time snapshot provenance, runtime injection behavior, and regression coverage.

  • Build provenance: Next.js webpack and Turbopack plus Vite resolved-env now mark baked payloads before optional encryption.
  • Runtime preservation: initVarlockEnv keeps runtime-provided values for undefined items in build-baked snapshots while retaining stale-echo cleanup for fresh resolutions.
  • Regression coverage: Unit tests cover flagged and unflagged blobs and both payload transports; framework tests exercise standalone webpack and Turbopack boots outside the project tree.

Pullfrog  | View workflow run | Using azure/gpt-5.6-sol𝕏

@pkg-pr-new

pkg-pr-new Bot commented Sep 1, 2026

Copy link
Copy Markdown

Open in StackBlitz

varlock

npm i https://pkg.pr.new/varlock@1055

@varlock/astro-integration

npm i https://pkg.pr.new/@varlock/astro-integration@1055

@varlock/cloudflare-integration

npm i https://pkg.pr.new/@varlock/cloudflare-integration@1055

@varlock/nextjs-integration

npm i https://pkg.pr.new/@varlock/nextjs-integration@1055

@varlock/nuxt-integration

npm i https://pkg.pr.new/@varlock/nuxt-integration@1055

@varlock/vite-integration

npm i https://pkg.pr.new/@varlock/vite-integration@1055

@varlock/native-helper-darwin

npm i https://pkg.pr.new/@varlock/native-helper-darwin@1055

@varlock/native-helper-linux-arm64

npm i https://pkg.pr.new/@varlock/native-helper-linux-arm64@1055

@varlock/native-helper-linux-x64

npm i https://pkg.pr.new/@varlock/native-helper-linux-x64@1055

@varlock/native-helper-win32-x64

npm i https://pkg.pr.new/@varlock/native-helper-win32-x64@1055

commit: 84b891d

@theoephraim
theoephraim merged commit a5c870f into main Sep 1, 2026
32 checks passed
theoephraim added a commit that referenced this pull request Sep 1, 2026
A frozen file is authoritative: it wins over env supplied at boot, and
process.env is kept in agreement with ENV. That is the most surprising thing
about the feature and the guide never said it, which is a problem because it
is the exact shape that took production down in #1055 (`docker run -e
REDIS_URL=...` against an image whose snapshot lacked the key).

The behavior is correct and deliberate. `injectedAtBuild` exists so an
*implicit* bake preserves runtime env, because those users never asked for a
seal. Freezing is opt-in and its whole promise is a validated unit, so a value
injected afterwards was part of neither resolution.

Tests pin both halves plus a control showing the same ambient value is honored
as a normal override when no seal is present, so a later change can't quietly
give freeze the baked-snapshot semantics.

Also states plainly that freezing is all or nothing today: if some values must
come from the container, freeze is not the right tool for that service yet, and
marking the key @optional to get past the validation refusal is the wrong fix
(it weakens the schema and the seal still clears the operator's value).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant