Get started
Start here
This page takes you from "I just cloned the repository" to "my first pull request is under review", one step at a time. After it you will have a checkout that builds, a binary you can run against isolated state, and a map of where your change goes and which rules it must respect. Every step says what to do, what you should see, and which page explains the details. Every rule on this page links to where it is written down — if a rule is not linked, it is not a rule.
If a word here is new to you, look it up in the glossary.
Day 0 in 30 minutes#
Day 0 follows the order of the handbook, but only takes what you need today from each part: the idea of the engine (IaaS and cloud native), the host it needs (Preparing your environment, which leans on the primitives in Linux foundations), a build and an isolated run (Clone, build and test, with every variable in Environment variables), and where things are (Project structure). Come back to the full pages when a step sends you there.
What Delonix is (5 minutes)#
Delonix Runtime is an engine that runs containers and microVMs on one node, together with the networking and storage they need. It is:
- declarative — you describe resources with its own Kinds (
delonix api-resourceslists them) and the engine plans and applies the difference; - daemonless — no background service is required; each command is a process that does its work and exits;
- rootless-first — the normal path runs as your own unprivileged user.
It does not know who uses it: no platform, tenant, account or billing concept exists in the code. Read IaaS and cloud native for where this places the engine in a cloud, and Architecture for how the boundary is enforced; for now, those four sentences are enough.
What you need (10 minutes)#
A Linux host with cgroup v2 and unprivileged user namespaces, the pinned Rust toolchain from
rust-toolchain.toml, and protoc on your PATH. The full list, and the host traps that look like
engine bugs, are in Preparing your environment. Read at least its
Known host traps section before step 5 below. If "user
namespace" or "cgroup delegation" are new words, the hands-on explanation is
Linux foundations — you do not need it to finish Day 0, but you will the
first time a limit does not apply.
Five commands that prove your setup works (15 minutes)#
Run these from the root of your checkout. If one of them does not give the shape shown, stop and fix it before going further — every later step depends on it.
1. Clone, with the tags.
git clone https://github.com/angolardevops/delonix-runtime.git
cd delonix-runtime
git fetch --tags origin
git describe --tags --abbrev=0 # prints the newest release, e.g. vX.Y.Z
The tags matter: the version gate and the contract gate compare your branch with them (Clone, build and test).
2. Build the CLI.
cargo build -p delonix-runtime-bin
Expected: Finished on the last line and a binary at target/debug/delonix. If it stops with a
message about protoc, install it (Preparing your environment).
3. Run the tests of a small, pure crate.
cargo test -p delonix-net-rules
delonix-net-rules has no dependencies at all (see its Cargo.toml), so this only proves that
your toolchain compiles and runs tests — nothing about the host. Expected: a line of the form
test result: ok. N passed; 0 failed.
4. Run the binary you just built.
./target/debug/delonix --version
./target/debug/delonix --help
Expected: --version prints delonix <version> on the first line, a one-line description of the
engine on the second, and then a line of the form commit: <sha> · built: <date> · <licence>;
between releases the commit: part also says how far the build is from the last tag
(+N commits since vX.Y.Z). A short get started: block follows. --help prints
Usage: delonix [OPTIONS] <COMMAND>, a Commands: list and a COMMAND MAP.
Always use ./target/debug/delonix, never a delonix found on your PATH — that one is an
installed release and is usually older (Preparing your environment).
5. Run one real command, fully isolated.
Anything beyond --help reads and writes engine state. Point both state variables at scratch
directories first — half isolation is worse than none
(Clone, build and test; what each variable
does is in Environment variables):
export DELONIX_ROOT=$HOME/scratch/dlx/root
export DELONIX_NET_RUNTIME_DIR=/tmp/dlx-run # keep it short: it holds unix sockets
mkdir -p "$DELONIX_ROOT" "$DELONIX_NET_RUNTIME_DIR"
./target/debug/delonix system info
./target/debug/delonix volume create hello
./target/debug/delonix volume ls
./target/debug/delonix volume inspect does-not-exist; echo "exit=$?"
./target/debug/delonix volume rm hello
Expected shapes:
$ delonix system info
Delonix Engine <version>
state root: <your $DELONIX_ROOT>
mode: rootless (daemonless)
cgroup2 delegated: yes | no
network infra: down (comes up on demand)
containers: 0 (0 running)
events: 0
$ delonix volume ls
NAME DRIVER MOUNTPOINT SIZE
hello local <your $DELONIX_ROOT>/volumes/hello/_data 0 B
$ delonix volume inspect does-not-exist; echo "exit=$?"
error no such volume does-not-exist
exit=4
What this proves: the state root: line is your scratch directory (so you are not touching real
state), the engine runs rootless, and errors carry a class in the exit code (4 = not found — see
Rust primer for this codebase). If
cgroup2 delegated: says no, container run refuses -m/--cpus/--cpu-weight in this
session (exit 69) and --cpuset/--io-weight have no effect; that is a host setting, explained in
Preparing your environment.
When you are done experimenting with networking later, tear the isolated network infra down with
the same two variables exported: ./target/debug/delonix net netns down.
Your first contribution, end to end#
1. Pick something#
- Look at open issues labelled
good first issueordocumentationon GitHub. Comment on the issue before starting, so two people do not do the same work. - For anything non-trivial — a new command, a new manifest Kind, a change to namespace or cgroup
setup, a new backend — open an issue first and agree on the approach
(
CONTRIBUTING.md, Contribution workflow). - Check open pull requests so you do not duplicate work already in flight.
Good first areas, because they are pure code with unit tests and no host privileges: a parser or
validator in the CLI crate, an error message that does not say what to do, a missing Portuguese
entry in bins/delonix-runtime-bin/data/pt.po, or a page of this handbook that is wrong.
2. Open a worktree from origin/main#
One task, one worktree, one branch — never edit a shared checkout, never put the worktree in /tmp
(One worktree per task):
git fetch --tags origin
git worktree add -b <topic>/<task> ../.worktrees/delonix-runtime/<task> origin/main
cd ../.worktrees/delonix-runtime/<task>
git log --oneline -- <path you will touch> # what was already decided or fixed there
Reading the history of the area first is part of the job: a lot of this code records things that were tried, measured and changed (Start from the latest tag).
3. Find where the change goes#
Use the decision tree in Where does my change go? below, then read the section of The crates for that crate. If a path in the table means nothing to you yet, Project structure explains every top-level directory, and why a crate's directory is its layer.
4. Write the test first#
- A new pure function (parser, validator, argument builder, plan) gets a unit test in the same file,
under
#[cfg(test)] mod tests. A test never touches the real state root: pass it a temporary directory — see Tests. - A bug fix gets a test that fails without the fix. Revert your fix once, run the test, see it fail, then restore the fix. A test that passes either way proves nothing.
- A change to namespaces, cgroups, the network holder or VM boot also needs a live run with the state isolated, because unit tests cannot reach those paths (Run the tests).
5. Run the local gates#
Every CI job has a local command, listed in The gates CI runs. At minimum, before asking for review:
cargo fmt --all --check
cargo clippy --workspace --all-targets --locked -- -D warnings
cargo test --workspace --locked --no-fail-fast
python3 scripts/lang_ratchet.py
python3 scripts/arch_fitness.py
python3 scripts/dev_docs.py --check
python3 scripts/version_gate.py
Add the ones that match what you touched (the CLI surface gates if you added a command, the contract
gate if you touched proto/, the docs generator if help text changed) — the table in
The gates CI runs says which.
6. Write the pull request#
Open it against main and fill in every section of
.github/PULL_REQUEST_TEMPLATE.md. The template has four
parts, and reviewers read all of them:
- What does this change do, and why? — the why; the diff already shows the what.
- How was this tested? — the gates you ran, and for runtime/namespace/cgroup/network code, the command you ran live and its output.
- Checklist — build/clippy/fmt/test clean; new user-facing strings in English wrapped in
po::t/po::tfwith a Portuguese entry inpt.po; every entry point of a command wired; unit tests for new pure functions; privilege boundaries called out. - Does this cross a privilege or namespace boundary? — user namespace mapping, the holder netns,
the control socket,
setns/unshare, or path handling driven by user or manifest input. If you are not sure, say so.
Say what was proved and what was not validated, and why (Commits and pull requests).
7. What reviewers check#
The code owners in .github/CODEOWNERS review every change. The review
checklist is in Coding conventions; the cloud-native standards a
change is measured against are in Cloud native standards. Read
both before you open the PR, not after the first round of comments.
8. After the merge#
Remove the worktree and the branch — the branch survives worktree remove:
cd ../../../delonix-runtime # from the worktree of step 2 back to the clone
git worktree remove ../.worktrees/delonix-runtime/<task>
git branch -D <topic>/<task>
Where does my change go?#
flowchart TD
Q{What are you changing?}
Q -->|a boundary: new daemon, new privilege,<br/>new provider or port, layer structure,<br/>node contract, schema stability| ADR[Write an ADR first<br/>docs/adr/]
Q -->|a CLI flag or subcommand| CLI[bins/delonix-runtime-bin/src/cmd/GROUP.rs]
Q -->|a manifest Kind or field| KIND[delonix-stack kinds.rs<br/>+ cmd/KIND.rs + schema.rs]
Q -->|network behaviour| NET{pure rule or dataplane?}
NET -->|pure: CIDR, bridge name, IPAM math| NR[delonix-net-rules]
NET -->|dataplane: holder, nftables, IPAM leases, CNI| SDN[delonix-sdn]
Q -->|images, registry, build| OCI[delonix-oci<br/>+ cmd/build.rs, cmd/image.rs]
Q -->|VMs| VM{local or remote?}
VM -->|Cloud Hypervisor / libvirt / cloud-init| DVM[delonix-vm]
VM -->|a remote management API| PROV[crates/providers/NAME<br/>implements VmBackend]
Q -->|what the kubelet sees| CRI[delonix-cri]
Q -->|a new crate| CRATE[LAYERS in arch_fitness.py<br/>+ crates/LAYER/ + root Cargo.toml]
PROV --> ADR
| Change | Where it goes (checked in the tree) | Read | Rule and its source |
|---|---|---|---|
| New CLI flag or subcommand | bins/ (one module per group); strings through cmd/po.rs with Portuguese in data/pt.po; manual text in cmd/manual_entries.rs; leaf list in scripts/ (scripts/). Pure validation of a run belongs in crates/ |
Adding or changing a CLI command, The CLI | LANG-01 (scripts/lang_ratchet.py); CLI surface gate (scripts/, scripts/); wire every entry point (CONTRIBUTING.md) |
| New Kind, or a field on one | The Kind's facts: FACTS in crates/. Its spec type and apply: bins/. Live-updatable fields: hot_fields in crates/. The schema: TYPED_KINDS in cmd/schema.rs, and the published docs/ (delonix manifest schema) |
Declarative reconciliation, delonix-stack |
Open an issue first (CONTRIBUTING.md); the schema is generated from the code (ADR-0007); tests in kinds.rs and schema.rs fail when a table is left out |
| Network behaviour | Pure rules with no I/O: crates/. Dataplane (holder, control socket, nftables, IPAM, CNI): crates/ (infra.rs, ipam.rs, cni.rs). The network step of container run: crates/. CLI: cmd/network.rs, cmd/net.rs, cmd/firewall.rs |
Container networking, delonix-sdn |
Rootless-first and no silent failure (Architecture rules); flag the privilege boundary in the PR (SECURITY.md) |
| Images, registry, build | crates/ (registry.rs, build.rs, cas.rs, overlay.rs); CLI in cmd/build.rs, cmd/image.rs |
Delonixfile and VMfile, delonix-oci |
Downloads are verified by digest (SECURITY.md, supply-chain scope) |
| Persisted state: a record field, a store, file locking, secrets at rest | Record types (Container, Vm): crates/; plain-data parts (Status, ContainerFw): crates/. How they are stored and locked (Store, JsonStore, write_atomic*, SecretStore, CredVault): crates/ (store.rs, secret.rs, cred_vault.rs) |
delonix-state, State on disk, Concurrency |
New record fields take #[serde(default)]; read-modify-write goes through update (State and concurrency) |
| VM behaviour on this node | crates/ (the VmBackend trait and the backend registry), cloudinit.rs; CLI in cmd/vm.rs, cmd/vmimage.rs, cmd/vmfile.rs |
Building microVMs, Traits as ports | ADR-0008 (backends are registrable) |
| A new VM backend or storage provider behind a remote API | A new crate under crates/providers/, implementing a port; registered at the composition root (cmd/vmbackends.rs) |
Providers, Layers | ADR first (When to write an ADR); ADR-0040 |
| CRI (what the kubelet talks to) | crates/ (runtime_svc.rs, runtime_svc/, streaming.rs) |
Kubernetes, delonix-cri |
ADR-0038 |
| A new crate | An entry in LAYERS in scripts/arch_fitness.py, a directory under crates/<layer>/, and its path in the root Cargo.toml [workspace. — all in the same commit |
Layers | scripts/arch_fitness.py (directory = layer, versions only in the root) |
| A decision that moves a boundary | docs/adr/NNNN-title.md, before the code |
When to write an ADR | docs/adr/README.md; accepted ADRs are superseded, never rewritten |
If your change does not fit any row, ask in the issue before writing code (see When you are stuck).
Rules you must not break#
Each rule is enforced by a gate, a review, or both. The link is where it is written.
| Rule | Source |
|---|---|
The engine knows no consumer. No product, platform, control plane, console or agent that uses the engine is named in crates/, bins/, proto/ or the manifests, comments included; no tenant, account, plan or billing. |
«Identidade e fronteira do motor» at the top of AGENTS.md. The named consumers are enforced by CONSUMER_NAMES in scripts/arch_fitness.py (a fixed list of names, matched by regular expression); the ban on tenant, account, plan and billing concepts is not matched by any gate and is checked in review |
| Daemonless. No resident process by default; a new one needs an ADR with evidence of what systemd could not do. | AGENTS.md (same section); Architecture rules |
| Rootless-first. The normal path runs unprivileged; privilege is an explicit, announced opt-in. A new privilege boundary needs a GO/NO-GO spike and an ADR. | AGENTS.md; When to write an ADR |
| Dependencies point inward, the directory is the layer, versions live only in the root. | ADR-0040; scripts/arch_fitness.py |
LANG-01: code is English. Identifiers, comments and messages in English; Portuguese only through pt.po. |
Language; scripts/lang_ratchet.py |
Version alignment. Do not change version in the root Cargo.toml in a feature PR; your branch must contain the newest tag. |
Version alignment; scripts/version_gate.py |
One worktree per task, outside /tmp, stage files by name, remove worktree and branch at the end. |
One worktree per task |
Never run the engine, the E2E battery or the chaos harness against real state. Export both DELONIX_ROOT and DELONIX_NET_RUNTIME_DIR; do not set E2E_SHARED_STATE=1 unless you are diagnosing your own host. |
Isolating the engine's state, E2E, chaos |
| Security-sensitive changes are called out, and vulnerabilities are reported privately, never in a public issue or PR. | SECURITY.md; Security-sensitive changes |
When you are stuck#
Look in this order — each step is cheaper than the next:
- This handbook. The README has the page list; the glossary explains the vocabulary.
AGENTS.md, organised by area. It is long and partly historical (and partly in Portuguese): use it to learn where to look, then confirm in the code.- The ADR index,
docs/adr/README.md— the decision behind a structure, and what was rejected. - The history of the file:
git log --oneline -- <path>andgit log -p -S '<symbol>'. Commit messages here explain why.
If you are still stuck, ask on GitHub:
- Comment on the issue you are working on, or open a new one with the feature request template (for questions about an approach) or the bug report template.
- A security problem goes through private vulnerability reporting, not an issue.
The bug report template asks for:
- the output of
delonix --version; - distro and kernel version, rootless or root, and whether you installed with
install.sh, downloaded a binary, or built from source; - the exact command or manifest that triggers it;
- what you expected, and the full, untrimmed output of what actually happened;
- whether it reproduces every time, sometimes, or only once;
- anything else that might be relevant.
This handbook additionally recommends two things the template does not ask, because they save a round trip:
- the whole
--versionoutput of the binary you ran, including thecommit:line (between releases every build reports the same version number, and only the commit tells them apart); - whether
DELONIX_ROOT/DELONIX_NET_RUNTIME_DIRwere set, and what you already read and tried (the page, theAGENTS.mdsection, the ADR).
Progress checklist#
- [ ] I read what Delonix is and the four principles (Architecture).
- [ ] My host meets Preparing your environment, and I read the known host traps.
- [ ]
cargo build -p delonix-runtime-binfinishes. - [ ]
cargo test -p delonix-net-rulesreportstest result: ok. - [ ]
./target/debug/delonix --helpworks, and I stopped using thedelonixon myPATH. - [ ]
delonix system infoshows my scratchDELONIX_ROOTas the state root. - [ ] I picked an issue and commented on it (or opened one for a non-trivial change).
- [ ] I work in my own worktree created from
origin/main. - [ ] I found where the change goes and read that crate's section in The crates.
- [ ] I wrote a test that fails without my change.
- [ ] The local gates from Clone, build and test pass.
- [ ] I read Coding conventions and Cloud native standards, layer by layer.
- [ ] My PR fills every section of the template, including what was not validated.
- [ ] After the merge, I removed my worktree and my branch.
Next: IaaS and cloud native — where the engine fits — the mental model of an IaaS, which of its layers this engine is, and how cloud native principles show up in its files.