rdebootstrap is a manifest-driven Debian/Ubuntu bootstrapper written in Rust.
It resolves packages from user-defined APT archives, locks the full dependency
graph, stages arbitrary artifacts, and builds a root filesystem tree inside a
sandbox so maintainer scripts run in a controlled environment. The same engine
is exposed as the debrepo library for embedding in other tooling.
- Declarative input –
Manifest.tomllists archives, optional imports, specs, staged files, local.debs, and metadata whileManifest.<arch>.lockcaptures the fully resolved set for reproducible builds. - Deterministic resolution – Release and Packages files are fetched with GPG verification, optional snapshot pinning, and a solver that locks each spec before anything is installed.
- Sandboxed builds –
buildexpands packages inside an isolated helper namespace; run as root for production ownership or unprivileged while iterating. - Rich spec tooling – add/drop requirements and constraints per spec, stage local files or HTTP artifacts, include local packages that ship alongside the manifest, and reuse selected parent specs from another locked manifest.
- Fast, resumable downloads – concurrent fetcher (
-n/--downloads) backed by a shared cache and optional transport relaxations for air-gapped or test environments.
- Linux host with user namespaces enabled (required by the sandbox helper).
- Rust toolchain ≥ 1.89 (
rustup toolchain install 1.89.0). - System packages needed by
cargoplusgpgme/libgpg-errorfor Release verification. - Optional
sudowhen you need ownership preserved inside the target rootfs.
# clone this repo
cargo build --release
# or install into ~/.cargo/bin
cargo install --path .
# nix users
nix build .#rdebootstrapThe resulting binary lives at target/release/rdebootstrap (or in
~/.cargo/bin when installed).
- Create a manifest
rdebootstrap init debian --package ca-certificates --package vim
or bootstrap from another locked manifest:
rdebootstrap init --import ../system/Manifest.toml --spec base --package vim - Iterate on specs
rdebootstrap archive add https://mirror.example/debian --suite bookworm,bookworm-updates --components main,contrib
rdebootstrap require --spec desktop openssh-server network-manager
rdebootstrap forbid --spec desktop 'systemd-hwe (= 255.5-1)' - Reuse a locked base manifest (optional)
rdebootstrap import ../system/Manifest.toml --spec base --spec bootable-base - Update and lock
rdebootstrap update --snapshot 20241007T030925Z(or--snapshot now)
This downloads Release/Packages data, solves the specs, and writesManifest.<arch>.lock. - Build a filesystem tree
sudo rdebootstrap build --spec desktop --path ./out
The resulting tree may be used directly with podman (requires the full path because podman enters its own mount namespace):podman run --rm -it --systemd=always --rootfs "$(pwd)/out" bash -l
build unpacks packages into the target directory, stages artifacts, and runs
maintainer scripts in the sandbox so the host stays clean.
Manifest.toml sits at the project root unless --manifest <path> is supplied.
The lock file is always written in the same directory as the selected
manifest. For a manifest named <name>.toml, the lock file path is
<name>.<arch>.lock.
A small example with an imported base spec:
[import]
path = "../system/Manifest.toml"
hash = "blake3-..."
specs = ["base"]
[[archive]]
url = "https://ftp.debian.org/debian/"
suites = ["trixie"]
components = ["main"]
snapshots = "https://snapshot.debian.org/archive/debian/@SNAPSHOTID@/"
[artifact."motd"]
type = "text"
target = "/etc/motd"
text = "hello from rdebootstrap\n"
[spec.frontend]
extends = "base"
include = ["ca-certificates", "openssh-server"]
stage = ["motd"]
[[local]]
path = "target/debian/mytool_0.1.0_amd64.deb"
hash = "sha256-..."Key sections:
[[archive]]— APT repositories with suites, components, optional snapshot templates, trusted keys, and priorities.[import]— Reuse archives, local packages, and selected named parent specs from another manifest. Imported parent specs keep their own staged artifact references.pathandhashare required when present;specsis optional and only needed when exporting imported parent specs for downstreamextends.[[local]]— Local.debfiles copied into the cache and treated like repo packages.[artifact."<name>"]— Files or URLs to drop into the tree during staging.[spec]and[spec.<name>]— Package requirements/constraints, staged artifacts, build-time environment/script, and metadata per spec. Specs can inherit from each other viaextends.
rdebootstrap import writes [import], pins the imported manifest bytes in
hash, and validates the selected named specs. Imported archives are prepended
to the effective archive list, imported [[local]] entries join the effective
package universe, and inherited stage entries from imported parent specs keep
resolving their own imported artifacts. Downstream-local stage entries still
only resolve artifacts defined in the downstream manifest. Imported local paths
stay anchored to the imported manifest directory.
The downstream lock keeps only downstream-local archives and locals, plus
an imported-universe fingerprint for imported lock state. rdebootstrap update refreshes stale import metadata, re-solves specs when the imported
manifest or imported lock changed, and build refuses to run if the resulting
lock is missing or stale.
- By default caching is enabled and lives in
XDG_CACHE_HOME/rdebootstrapor~/.cache/rdebootstrapifXDG_CACHE_HOMEis unset. - Use
--cache-dir <dir>to point elsewhere or--no-cacheto disable it entirely. - Local artifacts are hashed relative to the manifest directory, so keeping manifests and artifacts in the same repository ensures stable paths.
- Content integrity is enforced via the hashes recorded in the lock file; disabling cache does not bypass verification.
Artifacts are declared at the top level as [artifact."<name>"] and referenced
from specs via stage = ["<name>", ...]. Use rdebootstrap artifact add to
define them and rdebootstrap stage to attach them to specs.
- Artifact
typeis one of:file,tar,dir,text. - Hashes are serialized in SRI form:
<algo>-<base64>(for exampleblake3-...,sha256-...). - When
rdebootstrapcomputes an artifact hash (for example viaartifact add), it usesblake3. TARGET_PATHis treated as an absolute path inside the target filesystem (non-absolute values are auto-prefixed with/during staging).{file|text}.ext /path/target→/path/target{file|text}.ext /path/target/→/path/target/file.ext
file.tar /path/target(/?)→ extracted under/path/targetdir /path/target(/?)→ copied under/path/target- Filename resolution for
{file|text}artifacts happens during staging; manifests keep the rawtargetvalue. - Auto-unpack: tar archives and compressed files (
.gz,.xz,.bz2,.zst,.zstd) are unpacked by default; use--no-unpackto keep them as-is. - Safety: tar unpacking rejects absolute paths,
..traversal, and special entries like device nodes. - Inline text artifacts (
type = "text") embed atextvalue in the manifest and write it totargetduring staging.rdebootstrap artifact add @filecreates a text artifact from a UTF-8 file (target path required).
Specs can set:
build-env— key/value environment variables applied to bothdpkg --configureandbuild-script.build-script— a bash script executed after package configuration. Scripts fromextendsare executed in order (base → derived).
Use rdebootstrap edit env / rdebootstrap edit script to edit these fields.
rdebootstrap build supports --executor sandbox (default) and
--executor podman. The executor matters mainly for rootless runs: sandbox
uses the built-in helper, while podman runs configuration inside
podman run --rootfs ... (which may require a working rootless podman
environment such as a valid XDG runtime directory).
init– bootstrap a manifest from vendor presets (debian,ubuntu,devuan), explicit archives, or--import <path>from another locked manifest.import– add or replace[import]using another already-locked manifest and export selected named parent specs.edit– edit the manifest (rdebootstrap edit) or spec metadata (edit env,edit script).archive add,deb add– append repositories or register a local.deb.require/forbid– add requirements or version constraints to a spec (include/excluderemain aliases).remove– remove requirements or constraints (dropremains an alias).artifact add,stage,unstage– define, add, or remove staged artifacts.update– refresh metadata, solve dependencies, and rewrite the lock file (supports--snapshot,--localsrefreshes local packages and local artifacts, and refreshes stored import fingerprints when[import]is present).list,search,spec,package,source– inspect resolved specs and package/source metadata.build– expand a spec into a directory, running maintainer scripts within the sandbox helper.
-a/--authselects the auth source: omit for optionalauth.tomlnext to the manifest, usefile:/path/to/auth.toml(or just a path), orvault:<mount>/<path>to read secrets from Vault.
Do not commit auth.toml to version control.
- Auth file (
auth.toml) supports per-host entries:
[[auth]]
host = "deb.example.com"
login = "user"
password = "inline" # or password.env / password.cmd
[[auth]]
host = "deb.other.com"
token = "token-string"
[[auth]]
host = "deb.tls.com"
cert = "relative/cert.pem" # relative paths are resolved from the auth file directory
key = "relative/key.pem"
# password/env/cmd/file are also supported for passwordspassword.env reads an env var, password.cmd runs a shell command (cwd = auth
file dir), and password.file/password.path load file content. Tokens and
cert/key accept the same source forms.
- Vault secrets: pass
--auth vault:<mount>/<path>(for examplevault:secret/data/repos). Each host lives at<mount>/<path>/<host>and contains JSON like:
{ "type": "basic", "login": "user", "password": "secret" }
{ "type": "token", "token": "token-string" }
{ "type": "mtls", "cert": "PEM string", "key": "PEM key (decrypted)" }VAULT_ADDR, VAULT_TOKEN, VAULT_CACERT, and VAULT_SKIP_VERIFY influence
the Vault client.
Global flags of note:
--manifest <path>selects an alternate manifest.--arch <arch>switches the target architecture (default: host arch).-n/--downloads <N>controls concurrent downloads (default: 20).--cache-dir/--no-cacheadjust caching.-k/--insecuredisables TLS certificate and hostname verification (not recommended).
Verification controls (scoped):
--no-verify(oninit,add archive,update) skips InRelease signature verification (not recommended).-K/--allow-insecure(on archive definitions forinitandadd archive, orallow-insecure = truein the manifest) fetchesReleaseinstead ofInRelease.
Run rdebootstrap <command> --help for exhaustive usage information.
- Staging/unpacking happens concurrently; this makes
rdebootstrapincompatible withdpkg-divertworkflows. -q/--quietand-d/--debugcurrently affect onlyrdebootstrapoutput, not the output ofdpkg --configureorbuild-script.
cargo fmt,cargo clippy --all-targets, andcargo testkeep the codebase healthy.cargo bench -p debrepo version(and other benches underbenches/) run Criterion benchmarks.- The crate can also be embedded directly by depending on
debrepoand drivingManifest/HostCachefrom your own host tooling.
Licensed under the MIT License.