Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
nerpa
One binary that holds cloud resources and the contents of the machines they run on in a single graph — and plans both together, in one pass.
nerpa plans infrastructure and configuration together, in one pass, and
applies the result over a transport that carries an identity rather than a
static key. It is built for a world where the operator is often not a person:
the plan is a typed document before it is a page of text, and a change can be
made to require a signature from somebody other than whoever proposed it.
Status: stages 0–2 complete, stage 3 next — the cloud.
nerpa applyconverges templated files, packages and services across a fleet of machines reached overssh(Alpine, Gentoo, Rocky), delivering its own executor to each; a second run has nothing to do, and a machine that is switched off is reported by name rather than stopping the run. What cannot be changed later holds: a secret never reaches the state file or the plan, a rename declared withmovedkeeps its identity, and a recorded configuration's plan cannot change without a declared change. No cloud yet — see the roadmap.
Quick start
$ cat main.star
node("localhost")
etc = resource("os:directory.etc@localhost", {"path": "/tmp/demo/etc"})
resource("os:file.app@localhost", {
"path": "/tmp/demo/etc/app.conf",
"content": "listen 80;\n",
"mode": "0640",
})
$ nerpa plan --as alice@example.com
2 changes: 2 to create
touches 1 node: localhost
as alice@example.com over run until further notice
stage 1 · target: localhost
[create ] os:directory.etc@localhost
~ path — → "/tmp/demo/etc"
[create ] os:file.app@localhost
~ content
becomes:
│ listen 80;
~ mode — → "0640"
~ path — → "/tmp/demo/etc/app.conf"
plan 4545395e0afc
$ nerpa apply --as alice@example.com
2 changes: 2 to create
touches 1 node: localhost
as alice@example.com over run until further notice
stage 1 · target: localhost
[create ] os:directory.etc@localhost
~ path — → "/tmp/demo/etc"
[create ] os:file.app@localhost
~ content
becomes:
│ listen 80;
~ mode — → "0640"
~ path — → "/tmp/demo/etc/app.conf"
plan 4545395e0afc
applied 2 changes
$ nerpa plan --as alice@example.com
nothing to do: the world already matches the configuration
Every run says who is acting in it (--as), and every run is a line in a
journal beside the state file for whoever reads afterwards. The journal is an
append-only hash chain — an entry rewritten, reordered or removed breaks it —
and nerpa journal verify walks from the first entry and says whether it
still holds.
Where a deployment keeps who writes a change and who may make it apart, the fleet names the approvers:
[]
= "/etc/nerpa/approvers"
The plan is proposed and then signed by somebody else, who needs nothing but an ssh key of their own:
$ nerpa plan --as alice@example.com --json > plan.json
$ nerpa sign --as carol@example.com --with-key ~/.ssh/plan-key plan.json
$ nerpa apply --as alice@example.com --approval plan.json.approval
Whoever proposed a plan cannot sign it, and an apply over a plan that moved since the signature is refused, naming what moved.
That number under the plan is a digest of everything the plan says and everything it was read from. A second run against an unchanged world produces it again; a world that moved underneath produces a different one, which is what makes approving a plan mean something.
What it is not
nerpa is not a Terraform provider host, and is not trying to become one.
Terraform's providers are Go binaries with full process rights: any network, the
filesystem, exec. They receive credentials as values, from a registry you do
not control. Accepting them as a runtime would mean accepting all of that, which
is the opposite of what this tool claims to be. Their schemas, on the other
hand, are JSON — a complete machine-readable description of every resource type
that somebody else spent years getting right. nerpa takes the schemas and leaves
the binaries.
Terraform therefore appears on the input side only: .tf converts, tfstate
imports, and neither takes part in a run afterwards.
It is also not a Kubernetes replacement, not a CMDB, and not a monitoring system.
Why
Provisioning and configuration have been two tools for fifteen years, and the seam between them is where the work goes: external data sources, provisioners nobody defends, two state mechanisms, two languages, and a plan that stops being true at the boundary. Both halves also carry a decade of accumulated answers to questions that no longer need asking.
nerpa is an attempt at the same job with those answers removed rather than
improved on. There is no count, so an index cannot shift and propose
destroying a live resource. There are no workspaces, no separate module
language, no initialisation step, and no twenty-two levels of variable
precedence. A dry run does not lie about what it could not check.
The one distinction the rest is built on is where a resource's state can be read: through an API, or only by running code on one particular machine. Naming that difference is what lets a VM and the packages installed on it sit in one graph, with real edges between them.
How it works
Configuration is a Starlark program. It is evaluated with no I/O, no clock and no network, so the same source always produces the same graph — which is what makes a plan worth signing. Values that do not exist yet are opaque handles that can be passed around but not branched on, so the set of resources is always known before anything happens, even when individual values are not.
Planning is therefore a single pass over the whole graph. Stages exist, but they belong to execution: a VM is created before it is configured because the graph says so, not because the planner had to go back and think again.
How it is governed
nerpa's decisions, specifications, invariants and work plan are compiled Rust
code in doc/ — decisions as adr!, specifications as rfc!,
invariants as typed records whose code anchors are checked both ways. A reference
to a record that no longer exists does not compile, so the trail that explains
why a change was made cannot rot silently. The honest surface of what is proven
versus what is believed lives in SECURITY.md; the commit rules in
CONTRIBUTING.md.
Documentation
| What you want | Where it lives |
|---|---|
| Decisions, specs, invariants and work | doc/ — the compiled registry |
| The roadmap, and what is done | ROADMAP.md |
| What the system is, as contracts | docs/ARCHITECTURE.md |
| Which document answers which question | docs/DOCUMENTATION.md |
| Where each kind of knowledge lives | docs/MAP.md |
| How a commit is written | docs/COMMITS.md |
| What a released version changed | CHANGELOG.md |
| How to report a vulnerability | SECURITY.md |
| How to contribute | CONTRIBUTING.md |
Building
$ cargo test
The toolchain is pinned in rust-toolchain.toml; rustup will fetch it on the
first build.
License
Apache-2.0. Use it, ship it, build products on it, run it as a service — none of that owes anybody anything.
That is a deliberate choice rather than an oversight. The provider ecosystem this tool is designed around has to be written by people who are not the author, and nobody writes a plugin while wondering whether it has become a derivative work.
Said plainly so that nobody has to guess later: the engine is Apache-2.0 and stays that way. A coordination plane — grants, third-party approval, the audit chain, policy for several operators at once — is planned as a separate, separately licensed component, because that is what an organisation pays for and a single engineer does not need. Contributions are taken under a CLA so that this split remains possible; see CONTRIBUTING.md.