This page documents the Buildkite CI/CD pipeline that builds, tests, and releases Bun across all supported platforms. It covers the pipeline YAML generation script, the build phase breakdown (cpp/rust/link), the agent selection logic, artifact flow, Windows code signing, binary size tracking, and the release upload process.
For information about how the bootstrap scripts provision CI agent machines, see Platform Support and Bootstrap. For the CMake/Ninja build system that each CI step invokes, see CMake Build Configuration. For test runner internals and test reporting output, see Test Runner Architecture and Test Reporting.
The pipeline is defined by two files:
| File | Role |
|---|---|
.buildkite/bootstrap.yml | Static Buildkite YAML; runs node .buildkite/ci.mjs to dynamically upload the full pipeline |
.buildkite/ci.mjs | Generates and uploads the complete pipeline YAML based on build context |
When a build starts, Buildkite runs the bootstrap step defined in `.buildkite/bootstrap.yml`(). That step executes node .buildkite/ci.mjs on a macOS or Linux agent in the build-darwin queue. ci.mjs queries build context (branch, PR status, changed files, secrets) and then calls buildkite-agent pipeline upload with a dynamically generated YAML document.
Sources: .buildkite/bootstrap.yml1-16 .buildkite/ci.mjs1-35
ci.mjs models the pipeline using two main object shapes:
Target — the logical compilation target (what is being built):
| Field | Type | Description |
|---|---|---|
os | "linux" | "darwin" | "windows" | "freebsd" | Target OS |
arch | "aarch64" | "x64" | Target CPU architecture |
abi | "musl" | "android" | Optional ABI override |
baseline | boolean | Targets older CPUs (Nehalem / Cortex-A53) |
profile | "release" | "assert" | "debug" | "asan" | Build profile |
crossCompile | boolean | Built on a Linux host for a non-Linux target |
Platform — a Target plus OS image/distro metadata used for test matrix entries:
| Additional Fields | Description |
|---|---|
distro | "debian" | "ubuntu" | "alpine" | "amazonlinux" |
release | OS version string (e.g. "2023", "3.23", "13") |
tier | "latest" | "previous" | "oldest" | "eol" — for macOS agent routing |
features | Array of image features (e.g. ["docker"]) |
Sources: .buildkite/ci.mjs36-108
All compilation occurs on Linux EC2 machines. macOS and Windows are cross-compiled; their native fleets only run tests.
Build platforms (defined in the buildPlatforms array):
| Target | Host | Notes |
|---|---|---|
darwin-aarch64 | amazonlinux 2023 | Cross-compiled via Apple SDK + ld64.lld |
darwin-x64 | amazonlinux 2023 | Cross-compiled |
linux-aarch64 | amazonlinux 2023 | Native glibc |
linux-x64 | amazonlinux 2023 | Native glibc |
linux-x64-baseline | amazonlinux 2023 | SSE4.2-only binary |
linux-x64-asan | amazonlinux 2023 | AddressSanitizer build |
linux-aarch64-musl | alpine 3.23 | musl ABI |
linux-x64-musl | alpine 3.23 | musl ABI |
linux-x64-musl-baseline | alpine 3.23 | musl + SSE4.2 only |
linux-aarch64-android | amazonlinux 2023 | NDK cross-compile |
linux-x64-android | amazonlinux 2023 | NDK cross-compile |
freebsd-x64 | amazonlinux 2023 | base.txz sysroot |
freebsd-aarch64 | amazonlinux 2023 | base.txz sysroot |
windows-x64 | amazonlinux 2023 | clang-cl + xwin MSVC sysroot |
windows-x64-baseline | amazonlinux 2023 | SSE4.2-only |
windows-aarch64 | amazonlinux 2023 | clang-cl cross |
Sources: .buildkite/ci.mjs132-176
Each platform goes through three sequential steps. The diagram below shows the dependency graph for one representative target.
Build step dependency graph per target
build-cppFunction: getBuildCppStep `.buildkite/ci.mjs603-626
node --experimental-strip-types scripts/build.ts --profile=ci-cpp-only [flags]c8g.4xlarge (aarch64) or c7i.4xlarge (x64) EC2 via getCppAgentwindows-x64: installs NASM before building (BoringSSL assembly)build-rustFunction: getBuildRustStep `.buildkite/ci.mjs633-646
scripts/build.ts --profile=ci-rust-only [flags]getRustAgent:
cc crate)getRustPlatform) using r8g.4xlarge (ASAN) or r8g.2xlargebuild-bun (link)Function: getLinkBunStep `.buildkite/ci.mjs653-670
depends_on: both sibling build-cpp and build-rust stepsscripts/build.ts --profile=ci-link-only [flags]r8g.2xlarge (aarch64) or r7i.2xlarge (x64) — memory-optimized for LTO peak usageSources: .buildkite/ci.mjs385-455 .buildkite/ci.mjs530-670
Build agents are always AWS EC2 instances identified by robobun / robobun2 tags and matched by image name.
Test agents vary by platform:
| Platform | Agent | Instance type |
|---|---|---|
macOS (aarch64, latest) | queue: test-darwin with release-tier: latest | Physical Mac fleet |
macOS (aarch64, previous) | queue: test-darwin with release-tier: previous | Physical Mac fleet |
| macOS (x64) | queue: test-darwin (any Intel box) | Physical Mac fleet |
| Linux (x64, glibc) | EC2 c7i.xlarge | AWS |
| Linux (aarch64, glibc) | EC2 c8g.xlarge | AWS |
| Linux (x64, musl) | EC2 m7i.xlarge | AWS (2× RAM for docker containers) |
| Linux (aarch64, asan) | EC2 r8g.2xlarge | AWS (4× RAM for shadow memory) |
| Windows (x64/aarch64) | Azure Standard_D4ds_v6 / Standard_D4pds_v6 | Azure VM |
The getTestAgent function `.buildkite/ci.mjs461-524 encodes all these decisions. macOS agents emit a release-tier tag from scripts/agent.mjs based on the machine's macOS major version; ci.mjs targets jobs to release-tier: latest or release-tier: previous for arm64 shards.
Sources: .buildkite/ci.mjs357-524 scripts/agent.mjs32-45
Tests run on a separate set of platforms from builds. Test agents receive the compiled binary artifact from the corresponding build-bun step.
Test platforms (defined in testPlatforms):
| Platform | Distro | Tier | Parallelism |
|---|---|---|---|
| darwin aarch64 | — | latest (macOS 26) | 2 shards |
| darwin aarch64 | — | previous (macOS 14) | 2 shards |
| darwin x64 | — | latest | 2 shards |
| linux aarch64 | debian 13 | latest | 20 shards |
| linux x64 | debian 13 | latest | 20 shards |
| linux x64 baseline | debian 13 | latest | 20 shards |
| linux x64 asan | debian 13 | latest | 20 shards |
| linux aarch64 | ubuntu 25.04 | latest | 20 shards |
| linux x64 | ubuntu 25.04 | latest | 20 shards |
| linux x64 baseline | ubuntu 25.04 | latest | 20 shards |
| linux aarch64 musl | alpine 3.23 | latest | 20 shards |
| linux x64 musl | alpine 3.23 | latest | 20 shards |
| linux x64 musl baseline | alpine 3.23 | latest | 20 shards |
| windows x64 | — (2019) | oldest | 8 shards |
| windows x64 baseline | — (2019) | oldest | 8 shards |
| windows aarch64 | — (11) | latest | 8 shards |
Each test-bun step uses parallelism to split the test suite across N parallel agents. The runner script scripts/runner.node.mjs reads BUILDKITE_PARALLEL_JOB and BUILDKITE_PARALLEL_JOB_COUNT to determine its shard assignment.
Sources: .buildkite/ci.mjs181-207 .buildkite/ci.mjs808-859 scripts/runner.node.mjs120-127
runner.node.mjs)The test runner scripts/runner.node.mjs executes on each test agent shard. It is written in plain Node.js (no Bun APIs) so it can run the binary under test as an external process.
Platform verification: At startup, assertExpectedPlatform scripts/runner.node.mjs430-471 checks that the agent's actual OS/arch/ABI/distro/release matches the EXPECTED_PLATFORM_* environment variables set by getTestBunStep in ci.mjs. A mismatch exits immediately with code 1, preventing silently wrong test results from a misrouted job.
ASAN Exception Validation: When running in ASAN CI, the runner respects a list of tests in test/no-validate-exceptions.txt for which validateExceptionChecks=1 is not set test/no-validate-exceptions.txt1-135
Key CLI options:
| Option | Source | Purpose |
|---|---|---|
--step | BUILDKITE_STEP_KEY | Downloads the binary artifact from this build step |
--build-id | CLI | Downloads artifact from a different build |
--shard | BUILDKITE_PARALLEL_JOB | Which shard of the test suite to run |
--max-shards | BUILDKITE_PARALLEL_JOB_COUNT | Total number of shards |
--include / --exclude | CI pipeline args | Test file filters |
--retries | Defaults to 3 in CI | Retry flaky tests up to N times |
Docker coordinator: On Linux CI, if Docker Compose is available, the runner spawns test/docker/coordinator.ts to manage service containers (MySQL, PostgreSQL, Redis, MinIO, etc.) for the entire shard. Services are pre-started while vendor deps install. Tests reach it via BUN_DOCKER_COORDINATOR scripts/runner.node.mjs498-554
Sources: scripts/runner.node.mjs94-178 scripts/runner.node.mjs430-471 scripts/runner.node.mjs498-554 test/no-validate-exceptions.txt1-135
The getTargetTriplet function `.buildkite/ci.mjs678-691 produces the canonical artifact name. Each build-bun step uploads two zip files per target:
| Artifact | Contents |
|---|---|
bun-{os}-{arch}[-musl][-android][-baseline].zip | Stripped production binary |
bun-{os}-{arch}[-musl][-android][-baseline]-profile.zip | Binary with symbols (.pdb on Windows, DWARF on Linux) |
Examples: bun-linux-x64.zip, bun-linux-x64-musl-baseline.zip, bun-windows-aarch64-profile.zip.
Sources: .buildkite/ci.mjs678-691
Purpose: Ensure no AVX/AVX2 instructions leaked into baseline x64 builds, and no LSE/SVE instructions leaked into aarch64 builds.
Function: getVerifyBaselineStep `.buildkite/ci.mjs738-793
The step runs after build-bun and proceeds in two phases:
Phase 1 — Static scan: Builds and runs scripts/verify-baseline-static/ (a Rust binary) which disassembles the bun-profile binary and checks every instruction against an allowlist. The allowlist files (allowlist-x64.txt, allowlist-aarch64.txt, allowlist-x64-windows.txt) cover legitimate runtime-dispatched CPUID-gated code.
Phase 2 — Emulator execution: Runs the binary through an emulator that restricts the CPU feature set:
| Platform | Emulator | CPU model |
|---|---|---|
| Linux x64 baseline | qemu-x86_64-static -cpu Nehalem | SSE4.2, no AVX |
| Linux aarch64 | qemu-aarch64-static -cpu cortex-a53 | ARMv8.0-A+CRC, no LSE/SVE |
| Windows x64 baseline | C:\intel-sde\sde.exe -nhm | Nehalem, no AVX |
The bun-profile binary is used (not the stripped bun) so that violations are attributable to symbols. An SIGILL (exit code 132 on Linux) or an SDE violation pattern on Windows indicates a forbidden instruction.
Platforms requiring this step are identified by needsBaselineVerification `.buildkite/ci.mjs700-705
Sources: .buildkite/ci.mjs700-793 scripts/verify-baseline.ts1-305
Function: getWindowsSignStep `.buildkite/ci.mjs918-947
DigiCert's smctl tool is x64-only and cannot run under ARM64 emulation. Signing is therefore batched into a single step that:
build-bun steps completingStandard_D4ds_v6)bun-windows-*.zip and -profile.zip).buildkite/scripts/sign-windows-artifacts.ps1The release step uses WINDOWS_ARTIFACT_STEP=windows-sign `.buildkite/scripts/upload-release.sh125-132 to fetch signed artifacts from this step rather than the raw build steps.
Sources: .buildkite/ci.mjs909-947 .buildkite/scripts/upload-release.sh125-132
Function: getBinarySizeStep `.buildkite/ci.mjs960-984
After all release platform builds complete, this step:
binary-sizes.json from the latest main buildBINARY_SIZE_THRESHOLD_MB (0.5 MB) `.buildkite/ci.mjs986main branch builds: records only (no comparison failure) to update the baselineThe step runs bun scripts/binary-size.ts on a small c8g.large instance and uses allow_dependency_failure: true so partial build failures don't prevent size tracking.
Sources: .buildkite/ci.mjs960-986
Function: getReleaseStep → runs .buildkite/scripts/upload-release.sh `.buildkite/ci.mjs994-1019
The release step only executes on main branch builds (enforced by assert_main in the shell script `.buildkite/scripts/upload-release.sh5-26). It:
windows-sign if signing ran)releases/{commit}-canary/{artifact} (canary)releases/canary/{artifact} (latest canary tag)gh release uploadThe release step runs on a macOS agent in queue: test-darwin because the script requires gh (GitHub CLI) and AWS CLI, which are present on the macOS fleet.
Artifact list uploaded per release:
bun-darwin-{aarch64,x64}[-profile].zip
bun-linux-{aarch64,x64}[-baseline][-musl][-musl-baseline][-android][-profile].zip
bun-freebsd-{aarch64,x64}[-profile].zip
bun-windows-{x64,x64-baseline,aarch64}[-profile].zip
Sources: .buildkite/scripts/upload-release.sh5-26 .buildkite/scripts/upload-release.sh168-261 .buildkite/ci.mjs994-1019
The following diagram maps the ci.mjs pipeline generation logic to the step-creation functions.
Pipeline generation: function-to-step mapping
Sources: .buildkite/ci.mjs603-626 .buildkite/ci.mjs633-670 .buildkite/ci.mjs808-859 .buildkite/ci.mjs918-947 .buildkite/ci.mjs960-984 .buildkite/ci.mjs994-1019 .buildkite/scripts/upload-release.sh5-26
Priority (getPriority) `.buildkite/ci.mjs327-338:
| Context | Priority |
|---|---|
| Fork PR | -1 (lowest) |
| Default | 0 |
| Merge queue | 1 |
main branch | 2 (highest) |
Retry policy (getRetry) `.buildkite/ci.mjs304-320):
permit_on_passed: true)exit_status: -1 (agent lost) and exit_status: 255 (timeout kill), limit 1 eachMerge queue behavior: All steps set cancel_on_build_failing: true when isMergeQueue() returns true, so the entire pipeline cancels on first failure.
Sources: .buildkite/ci.mjs304-338
CI agent images are built and published by a separate pipeline triggered manually or by [build images] in the commit message. The function getBuildImageStep `.buildkite/ci.mjs866-907 generates steps that run node ./scripts/machine.mjs create-image or publish-image.
Image names follow the pattern {os}-{arch}-{version}-{distro}[-with-{features}]-v{bootstrap_version} where bootstrap_version comes from the Version: comment at the top of scripts/bootstrap.sh scripts/bootstrap.sh2 or scripts/bootstrap.ps1 scripts/bootstrap.ps11
The getImageName function `.buildkite/ci.mjs279-298 selects either:
{name}-build-{buildNumber} (during [build images] runs){name}-v{bootstrapVersion} (for normal CI builds)Docker support: Bun also publishes official Docker images based on Alpine dockerhub/alpine/Dockerfile1-76 and Debian dockerhub/debian-slim/Dockerfile1-89 These Dockerfiles download the corresponding binary artifact, verify its GPG signature, and set up a bun user.
Sources: .buildkite/ci.mjs243-298 .buildkite/ci.mjs866-907 scripts/bootstrap.sh2 scripts/bootstrap.ps11 dockerhub/alpine/Dockerfile1-76 dockerhub/debian-slim/Dockerfile1-89
Refresh this wiki
This wiki was recently refreshed. Please wait 5 days to refresh again.