Root Expression
The root expression is a deployment’s entry point: a single Nix expression describing all of your resources and how they connect. nixops4 evaluates it to learn what your deployment consists of.
It must evaluate to a root component, which you normally create with nixops4.lib.mkRoot:
nixops4.lib.mkRoot {
modules = [
({ providers, members, ... }: {
# your providers and resources go here
})
];
}
You rarely load it by hand; the nixops4 command discovers it for you, from either a flake or a plain Nix file.
Where NixOps4 looks
nixops4 selects the root source in this order:
--file <PATH>, if you pass it.nixops4.nixin the current directory, if that file exists.- Otherwise, the flake in the current directory.
The first match wins: a nixops4.nix is used even when the directory also contains a flake.nix.
From a flake
In this case, NixOps4 reads the current directory’s flake and takes its nixops4 output attribute, which must be a root component.
An easy way to produce that output is the flake-parts module, which lets you write your deployment as a module and wires up the nixops4 output for you:
{
inputs.nixops4.url = "...";
outputs = inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [ inputs.nixops4.modules.flake.default ];
nixops4 = { providers, members, ... }: {
imports = [ ./deployment.nix ];
providers.local = inputs.nixops4.modules.nixops4Provider.local;
members.myDeployment = {
# ...
};
};
};
}
Check that:
- you don’t call
mkRoot— the module does it for you nixops4is at the top level, notflake.nixops4
Without flake-parts, produce the nixops4 output directly:
{
inputs.nixops4.url = "...";
outputs = inputs@{ ... }: {
nixops4 = inputs.nixops4.lib.mkRoot {
modules = [
./deployment.nix
({ providers, members, ... }: {
providers.local = inputs.nixops4.modules.nixops4Provider.local;
members.myDeployment = {
# ...
};
})
];
};
};
}
Check that:
nixops4is at the top level- you do call
mkRoot mkRoot’s argument is{ modules = [ … ]; }— your module goes in themoduleslist, not as the argument itself
Overriding flake inputs
When loading from a flake, you can swap an input without editing flake.nix:
$ nixops4 apply --override-input nixpkgs ~/src/nixpkgs
It takes an input attribute path and a flake reference, and applies only to flake loading — so it cannot be combined with --file or a discovered nixops4.nix.
From a file
Point NixOps4 at a plain Nix file with --file, or place a nixops4.nix in the current directory:
$ nixops4 apply --file ./my-deployment.nix
The file must evaluate to a root component, just like the flake’s nixops4 output.
A plain file has no inputs, so NixOps4 does not inject nixops4.lib; the file must obtain nixops4 itself, for instance by importing it:
let
nixops4 = import ./nixops4-deps.nix;
in
nixops4.lib.mkRoot {
modules = [
({ providers, members, ... }: {
providers.local = nixops4.modules.nixops4Provider.local;
members.myDeployment = {
# ...
};
})
];
}
For the same reason, flake-specific flags like --override-input don’t apply in file mode; passing them with a discovered nixops4.nix is an error.