Deploying Assemblage on Linux
This page covers the current code on the main branch: Linux builders for
C/C++ (gcc, clang) and Rust (rustc with the LLVM, Cranelift and GCC
backends). INSTALL.md in the repository is the full step-by-step guide
with a verification command for each step; this page is the short version.
Components
Everything runs from one docker-compose.yml:
scraper: searches GitHub in date windows for repositories that have a license and a recognized build system, and sends them to the coordinator in bundles of 25.
scraper_0searches C/C++,scraper_rustRust.coordinator: records repositories in PostgreSQL, creates one build task per repository per build option, and runs one dispatch thread per build option that feeds its
build_opt_{id}queue.builders: containers that restore or clone a repository, build it, extract DWARF function and line information, and upload the binaries plus
assemblage_meta.jsonto MinIO. One compose service per build option.RabbitMQ (queues), PostgreSQL (metadata), MinIO (S3-compatible store for source archives and build artifacts).
export scripts on the host that turn the MinIO contents into a publishable dataset.
Builders acknowledge a task before building it. A task in progress when its builder is recreated is lost, not retried.
Requirements
A Linux host with Docker and Docker Compose v2.
Disk measured in terabytes. Artifacts dominate; a few thousand repositories across the build matrix reach hundreds of GB.
About 2 GB RAM per builder as a floor. DWARF extraction of large Rust binaries has been measured at roughly 42 times the binary size.
uv for host-side scripts and tests (Python 3.12).
A GitHub personal access token with public-repository read scope.
Warning
The compose file publishes PostgreSQL (5432), RabbitMQ (5672) and MinIO (9010 API, 9011 console) on the host. Put the host behind a firewall and open these ports only to your own worker hosts.
Configure
git clone https://github.com/Assemblage-Dataset/Assemblage.git
cd Assemblage
cp secrets.env.example secrets.env
Fill in secrets.env; every service loads it.
Key |
Purpose |
|---|---|
|
metadata database; keep the user as |
|
|
|
scraper API access |
|
|
|
must match |
|
default |
Build the images
Images are built locally, not pulled. Expect about ten minutes each on a cold cache.
docker build --build-arg TOOLCHAIN=gcc -t assemblage-gcc:default -f docker/worker/Dockerfile .
docker build --build-arg TOOLCHAIN=clang -t assemblage-clang:default -f docker/worker/Dockerfile .
docker build --build-arg RUST_TOOLCHAIN=nightly-2026-06-15 \
-t assemblage-rust:default -f docker/rust/Dockerfile .
The C/C++ image is Ubuntu 24.04 with gcc 13 and clang 18. Both variants
install the same packages; the clang variant exposes the gcc and cc
names as links to clang so build systems that hardcode them still work. The
Rust image installs one pinned nightly with the Cranelift and GCC codegen
components plus rustfilt. Check that Cranelift is present before
deploying, otherwise every Cranelift builder fails every task:
docker run --rm --entrypoint bash assemblage-rust:default -c \
'rustup component list --toolchain nightly-2026-06-15 | grep -i "cranelift.*installed"'
First boot
Start in this order. Builders that start before RabbitMQ resolves crash-loop on DNS, and the coordinator must be running before builders register.
docker compose up -d database rabbitmq minio
# wait until: docker inspect -f '{{.State.Health.Status}}' assemblage-db -> healthy
docker compose up -d coordinator
docker compose up -d scraper_0 scraper_rust
docker compose up -d --no-recreate builder_6 builder_rust_llvm_o2
On a fresh volume the coordinator creates the database and applies the
Alembic migrations itself. After pulling new migrations, apply them with
docker exec -it assemblage-coordinator-1 alembic upgrade head.
Warning
A bare docker compose up -d starts every service in the file: ten
C/C++ builder services at ten replicas each plus twenty Rust services.
Name the services you want.
Check that the fleet is live:
docker exec assemblage-rabbitmq-1 rabbitmqctl list_queues name messages consumers --quiet \
| awk '/^build_opt_/ && $3>0 {print; c+=$3} END {print "consumers="c}'
docker exec assemblage-db psql -U assemblage -d assemblage -t \
-c "SELECT count(*), max(build_date) FROM binaries;"
Every running builder should be a consumer on its own build_opt_{id}
queue, and max(build_date) should advance within minutes. Consumers with
zero messages on every queue means the fleet is stranded (see Failure
modes), not idle.
Build matrix
C/C++: builder_0 to builder_9 cover gcc and clang at -O0,
-O1, -O2, -O3 and -Os in build mode RelWithDebInfo
(-g -DNDEBUG). The builder detects autotools, CMake or plain Make from
the file list and runs make with a ten-minute timeout.
Rust: twenty builder_rust_* services, one per combination of codegen
backend, build mode and optimization flag, all on nightly-2026-06-15
with v0 symbol mangling:
Backend |
Build mode |
Flags |
|---|---|---|
llvm |
RelWithDebInfo |
|
llvm |
Debug |
|
llvm |
Release |
|
cranelift |
RelWithDebInfo |
|
gcc |
RelWithDebInfo |
|
Metadata quality differs by tier. LLVM and Cranelift builds have full
function, line and source mapping. GCC backend builds have correct names and
addresses, but repository-level source file and line information is mostly
missing. Release builds keep repository symbols in .symtab but carry
little repository DWARF. The six llvm/RelWithDebInfo services also
dump compiler IR (llvm-ir, mir, asm, hir, thir) for the
repository’s own crates, which adds about 75% to build time.
Each builder registers its compiler, language, backend, build mode and flag with the coordinator at startup. A new combination becomes a new build option row with its own queue, and existing repositories are queued for it. Adding a build option is a matter of adding a compose stanza.
Builder settings (environment variables, per service):
Variable |
Default |
Meaning |
|---|---|---|
|
10, 16g |
replicas and memory limit of the C/C++ services |
|
2 |
replicas of all twenty Rust services (see Scaling) |
|
1800 (Rust) |
build wall-clock limit |
|
4 |
cargo parallelism per Rust builder; unbounded cargo uses every host core in every container |
|
150 MB (Rust: 250 MB) |
binaries above this size skip extraction, with a logged warning |
|
300, 900, 8192 |
extraction runs in a child process bounded by these limits; on timeout the binary is still stored with an empty function list. The Rust Debug services use 900 and 1800. |
|
off, |
Rust IR dumps |
Extraction is single-threaded and is the throughput bottleneck. Its time follows DWARF complexity rather than file size: a 41 MB Rust binary took 46 minutes of CPU. On one host, adding builders past roughly 32 did not add throughput; the limit was disk I/O.
Operating the fleet
Scaling
BUILDER_REPLICAS=4 BUILDER_MEM=8g docker compose up -d # C/C++ defaults
docker compose up -d --scale builder_6=20 builder_6 # more gcc -O2
docker compose up -d --no-recreate \
--scale builder_rust_llvm_o2=2 --scale builder_rust_clift_o2=1 \
builder_rust_llvm_o2 builder_rust_clift_o2
RUST_BUILDER_REPLICAS applies to all twenty Rust services, including
those you left stopped, so raising it starts services you did not intend to
run. Scale per service and name only the services you want. Scaling a
service to zero removes its containers; its queue then accumulates tasks
until a consumer returns.
Watchdog loop
database and rabbitmq have no restart policy. assemblage_loop.sh
is what brings them back, so it should always be running:
mkdir -p var
nohup setsid flock -n var/loop.lock /bin/bash ./assemblage_loop.sh >/dev/null 2>&1 &
var/ must exist before flock runs, and the script should be invoked
through /bin/bash. Add a @reboot crontab entry so it survives
reboots, and check crontab -l after each reboot:
@reboot sleep 60 && cd /path/to/Assemblage && \
/usr/bin/flock -n var/loop.lock /bin/bash ./assemblage_loop.sh >/dev/null 2>&1
The loop only starts missing infrastructure containers; it never restarts a healthy broker or coordinator. Periodic mass restarts of workers are turned off: restarting about 50 builders at once caused a two-hour disk I/O storm.
Blocklist
backend/blocklist.txt lists owners, or owner/name repositories, that
are never dispatched, one per line, # for comments. The coordinator
re-reads it about every 30 seconds, so edits need no restart.
Failure modes
Fleet stranded, containers healthy. The coordinator starts a build
option’s dispatch thread only when a builder registers. After the
coordinator restarts on its own, connected builders keep their registrations,
never re-register, and are never dispatched to again. All build_opt_*
queues show consumers but zero messages, and max(build_date) stops.
Fix: restart the builder services; they re-register in under 20 seconds.
Do not restart the coordinator or RabbitMQ as routine maintenance.
midnight_recover.sh detects this state (builder registrations older
than the coordinator) and restarts the builders; it can run from cron.
Builders crash-loop after a host reboot. Infrastructure stays down (no
restart policy) while builders, which are unless-stopped, restart
forever against a RabbitMQ that is not there. Logs show
socket.gaierror: Temporary failure in name resolution. Fix:
docker compose up -d --no-recreate database rabbitmq coordinator.
Builds fail on missing system libraries. Errors naming openssl,
protoc, glib-2.0, wayland-client, libudev, libclang,
cmake, alsa or dbus are image packaging gaps, not bad
repositories. Add the package to docker/rust/Dockerfile and rebuild.
A builder sits at 100% CPU for a long time. DWARF extraction or an IR
dump, bounded by the DWARF_* limits above.
Storage layout
MinIO holds two buckets. project-archive keeps the source checkout of
each repository as {owner}/{project}/{sha12}.tar.gz with a
latest.txt pointer. artifacts keeps one directory per build, in the
same shape as the published dataset:
artifacts/{owner}_{project}_{sha12}-{flag}-{backend}-{mode}/
binaries/{name}.zst
metadata/assemblage_meta.json.zst
ir/{stage}.tar.gz
export.json
Builds stored before August 2026 use the older uncompressed prefixes
{owner}_{project}_{sha12}_{compiler}_{flag}/ and
..._rustc-{backend}_{mode}_{flag}/. Readers handle both;
backend/scripts/backfill_compress.py converts old builds in place after
verifying every object by checksum.
Publishing a corpus
Run on the host, in order. The scripts read MinIO on the host port (9010) and join repository URL, commit and license from PostgreSQL, because builders have no database access.
set -a; . ./secrets.env; set +a
python backend/scripts/export_corpus.py --out ./assemblage-rust
python backend/scripts/export_sources.py --src ./assemblage-rust --out ../assemblage-hf
python backend/scripts/pack_repos.py --src ./assemblage-rust --out ../assemblage-hf
python backend/scripts/refresh_release_meta.py --root ../assemblage-hf
python backend/scripts/upload_hf.py --folder ../assemblage-hf --repo <user>/<dataset>
export_corpus.pycopies each build into one directory. It exports only builds whose license is on the permissive allowlist; everything else, including unidentified licenses, is skipped before download.--all-licensesturns the gate off for private use.export_sources.pystages the source archive of every exported repository assources/{owner}__{name}.tar.gz.pack_repos.pybundles the builds of each repository into one uncompressed tar (Hugging Face rate-limits per file), writesindex.jsonl, and leaves binaries above 1 GiB uncompressed out of the tars. It rebuilds a tar only when its member set changed.refresh_release_meta.pyregeneratesLICENSES.csvand the counts in the datasetREADME.mdfromindex.jsonl.upload_hf.pyuploads withupload_large_folder, which resumes on its own. Hugging Face rejects a directory with more than 10,000 files; move the overflow intorepos2/andsources2/and runfix_overflow_paths.pyto re-point the manifests.
All stages can be interrupted and re-run.
Tests
export PATH="$HOME/.local/bin:$PATH"
uv run pytest tests/ # unit tests
uv run pytest tests/ -m integration # needs: docker compose up -d database
make e2e # golden-repo end-to-end gate (C and Rust)
Queue names, wire JSON (tests/fixtures/messages/), metadata keys and the
database schema are frozen. Migrations are handwritten; never commit
alembic revision --autogenerate output.
DeepHistory on Linux
DeepHistory builds fixed releases of C/C++ libraries listed in
deephistory/packages.json (1,155 package versions, each with GitHub
URL, commit and license). The Linux builder is standalone: no coordinator
or RabbitMQ, only MinIO.
docker compose up -d minio
docker compose -f compose/deephistory.yml up --build -d
docker compose -f compose/deephistory.yml up -d --scale gcc-O2=8
Each service is one compiler and flag (COMPILER gcc or clang,
COMPILER_FLAG -O0 to -O3). Progress is kept under
deephistory/ so runs can resume. The Windows/MSVC side of DeepHistory
uses Conan and is described on the Windows page.
Rebuilding from a recipe (legacy)
The linux_github branch (last updated May 2024) could rebuild a dataset
from a JSON recipe of repositories through the loadrepo command of its
cli.py. The recipe for the Linux dataset is still available:
linux_recipe.zip. Repositories may
since have been deleted or changed, and each one needs a full clone with
history, so the result is not identical. The current code has no recipe
loader.