mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
## 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-builder` Dockerfile 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 via `crossbuild-essential`, musl sysroots from
musl.cc, Node.js 20, pinned Rust nightly, and `cargo-rustflags` for flag
resolution
- **`scripts/docker-native-build.sh`**: Inner build script that
constructs RUSTFLAGS via `cargo rustflags --config` (merges
`.cargo/config.toml` with cross-compilation overrides), sets up CC/CXX
per target, and runs the napi build
- **`scripts/docker-native-build.js`**: Node.js local build orchestrator
replacing the bash wrapper, with `--quick`, `--rebuild`, `--test` flags
and smoke testing via `getTargetTriple()`
- **`scripts/docker-image-cache.js`**: Turbo-cached Docker image
(save/load with `--load` for cache hit handling)
- **`.cargo/config.toml`**: Added musl `crt-static` flag, removed
cross-compilation flags (now in build script)
- **CI workflows**: Docker image cached via turbo remote cache,
`build-docker-image` turbo task, simplified `build_and_deploy.yml`
## How
All 4 Linux targets use clang as the C compiler + linker driver with
`gnu-lld-cc` flavor (rust-lld does actual linking). Cross-compilation
uses `--target` + `--sysroot`:
- **GNU targets**: Ubuntu multiarch sysroots from
`crossbuild-essential-{amd64,arm64}`
- **Musl targets**: musl.cc cross-toolchain sysroots with GCC crt files
copied into sysroot lib (works around clang 10's `--gcc-toolchain`
limitation)
RUSTFLAGS are resolved inside the container via `cargo rustflags` which
merges `.cargo/config.toml` (base flags like `-Zunstable-options`, musl
`crt-static`) with per-target cross flags passed as `--config` inline
TOML.
The Docker image is cached via turbo's remote cache (~885 MB tar). On
cache hit, `docker load` restores 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](https://repology.org/project/glibc/versions)):
| Distribution | glibc | Compatible? |
|---|---|---|
| Ubuntu 20.04 (Focal) | 2.31 | ✅ baseline |
| Ubuntu 22.04 (Jammy) | 2.35 | ✅ |
| Ubuntu 24.04 (Noble) | 2.39 | ✅ |
| Debian 11 (Bullseye) | 2.31 | ✅ |
| Debian 12 (Bookworm) | 2.36 | ✅ |
| RHEL / AlmaLinux 8 | 2.28 | ❌ EOL May 2029 (security only) |
| RHEL / AlmaLinux 9 | 2.34 | ✅ |
| Amazon Linux 2 | 2.26 | ❌ EOL June 2026 |
| Amazon Linux 2023 | 2.34 | ✅ |
| Fedora 40+ | 2.39+ | ✅ |
| Arch Linux | 2.41+ | ✅ |
98 lines
4.4 KiB
Docker
98 lines
4.4 KiB
Docker
# Multi-stage Dockerfile for building Next.js native binaries (next-swc).
|
||
#
|
||
# Produces a single image that can cross-compile ALL 4 Linux targets
|
||
# (x86_64/aarch64 × gnu/musl) from either an x86_64 or aarch64 host.
|
||
#
|
||
# Build:
|
||
# docker build -t next-swc-builder:latest -f scripts/native-builder.Dockerfile .
|
||
#
|
||
# The image includes:
|
||
# - Ubuntu 20.04 (glibc 2.31 — broad compatibility baseline)
|
||
# - Clang/LLD for all compilation and linking via --target
|
||
# - GNU cross-sysroots via crossbuild-essential (Ubuntu multiarch)
|
||
# - musl sysroots from musl.cc (headers + libs only; clang/lld do the work)
|
||
# - Node.js 20 (glibc-linked, used as build tool for all targets)
|
||
# - Rust nightly toolchain (pinned to match rust-toolchain.toml)
|
||
# - @napi-rs/cli for building native Node.js addons
|
||
|
||
FROM ubuntu:20.04 AS builder
|
||
|
||
# Avoid interactive prompts during apt-get
|
||
ENV DEBIAN_FRONTEND=noninteractive
|
||
|
||
# Enable multiarch for cross-compilation sysroots.
|
||
# Write sources.list from scratch with explicit [arch=...] tags.
|
||
# On arm64 hosts: native packages from ports, foreign amd64 from archive.
|
||
# On amd64 hosts: native packages from archive, foreign arm64 from ports.
|
||
RUN HOST_ARCH=$(dpkg --print-architecture) && \
|
||
if [ "$HOST_ARCH" = "arm64" ]; then \
|
||
NATIVE_MIRROR="http://ports.ubuntu.com/ubuntu-ports"; FOREIGN_ARCH=amd64; \
|
||
FOREIGN_MIRROR="http://archive.ubuntu.com/ubuntu"; \
|
||
else \
|
||
NATIVE_MIRROR="http://archive.ubuntu.com/ubuntu"; FOREIGN_ARCH=arm64; \
|
||
FOREIGN_MIRROR="http://ports.ubuntu.com/ubuntu-ports"; \
|
||
fi && \
|
||
dpkg --add-architecture "$FOREIGN_ARCH" && \
|
||
printf '%s\n' \
|
||
"deb [arch=${HOST_ARCH}] ${NATIVE_MIRROR} focal main universe" \
|
||
"deb [arch=${HOST_ARCH}] ${NATIVE_MIRROR} focal-updates main universe" \
|
||
"deb [arch=${HOST_ARCH}] ${NATIVE_MIRROR} focal-security main universe" \
|
||
"deb [arch=${FOREIGN_ARCH}] ${FOREIGN_MIRROR} focal main universe" \
|
||
"deb [arch=${FOREIGN_ARCH}] ${FOREIGN_MIRROR} focal-updates main universe" \
|
||
"deb [arch=${FOREIGN_ARCH}] ${FOREIGN_MIRROR} focal-security main universe" \
|
||
> /etc/apt/sources.list
|
||
|
||
# Core build tools + GNU cross-compilation sysroots + Node.js 20 via nodesource.
|
||
# crossbuild-essential installs headers + libs in the multiarch layout
|
||
# that clang finds via --target. Both archs installed so the image
|
||
# works on either host architecture.
|
||
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates && \
|
||
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
|
||
apt-get install -y --no-install-recommends \
|
||
nodejs \
|
||
clang lld llvm pkg-config wget git xz-utils \
|
||
crossbuild-essential-amd64 crossbuild-essential-arm64 \
|
||
&& rm -rf /var/lib/apt/lists/*
|
||
|
||
# Download musl cross-toolchains from musl.cc for their sysroots
|
||
# (headers, crt files, libc, libgcc). Clang + rust-lld handle compilation
|
||
# and linking; we only need the target libraries.
|
||
# Also copy GCC's crt files and libgcc into the sysroot lib dir — clang 10
|
||
# doesn't search the --gcc-toolchain path for these files.
|
||
# https://musl.cc/
|
||
RUN cd /opt && \
|
||
for TRIPLE in aarch64-linux-musl x86_64-linux-musl; do \
|
||
wget -qO- "https://musl.cc/${TRIPLE}-cross.tgz" | tar xz && \
|
||
cp /opt/${TRIPLE}-cross/lib/gcc/${TRIPLE}/*/crt*.o \
|
||
/opt/${TRIPLE}-cross/lib/gcc/${TRIPLE}/*/libgcc.a \
|
||
/opt/${TRIPLE}-cross/${TRIPLE}/lib/; \
|
||
done
|
||
|
||
# Install Rust — pinned nightly from rust-toolchain.toml
|
||
# The COPY of rust-toolchain.toml ensures the image rebuilds when the toolchain changes.
|
||
COPY rust-toolchain.toml /tmp/rust-toolchain.toml
|
||
RUN TOOLCHAIN=$(grep 'channel' /tmp/rust-toolchain.toml | sed 's/.*"\(.*\)".*/\1/') && \
|
||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
|
||
sh -s -- -y --default-toolchain "$TOOLCHAIN" --profile minimal && \
|
||
rm /tmp/rust-toolchain.toml
|
||
|
||
ENV PATH="/root/.cargo/bin:${PATH}"
|
||
|
||
# Add all 4 Linux rustup targets
|
||
RUN rustup target add \
|
||
x86_64-unknown-linux-gnu \
|
||
aarch64-unknown-linux-gnu \
|
||
x86_64-unknown-linux-musl \
|
||
aarch64-unknown-linux-musl
|
||
|
||
# Install @napi-rs/cli and cargo-rustflags globally.
|
||
# cargo-rustflags resolves the effective RUSTFLAGS for a target by querying
|
||
# cargo's own config resolution (handles cfg() predicates, --config overlays).
|
||
RUN npm i -g @napi-rs/[email protected] && \
|
||
cargo install cargo-rustflags
|
||
|
||
# Verify installations
|
||
RUN node --version && rustc --version && napi -h > /dev/null && cargo rustflags --help > /dev/null
|
||
|
||
WORKDIR /build
|