Back to Netdata

Building a static Netdata binary

.agents/skills/project-build-static-binary/SKILL.md

2.11.013.4 KB
Original Source

Building a static Netdata binary

The static build produces a self-extracting installer (netdata-<arch>-latest.gz.run) that runs on any 64-bit Linux without a native toolchain or system libraries. The output installs under /opt/netdata. Use it to test a PR on a target system that lacks build tools (RHEL, old distros, embedded), to ship a prebuilt to a customer host, or to reproduce CI behavior locally.

This skill captures the full local build flow plus the gotchas that block first-time builds. Read top to bottom on first use; the Common failures section exists because every one of them has burned a build in this repo.

TL;DR — x86_64 native

bash
# 1. Pre-flight (mandatory)
docker pull netdata/static-builder:v1     # refresh the cached image — see Gotcha #2
git submodule update --init --recursive   # populate vendored sources — see Gotcha #1

# 2. Build (~22-25 min cold, ~10-15 min on cache hit, on a 24-core host)
./packaging/makeself/build-static.sh x86_64

# 3. Output
ls -la artifacts/
# artifacts/netdata-x86_64-latest.gz.run        ~190 MB, ready to copy to any 64-bit Linux
# artifacts/netdata-x86_64-vX.Y.Z-N-nightly.gz.run
# artifacts/netdata-latest.gz.run               (x86_64 only — alias of the above)
# artifacts/netdata-vX.Y.Z-N-nightly.gz.run     (x86_64 only — alias)

For debug builds: ./packaging/makeself/build-static.sh x86_64 debug.

What you actually run

The orchestrator is packaging/makeself/build-static.sh, which:

  1. Translates the architecture name (x86_64, aarch64, armv6l, armv7l) to a docker --platform value via packaging/makeself/uname2platform.sh.
  2. Sets per-arch tuning flags (packaging/makeself/build-static.sh:27-56):
    • x86_64-march=x86-64 baseline (x86-64-v2 equivalent), Nehalem-v2 QEMU CPU, GOAMD64=v1.
    • aarch64-march=armv8-a, Cortex-A53, GOARM64=v8.0.
    • armv7l-march=armv7-a, Cortex-A7, GOARM=7.
    • armv6l-march=armv6zk -mtune=arm1176jzf-s, ARM1176, GOARM=6.
  3. Installs binfmt/QEMU emulation if cross-arch and not already registered.
  4. Pulls netdata/static-builder:v1 if missing locally; removes a mismatched-platform image first.
  5. Bind-mounts $(pwd) into the container at /netdata and runs /netdata/packaging/makeself/build.sh inside it.

