Files
next.js/scripts/native-builder.Dockerfile
T
Matt MastracciandGitHub b163a8bf66 [turbopack] Rebuild the docker build scripts (#91799)
## 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+ | ✅ |
2026-03-24 13:07:37 -06:00

98 lines
4.4 KiB
Docker
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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