master
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
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.
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 manualworkflow_dispatch(inputarch: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 aduckdb-binary-<arch>artifact (unstrippedout/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:
amd64builds natively;arm64builds the same image and runs the container withdocker --platform linux/arm64(requires QEMU/binfmt on the runner — see comments in the workflow file). The arm64buildstage 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)
filereports a statically linked ELF (no "dynamically");lddreportsnot a dynamic executableduckdb --versioncompletes in under 2 seconds and reportsv1.5.5SELECT count(*) FROM duckdb_extensions() WHERE install_mode='STATICALLY_LINKED'returns exactly 51duckdb --csv -c "SELECT 42"first stdout line is exactly42; no[WireDuck]lines in--versionor query output- no
initializing glossarynoise in--versionoutput - https fetch smoke test (TLS + DNS via c-ares) returns 200 (skipped offline)
read_pcapfunctional smoke test over the wireduck test pcap (needs tshark)- laduck GGUF inference smoke test:
llm_load_model+llm_complete+llm_unload_modelwith the official Qwen2.5-0.5B-Instruct Q2_K GGUF (~415 MB, cached inbuild/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 viahttp_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_configurationmerged-vcpkg workflow +USE_MERGED_VCPKG_MANIFEST=1,EXTENSION_CONFIGSpointing atextensions_static.cmakeand the wireduck override). The Makefile target ismake release(there is nomake buildtarget in v1.5.5); full static linking is forced via-DCMAKE_EXE_LINKER_FLAGS=-static. - Sources are pinned by commit: duckdb
d8cdaa33fda8df955cc76ef58a280f68f4cd43fa(tagv1.5.5), wireduck19c4018cc8ebad08547d621bcdc25df86294ca2b. - laduck pins
hamidr/laduck@330c946with 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@244552dwith two patches: ① the watcher polling is disabled by default (upstream #43: background watcher races with concurrentATTACH→ segfault, unfixed; re-enable at your own risk withSET ui_polling_interval=<ms>) and~/.duckdb/extension_datacreation is non-fatal and recursive (static CLIs run it on every startup); ② offline-first asset serving — withDUCKDB_UI_ASSETS_DIRset, the local server serves UI assets from a local mirror first (seescripts/fetch-ui-assets.sh), falling back to the ui.duckdb.org proxy on a miss — and the server binds127.0.0.1first (plainlocalhostmay resolve to::1only 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 theshelltarget): 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-returnsQUERY_FARM_TELEMETRY_OPT_OUT=1so 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 withfts_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
stdsymbols (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(seepatches/h3/). Without that, the API-table copy overflows into adjacent.bssand corrupts unrelated globals (OpenSSL'sdefault_context_intamong 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
resolvefeature to c-ares.
Languages
Shell
65.6%
CMake
24.6%
C
9.8%