The container then runs packaging/makeself/run-all-jobs.sh, which executes packaging/makeself/jobs/*.sh in lexical order:

#JobWhat it does
00prepare-destinationLays out /opt/netdata/{bin,usr,sbin,...} symlinks
10libucontext.installBuilds bundled libucontext (musl context-switch fallback)
11openssl.installBuilds OpenSSL 3.6.0 statically
20libnetfilter_acct.installBuilds libnetfilter_acct statically
20libunwind.installBuilds libunwind statically
30curl.installBuilds curl + libcurl statically
40bash.installBuilds bash statically
50ioping.installBuilds ioping statically
70netdata-git.installBuilds Netdata itself (Rust crates + C/CMake + Go plugins) and installs into /opt/netdata
71install-typeStamps .install-type so the post-installer knows it's a static install
72conf-fixupStrips machine-specific configuration and sample files
80netdata-static-checkVerifies key binaries are statically linked
81netdata-runtime-checkBoots /opt/netdata/bin/netdata, waits for localhost:19999, checks /api/v1/info
82cpu-arch-checkVerifies the produced binaries match the target arch baseline
89buildinfo.installWrites /opt/netdata/share/netdata/buildinfo.txt
90prepare-archive-sourceCopies post-installer scripts into the install tree
91copy-ca-certificatesBundles a CA bundle
98create-archiveRuns makeself.sh --gzip --complevel 9 --notemp --needroot to build the .gz.run
99copy-archivesRenames the archive to netdata-<arch>-<version>.gz.run and copies aliases

Source files: packaging/makeself/build-static.sh, packaging/makeself/build.sh, packaging/makeself/run-all-jobs.sh, packaging/makeself/functions.sh, packaging/makeself/jobs/.

Pre-flight (DO NOT SKIP)

These two checks save you a wasted 20+ min build.

Gotcha #1: submodules must be initialized

The build configures with CMake against vendored sources at:

  • src/aclk/aclk-schemas/
  • src/collectors/debugfs.plugin/libsensors/vendored/

If either is empty, CMake aborts after ~2 min with:

Cannot find source file: vendored/lib/access.c
No SOURCES given to target: vendored_libsensors
ABORTED  Failed to configure Netdata sources.

A fresh git clone does not populate submodules. A worktree created from a fork that hasn't initialized submodules does not either. Always run before your first build in a working tree:

bash
git submodule update --init --recursive

Verify with git submodule status — every line must start with a space (the leading - means uninitialized).

Gotcha #2: refresh the cached docker image

The build pulls netdata/static-builder:v1 only if no local image exists. If you have an old cached image (the project published a v1 in 2023 based on Alpine 3.18, then refreshed it in 2026 with Alpine 3.23 + coreutils), the build fails inside packaging/makeself/functions.sh:84 with:

sha256sum: unrecognized option: c
SHA256 verification of tar file libnetfilter_acct-1.0.3.tar.bz2 failed (rc=1)
expected: <hash>, got <same-hash>

Why: functions.sh:84 runs sha256sum --c --status (long-option form). BusyBox's sha256sum (Alpine without coreutils) does not accept long options, so it fails even though the actual hash matches. The fresh image installs coreutils, which symlinks /usr/bin/sha256sum to GNU coreutils and accepts the long form.

Fix: always re-pull before a build session:

bash
docker pull netdata/static-builder:v1

The pull is a no-op if your cache is already up to date.

Output artifacts

Job 99-copy-archives.sh writes to artifacts/ (gitignored, host-side, owned by your user):

artifacts/
├── netdata-x86_64-latest.gz.run            # symlink/alias to the latest
├── netdata-x86_64-v2.10.0-171-nightly.gz.run
├── netdata-latest.gz.run                   # x86_64 only — generic alias
├── netdata-v2.10.0-171-nightly.gz.run      # x86_64 only — generic alias
└── cache/                                  # build cache, see below

For non-x86_64 builds, only the two netdata-<arch>-* files are produced (packaging/makeself/jobs/99-copy-archives.sh:25-30).

Verify a build:

bash
ls -la artifacts/
sha256sum artifacts/netdata-x86_64-latest.gz.run

# What's inside (read-only inspection, does not run the installer)
sh artifacts/netdata-x86_64-latest.gz.run --info
sh artifacts/netdata-x86_64-latest.gz.run --list | head

Each .gz.run is a makeself archive: a shell prefix that extracts the gzipped tar embedded after it. Run it as root on the target to install.

Build cache

artifacts/cache/<arch>/ holds the compiled third-party deps (openssl, curl, bash, libunwind, libnetfilter_acct, ioping) keyed by their pinned source versions in packaging/makeself/bundled-packages.version. The fetch logic is in packaging/makeself/functions.sh (fetch() + cache_key()).

Implications:

  • First build: ~22-25 min on a 24-core host (most time = third-party compile + Rust crate compile + LTO link of the C plugins).
  • Cache hit: ~10-15 min (skips the third-party deps; only the netdata sources rebuild).
  • The cache survives git checkout and git clean -fd (it's under the gitignored artifacts/).
  • Bumping a version in bundled-packages.version invalidates that one entry — the rest still reuse.
  • To force a clean build: rm -rf artifacts/.

Cross-architecture builds (aarch64, armv7l, armv6l)

bash
./packaging/makeself/build-static.sh aarch64
./packaging/makeself/build-static.sh armv7l
./packaging/makeself/build-static.sh armv6l

The script auto-installs QEMU binfmt handlers via tonistiigi/binfmt:master if not already registered (packaging/makeself/build-static.sh:60-62). Cross-arch builds:

  • Run all C/Rust/Go compilation under QEMU emulation — expect 4-8× slowdown vs native.
  • Are CPU-bound, not network-bound — the source download is one-time.
  • Can fail in ways native builds do not (e.g. Rust LTO under QEMU has historically OOMed; libbpf BPF skeleton generation has hit qemu syscall edge cases). When you see a failure that isn't on x86_64 native, suspect QEMU first.

SKIP_EMULATION=1 is set automatically when the host already has a binfmt_misc entry for the target arch (e.g. on a CI runner with persistent qemu).

Debug builds

bash
./packaging/makeself/build-static.sh x86_64 debug

Sets NETDATA_BUILD_WITH_DEBUG=1 (packaging/makeself/build.sh:9-22), which disables optimization and includes debug symbols. Result: larger archive (~2× size), slower runtime, useful for valgrind/gdb. The README.md in packaging/makeself/ documents valgrind invocation.

Common failures

SymptomJobCauseFix
Cannot find source file: vendored/lib/access.c70 (CMake configure)Submodules not initializedgit submodule update --init --recursive
sha256sum: unrecognized option: c then expected: X, got X11 / 20 / 30 / 40 / 50Stale static-builder:v1 (Alpine 3.18, no coreutils)docker pull netdata/static-builder:v1
No cached copy of build directory for X found, fetching sources instead. (every run)any third-partyartifacts/cache/ removed or arch dir missingNormal on first build; persists for the next run
Could not find a usable OCI runtimen/aNeither docker nor podman in $PATHInstall one
Waiting for netdata on localhost:19999 ... hangs forever81 (runtime check)Built binary segfaulted at startupRead end of 81-netdata-runtime-check.sh log; reproduce locally with the install path
not statically linked warning80 (static check)A new dep introduced a dynamic linkAudit ldd of the built binary; check CMakeLists.txt for target_link_libraries adding a shared lib
OOM kill mid-Rust compile under QEMU70QEMU + Rust LTO is memory-hungryAdd swap; reduce -j via PROCESSORS env

The build script exits with Build failed. on any job failure (packaging/makeself/build.sh:44-52). If DEBUG_BUILD_INFRA=1 is set in the environment, the container drops to a bash shell instead of exiting — useful when you want to inspect the in-progress install tree.

Watching a long-running build

Background launch + log tail pattern:

bash
LOG=/tmp/netdata-build-$(date +%s).log
nohup ./packaging/makeself/build-static.sh x86_64 > "$LOG" 2>&1 &
echo $! > /tmp/netdata-build.pid

# Progress
grep -E '^ --- running' "$LOG"          # job-level milestones
tail -f "$LOG"                          # streaming output
docker stats --no-stream                # CPU/RAM of the running container

Indicators the build is alive (output buffering can stall the log for minutes during heavy compile):

  • ps -eo pid,pcpu,comm --sort=-pcpu | head shows rustc, cc1, lto1-ltrans near 50-70% each.
  • docker stats shows the static-builder container at hundreds of % CPU.
  • The container's working set in docker stats keeps changing.

Deploying to a target

bash
# Copy
scp artifacts/netdata-x86_64-latest.gz.run target-host:/tmp/

# Install (on the target, as root)
ssh target-host
sudo sh /tmp/netdata-x86_64-latest.gz.run -- --auto-update
# installer flags after the bare `--`; common: --dont-start-it, --disable-telemetry,
# --claim-token <T> --claim-rooms <R>, --no-updates

The installer always installs into /opt/netdata (hard-coded; --target would change it but the in-archive paths assume /opt/netdata, do not override).

Inspecting what's inside without installing:

bash
sh artifacts/netdata-x86_64-latest.gz.run --info     # makeself metadata
sh artifacts/netdata-x86_64-latest.gz.run --list     # full file manifest
sh artifacts/netdata-x86_64-latest.gz.run --target /tmp/nd-extract --noexec --keep

How to extend this skill

When you discover a new failure mode, an arch-specific quirk, or a workflow that's worth preserving, add a how-to under how-tos/ and link it from how-tos/INDEX.md. Keep SKILL.md tight; push detail into how-tos. The catalog is live — every assistant who solves a non-trivial build problem must commit the recipe before moving on.

Source-of-truth pointers

  • packaging/makeself/README.md — high-level user-facing doc (architectures, valgrind notes).
  • packaging/makeself/build-static.sh — host-side launcher, arch matrix, docker invocation.
  • packaging/makeself/build.sh — in-container entry; debug-flag parsing.
  • packaging/makeself/run-all-jobs.sh — job runner.
  • packaging/makeself/functions.shfetch(), cache(), cache_key(), progress(), run() helpers.
  • packaging/makeself/jobs/*.sh — the 18 ordered build steps.
  • packaging/makeself/bundled-packages.version — pinned versions of openssl, curl, bash, libunwind, libnetfilter_acct, ioping.
  • packaging/makeself/install-alpine-packages.sh — the package list the static-builder docker image is built from (used when refreshing the image, not on every build).
  • packaging/makeself/uname2platform.sh — arch → docker --platform translation.
  • packaging/makeself/makeself.sh, makeself-header.sh — vendored makeself archive builder.