tqcq 858557aec1
build / build (amd64) (push) Successful in 47m53s
build / build (arm64) (push) Successful in 5h9m49s
build / verify (arm64) (push) Failing after 3m37s
build / verify (amd64) (push) Successful in 3m10s
build / package (amd64) (push) Skipped
build / package (arm64) (push) Skipped
build / Create release with dist artifacts (push) Skipped
CI: best-effort relay artifact cleanup; update stale 3h-deadline notes
duckdb-binary-<arch> is an inter-stage relay, not a deliverable. After the
dist artifacts are uploaded, the package job now tries to delete the relay
artifact via the Gitea artifacts API (non-fatal if unsupported — the 1-day
retention still removes it). Comments updated: the instance job deadline
was raised from 3h to 12h, so arm64 QEMU builds now complete.
2026-09-04 10:07:28 +08:00

duckdb-static-build

Build a fully-static DuckDB v1.5.5 CLI for Linux (glibc/Ubuntu 24.04) with 51 statically-linked extensions, including a patched WireDuck extension (lazy-load + silent output). Builds run in Docker on both amd64 (native) and arm64 (via QEMU), triggered from Gitea Actions.

Layout

Path Purpose
docker/Dockerfile.build Ubuntu 24.04 + toolchain + pinned vcpkg builder image (multi-arch)
scripts/build.sh Fetch pinned duckdb/wireduck sources, apply patches, make extension_configuration + static release build, stage out/duckdb
scripts/verify.sh Post-build verification of out/duckdb (staticness, version, 51 static extensions, output cleanliness, GGUF inference + ui server smoke tests)
scripts/package.sh Split debug symbols, strip, checksum, produce out/dist/*.tar.zst
extensions_static.cmake Extension config selecting the 51 statically-linked extensions
patches/wireduck/ WireDuck patches (*.patch) + wireduck_override.cmake
.github/workflows/build.yml CI: dual-arch build, artifact upload, tag-triggered release (.gitea/ is no longer supported on this instance)
build/ Reference checkout of duckdb v1.5.5 (ground-truth docs; gitignored, not used by the build itself)

Local build quickstart

docker build -f docker/Dockerfile.build -t duckdb-static-builder:local .

# build (sources are cloned into ./build/, binary staged at ./out/duckdb)
docker run --rm -v "$PWD":/work duckdb-static-builder:local

# verify
docker run --rm -v "$PWD":/work --entrypoint /bin/bash \
    duckdb-static-builder:local /opt/scripts/verify.sh

# package (writes ./out/dist/*.tar.zst)
docker run --rm -v "$PWD":/work --entrypoint /bin/bash \
    duckdb-static-builder:local /opt/scripts/package.sh

Useful env overrides for build.sh (pass with docker run -e): DUCKDB_TAG, DUCKDB_COMMIT, JOBS, EXTENSION_CONFIGS, EXTRA_CMAKE_VARIABLES.

CI

  • Trigger: push a tag matching v*, or manual workflow_dispatch (input arch: both (default), amd64, arm64). Pushes to master also run the full pipeline on both architectures.
  • Pipeline: three staged jobs per arch — build → verify → package — chained by a duckdb-binary-<arch> artifact (unstripped out/duckdb + wireduck test pcap). Stage isolation means a failed stage can be re-run alone (e.g. re-verify without a full rebuild). The builder image is rebuilt per stage (runner cache is not shared).
  • Matrix: amd64 builds natively; arm64 builds the same image and runs the container with docker --platform linux/arm64 (requires QEMU/binfmt on the runner — see comments in the workflow file). The arm64 build stage takes ~5-7h under QEMU; the instance's job deadline is 12h.
  • Artifacts: per-arch uploads (duckdb-static-<arch>) from the package stage, and on tag pushes a Gitea Release is created with all dist files attached.

Artifacts

File Contents
duckdb-static-linux-<arch>.tar.zst stripped duckdb binary + SHA256SUMS (covers both archives)
duckdb-static-linux-<arch>-debug.tar.zst duckdb.debug split debug symbols

<arch> is amd64 or arm64.

Verification summary (scripts/verify.sh)

  • file reports a statically linked ELF (no "dynamically"); ldd reports not a dynamic executable
  • duckdb --version completes in under 2 seconds and reports v1.5.5
  • SELECT count(*) FROM duckdb_extensions() WHERE install_mode='STATICALLY_LINKED' returns exactly 51
  • duckdb --csv -c "SELECT 42" first stdout line is exactly 42; no [WireDuck] lines in --version or query output
  • no initializing glossary noise in --version output
  • https fetch smoke test (TLS + DNS via c-ares) returns 200 (skipped offline)
  • read_pcap functional smoke test over the wireduck test pcap (needs tshark)
  • laduck GGUF inference smoke test: llm_load_model + llm_complete + llm_unload_model with the official Qwen2.5-0.5B-Instruct Q2_K GGUF (~415 MB, cached in build/models/; skipped offline). Note: legacy "tinyllama/stories" GGUFs load but produce empty completions with modern llama.cpp — do not use them as test payloads.
  • ui server smoke test: start_ui_server + self-fetch via http_get + stop_ui_server (exercises threading, httplib, c-ares localhost lookups)

Notes

  • DuckDB is built with the v1.5.5 static-extension mechanism (make extension_configuration merged-vcpkg workflow + USE_MERGED_VCPKG_MANIFEST=1, EXTENSION_CONFIGS pointing at extensions_static.cmake and the wireduck override). The Makefile target is make release (there is no make build target in v1.5.5); full static linking is forced via -DCMAKE_EXE_LINKER_FLAGS=-static.
  • Sources are pinned by commit: duckdb d8cdaa33fda8df955cc76ef58a280f68f4cd43fa (tag v1.5.5), wireduck 19c4018cc8ebad08547d621bcdc25df86294ca2b.
  • laduck pins hamidr/laduck@330c946 with llama.cpp (third_party/llama.cpp@69c28f1) built as a static subproject — local GGUF inference (llm_complete / llm_embed / llm_classify) runs in-process, no server/subprocess. Upstream is dormant; the pin is effectively owned by this repo. The patch disables ggml OpenMP (system libgomp is shared-only, incompatible with -static); ggml keeps its pthread compute pool.
  • ui pins duckdb/duckdb-ui@244552d with two patches: ① the watcher polling is disabled by default (upstream #43: background watcher races with concurrent ATTACH → segfault, unfixed; re-enable at your own risk with SET ui_polling_interval=<ms>) and ~/.duckdb/extension_data creation is non-fatal and recursive (static CLIs run it on every startup); ② offline-first asset serving — with DUCKDB_UI_ASSETS_DIR set, the local server serves UI assets from a local mirror first (see scripts/fetch-ui-assets.sh), falling back to the ui.duckdb.org proxy on a miss — and the server binds 127.0.0.1 first (plain localhost may resolve to ::1 only via the c-ares override, breaking IPv4 clients). The UI frontend itself is MotherDuck's proprietary code and is NOT redistributed by this repo.

Known quirks of this binary

  • Process exit skips global teardown on purpose (scripts/static_cli_runtime_overrides.c, linked into the shell target): a fully-static binary bundling OpenSSL + libmariadbclient + two Rust staticlibs + 51 extensions has cross-library atexit handlers that race and double-free after all real work is done. exit() flushes stdio and calls _exit(); getenv() force-returns QUERY_FARM_TELEMETRY_OPT_OUT=1 so query-farm extensions never spawn their startup telemetry threads (their concurrent first-use OpenSSL init races in this build). Side effect: memory is not freed at exit (invisible for a CLI).
  • fts: PRAGMA create_fts_index('main.t', 'x', 'txt') — qualify the table name (or quote it); the bare unqualified form misparses in this fts pin against duckdb v1.5.5. Query with fts_main_<table>.match_bm25(...). Note upstream fts (v1.5.5 pin) also drops some tokens during indexing — verified identical on a reference v1.5.5-dev23 build, not a static-build artifact.
  • Rust extensions (prql, lindel): the two Rust archives embed duplicate copies of three std symbols (EMPTY_PANIC, ARGV_INIT_ARRAY, rust_eh_personality); the link is permitted via -Wl,--allow-multiple-definition (verified: exactly these three duplicates exist and they are bit-identical).
  • h3 bridge: h3's vendored C-API header is an older API table (2856 B) than duckdb v1.5.5's (4368 B); the global table symbol is defined once with the v1.5.5 type in src/h3_static_api_init.cpp (see patches/h3/). Without that, the API-table copy overflows into adjacent .bss and corrupts unrelated globals (OpenSSL's default_context_int among others).
  • curl is built against c-ares: static glibc cannot dlopen NSS modules, which crashed hostname resolution (SIGFPE inside getaddrinfo). The merged vcpkg manifest patches curl's resolve feature to c-ares.
S
Description
No description provided
Readme
177 KiB
Languages
Shell 65.6%
CMake 24.6%
C 9.8%