Stop deleting runtime env vars when booting from a build-baked snapshot - #1055
Merged
Conversation
…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).
Contributor
|
The changes in this PR will be included in the next version bump.
|
Contributor
📦 Bundle size
dist/ only; native binaries are versioned separately and not counted here. |
Contributor
There was a problem hiding this comment.
✅ 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-envnow mark baked payloads before optional encryption. - Runtime preservation:
initVarlockEnvkeeps 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.
azure/gpt-5.6-sol | 𝕏
varlock
@varlock/astro-integration
@varlock/cloudflare-integration
@varlock/nextjs-integration
@varlock/nuxt-integration
@varlock/vite-integration
@varlock/native-helper-darwin
@varlock/native-helper-linux-arm64
@varlock/native-helper-linux-x64
@varlock/native-helper-win32-x64
commit: |
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



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 fromprocess.envby 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.
initVarlockEnvtreated 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-envSSR entry) bake aninjectedAtBuild: trueflag INSIDE the serialized payload, before encryption where applicable.initVarlockEnvskips 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 withvarlock freeze(#1049) where baking becomes explicit and the contract can be enforced honestly.Consequence worth naming: after this fix a runtime-provided
REDIS_URLstays inprocess.envbutENV.REDIS_URLis stillundefined. That split is exactly the 1.17.0 behavior, and restoring it is the point.Testing
globalThis(vite) blob path, and unflagged blobs keeping existing fresh-resolution semantics.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.injectedAtBuildis a bridge and should collapse into the seal at that point.