[turbopack] Rebuild the docker build scripts#91799
Conversation
Failing test suitesCommit: 097c7c4 | About building and testing Next.js
Expand output● next-config-ts-tsconfig-extends-esm › should support tsconfig extends (ESM)
Expand output● instant-nav-panel › should show loading skeleton during SPA navigation after clicking Start ● instant-nav-panel › should auto-open panel on page load when cookie is already set
Expand output● use-params › should work for single dynamic param ● use-params › should work for nested dynamic params ● use-params › should work for catch all params ● use-params › should work for single dynamic param client navigating ● use-params › should work for nested dynamic params client navigating ● use-params › should work on pages router ● use-params › shouldn't rerender host component when prefetching
Expand output● next-config-ts-tsconfig-extends-cjs › should support tsconfig extends (CJS) |
Stats from current PR✅ No significant changes detected📊 All Metrics📖 Metrics GlossaryDev Server Metrics:
Build Metrics:
Change Thresholds:
⚡ Dev Server
📦 Dev Server (Webpack) (Legacy)📦 Dev Server (Webpack)
⚡ Production Builds
📦 Production Builds (Webpack) (Legacy)📦 Production Builds (Webpack)
📦 Bundle SizesBundle Sizes⚡ TurbopackClient Main Bundles
Server Middleware
Build DetailsBuild Manifests
📦 WebpackClient Main Bundles
Polyfills
Pages
Server Edge SSR
Middleware
Build DetailsBuild Manifests
Build Cache
🔄 Shared (bundler-independent)Runtimes
📎 Tarball URL |
Merging this PR will improve performance by 3.99%
Performance Changes
Comparing Footnotes
|
a2ecffd to
c2ba839
Compare
This stack of pull requests is managed by Graphite. Learn more about stacking. |
| // @ts-check | ||
| // |
There was a problem hiding this comment.
We use https://npmx.dev/package/tsx to call scripts/pack-next.ts. We could potentially do the same here.
We'll be able to drop tsx in Next 17 when we drop Node 20, since Node 22 supports --experimental-strip-types.
There was a problem hiding this comment.
I made the change and then realized that the CI doesn't have tsx installed :/ I can revisit easily later on.
| const { execSync } = require('child_process') | ||
| const path = require('path') | ||
| const fs = require('fs') | ||
| const os = require('os') |
There was a problem hiding this comment.
This could be an .mjs file (or .ts as mentioned above) and use ESM imports. I don't think we have a good reason anymore to use cjs in scripts.
| execSync( | ||
| `docker build -t ${IMAGE_NAME} -f ${path.join(REPO_ROOT, 'scripts/native-builder.Dockerfile')} ${ctx}`, | ||
| { stdio: 'inherit' } | ||
| ) |
There was a problem hiding this comment.
- Nit: Prefer
spawnSyncoverexecSyncto avoid the extra shell and possible shell escaping issues. - Other scripts use
execa(it's a really old version though), so you could use that if you prefer. https://npmx.dev/package/execa/v/2.0.3
41d1fb8 to
5ebf44d
Compare
…e-prepends sysroot in linker scripts)
- Add --icf=all to Windows config (already uses rust-lld) - Replace multi-stage node:20-slim copy with nodesource apt repo install - Simplify Dockerfile (single stage, no COPY --from)
- Add rust-lld linker and --icf=all for macOS targets in .cargo/config.toml - Replace manual switch/case arg parsing with Node.js util.parseArgs (zero dependencies, available since Node 16.17)
5ebf44d to
097c7c4
Compare

Why
The native binary build system uses 3 different napi-rs Docker images that are opaque, can't be customized, and require runtime toolchain downloads. Each CI run downloads compilers and sysroots from scratch, wasting ~5 minutes per build. There's no way to customize the build environment or share toolchains across the 4 Linux targets.
Some smoke tests are added to ensure that node.js can actually load the binaries we build - we don't actually perform much beyond that, just making sure it survives the dynamic linker and correctly exposes the node APIs.
We also are able to enable
--icf=all. This appears to (consistently) chop off about ~1-2% of the binary size.What
Replaces the 3 napi-rs Docker images with a single custom
next-swc-builderDockerfile based on Ubuntu 20.04 that can cross-compile all 4 Linux targets (x86_64/aarch64 × gnu/musl) from either an x86_64 or aarch64 host. All toolchains are baked into the image.Key changes:
docker/native-builder.Dockerfile: Ubuntu 20.04 with clang/lld, GNU cross-sysroots viacrossbuild-essential, musl sysroots from musl.cc, Node.js 20, pinned Rust nightly, andcargo-rustflagsfor flag resolutionscripts/docker-native-build.sh: Inner build script that constructs RUSTFLAGS viacargo rustflags --config(merges.cargo/config.tomlwith cross-compilation overrides), sets up CC/CXX per target, and runs the napi buildscripts/docker-native-build.js: Node.js local build orchestrator replacing the bash wrapper, with--quick,--rebuild,--testflags and smoke testing viagetTargetTriple()scripts/docker-image-cache.js: Turbo-cached Docker image (save/load with--loadfor cache hit handling).cargo/config.toml: Added muslcrt-staticflag, removed cross-compilation flags (now in build script)build-docker-imageturbo task, simplifiedbuild_and_deploy.ymlHow
All 4 Linux targets use clang as the C compiler + linker driver with
gnu-lld-ccflavor (rust-lld does actual linking). Cross-compilation uses--target+--sysroot:crossbuild-essential-{amd64,arm64}--gcc-toolchainlimitation)RUSTFLAGS are resolved inside the container via
cargo rustflagswhich merges.cargo/config.toml(base flags like-Zunstable-options, muslcrt-static) with per-target cross flags passed as--configinline TOML.The Docker image is cached via turbo's remote cache (~885 MB tar). On cache hit,
docker loadrestores it in seconds. On miss, the full image build takes ~2 minutes.glibc compatibility
Building on Ubuntu 20.04 (glibc 2.31) ensures broad compatibility. The table below shows the minimum glibc version required by major Linux distributions (source):