Contracts and data flow
This page tells how an operator configuration becomes a deployment, and how contracts connect the software that needs a service to the software that gives it. The data model and hosting resources pages are the option references for the same parts. The concepts page defines the terms.
From a configuration to a deployment
flowchart
subgraph configuration
application-config
end
subgraph resource[resource module]
request
policy
end
subgraph application[application module]
application-options{{application-options}} --> application-config
application-config --> config-mapping
request{{request}} --> config-mapping
config-mapping(config-mapping)
end
subgraph fediversity[fediversity setup]
config-mapping -->|required resources| resource-mapping(resource-mapping)
policy{{policy}} --> provider-config -->|available resources| resource-mapping -->|configuration| deployment{deployment}
end
maintainer((maintainer)) -->|curates| resource
contributor((developer)) -->|curates| application
operator((operator)) -->|enters| configuration
hosting-provider((hosting provider)) -->|maintains| fediversity
In the diagram, a circle is an actor, a box with angled sides is a type, a rectangle is a value, a box with round corners is a function, and a diamond is a state. An arrow points to the part that uses the other part. An application module is a component that the operator can select.
- The operator selects the applications and sets their options in the panel.
The panel form comes from
frontendConfiguration, which has only the operator-facing components. The result is aconfiguration. - Each enabled component maps its options to the resources that it needs. The
implementationof a component, aconfig-mapping, returns one request for each resource, keyed by an instance name. For example, Forgejo requests a TLS certificate as"tls".ssl, its secrets as"age".secrets, and outbound mail as"forgejo-smtp".smtp. - The hosting provider declares an environment. Its
resourcesset the policy of each resource: which provider gives the resource, and with which limits. - The
implementationof the environment, aresource-mapping, collects the requests of all components. For each resource, theprocessfunction of the policy turns the requests into NixOS modules or OpenTofu resources. - The result is a deployment for an effect, for example a set of Incus containers that OpenTofu creates and NixOS configures.
environments.<env>.deployment does all of these steps for one deployment.
Resources and contracts
Fediversity uses two levels of interface:
- A resource is the interface between a component and a hosting provider.
The component sets the
request, and the hosting provider sets thepolicy. The request/policy resource pattern on the data model page tells more, and the hosting resources page lists the request options of each resource. - A contract is the interface between a consumer module and a provider
module of a service, for example
smtp,postgresql,ssloroidc. It follows RFC 189. A resource usually uses one or more contracts to give what a request asks for.
For example, Forgejo asks the smtp contract for outbound mail with a sender
address. The mail relay that provides the contract answers with a host, a port,
a user name and a password file:
A contract has these parts, under config.contracts.<type>:
want.<consumer>.<instance>.request-- what a consumer asks for.providers.<name>-- the providers that can answer the request.defaultProvider-- the provider that answers when the consumer selects none. A want that no provider answers fails the evaluation.providerRequests.<provider>-- the requests that a provider reads.results.<consumer>.<instance>-- what the consumer reads back, for example a host, a port and a password file.
Each contract type defines the shape of its request and its result. The consumer and the provider agree only on these shapes, so a hosting provider can replace a provider without a change to the consumer.
Contracts across nodes
Each node of a deployment is a separate NixOS evaluation, so the nodes do not
share config.contracts. When the consumer and the provider are on different
nodes, crossNodeContract and crossNodeWant in core/lib/lib.nix evaluate
the contract once, and give the result to each node. A contract that only gives
coordinates, such as the address of a DNS server, does not need this.
What a cross-node contract costs tells when to use one.
Where the code is
| Part | Location |
|---|---|
| The data model | core/lib/data-model.nix |
| The resources | core/resources/ |
| The components | core/components/ |
| The environments | core/setups/hosts-common.nix |
| The effects | core/effects/ |
| The contracts machinery | nix/contracts/lib/contracts/module.nix |
| The contract types | nix/contracts/lib/contracts/definitions/ |
| The cross-node helpers | core/lib/lib.nix |
The contracts guide of the vendored contracts tree tells how to write a consumer, a provider and a new contract type.
Further reading
- RFC 189, the contracts RFC, and its implementation in Self Host Blocks.
- nixpkgs pull request 506343, which adds default providers, providers outside NixOS, contracts across nodes, and introspection to make a form.
- Talks on contracts: NixCon NA 2024 and NixCon 2024.
- The configuration data flow of the Fediversity architecture.
- The data model review of 2025-07-03.