Reproducible builds: let the draft-mode keys be pinned like BUILD_ID and the server-actions key already can be #98484
alexandrucojocaru-creatopy
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Summary
A production
next buildbakes three randomly generated values into its output. Two of them have supported ways to make a build reproducible; the third does not:BUILD_ID_next/static/<id>/,pages/500.html, standalone servergenerateBuildIdinnext.config.jsserver-reference-manifest.json, every action id in the server chunksNEXT_SERVER_ACTIONS_ENCRYPTION_KEYpreviewModeId,previewModeSigningKey,previewModeEncryptionKey)prerender-manifest.json,required-server-files.json, the edge chunkspackages/next/src/build/preview-key-utils.tsgenerates the draft-mode keys withcrypto.randomBytesand only persists them in<distDir>/cache/.previewinfofor 14 days. A CI checkout has no such cache, andgetStorageDirectorydeliberately returns nothing inside Docker, so every CI build gets fresh keys. WithgenerateBuildIdandNEXT_SERVER_ACTIONS_ENCRYPTION_KEYset, and.next/cacheplus the trace files excluded,prerender-manifest.json(and its copy understandalone/) is the one remaining file that differs between two builds of identical sources (Next 16.2.11, Turbopack,output: 'standalone').Why it matters
Byte-identical builds for identical sources let a deployment pipeline skip rebuilding and redeploying an unchanged app: our CD compares a digest of the docker build context against the last release and reuses the released image on every pull request that did not change the app. Every other service in our monorepo can be reused that way; a Next app cannot, only because of these three keys. It is also what makes two builds diffable at all when hunting a real change.
Proposal
Honour the environment variables the edge runtime already reads these keys from, at build time as well.
packages/next/src/server/web/get-edge-preview-props.tsreads__NEXT_PREVIEW_MODE_ID,__NEXT_PREVIEW_MODE_SIGNING_KEYand__NEXT_PREVIEW_MODE_ENCRYPTION_KEY; ifgeneratePreviewKeysreturned those when all three are set (before consulting the cache or generating random bytes), a build could be pinned with no new configuration surface:A config option in the spirit of
generateBuildId(sayexperimental.generatePreviewKeys) would be equally fine; the environment route has the advantage of matching what the runtime already expects.We run this as a pnpm patch on 16.2.11 and it is exactly what makes our builds byte-identical (verified: two clean builds under one key produce one digest, a third under a different key produces another). Happy to turn it into a PR if the idea is welcome; there is prior art for caring about this in #83501, which fixed output ordering between consecutive builds.
Notes
NEXT_SERVER_ACTIONS_ENCRYPTION_KEYapply: a pinned key must be kept secret and rotated deliberately. Deriving the three draft-mode values from that one secret with HMAC under distinct labels is what we do, so one secret rotates everything together.All reactions