nix
The nix module manages Nix packages and environments on any Linux distribution — not just NixOS. If Nix is not installed on the target, it can be auto-installed with install=#true, similar to how the container module handles runtime installation.
// Install a package (auto-install Nix if missing)nix "ripgrep" install=#true
// Run a command in an ephemeral Nix shellnix "python3 -c 'import sys; print(sys.version)'" { action "shell" packages { - "python3" }}
// Build a flake derivationnix ".#myapp" { action "build" out-link "/opt/myapp"}Actions
Section titled “Actions”The module dispatches on the action parameter. Default is "install".
install (default)
Section titled “install (default)”Install or remove Nix packages. For the default user profile, uses nix profile install with a fallback to nix-env. The idempotency check looks at both mechanisms, so a package installed via either is treated as satisfied.
nix "htop" install=#truenix "htop" state="absent"
// Install into the multi-user default profile (visible to any user / systemd).// Typically requires connecting as root.nix "htop" install=#true profile="default"
// Or an explicit profile pathnix "htop" profile="/nix/var/nix/profiles/custom"| Parameter | Type | Description |
|---|---|---|
| (positional) | string | Package name |
state | string | "present" (default) or "absent" |
install | boolean | Auto-install Nix runtime if missing |
profile | string | Target profile: "user" (default, ~/.nix-profile), "default" or "system" (/nix/var/nix/profiles/default), or an explicit path |
Run a command inside an ephemeral Nix shell with specific packages available. Packages are only present for the duration of the command — nothing is permanently installed.
The command is passed to bash -c inside the Nix shell, so pipes, redirections, variable expansion, and quoted arguments work as expected:
nix "python3 -c 'print(42)'" { action "shell" packages { - "python3" - "curl" }}
// Pipes, redirects, and shell syntax are preservednix "curl -sf https://example.com | jq . > /tmp/out.json" { action "shell" packages { - "curl"; - "jq" }}| Parameter | Type | Description |
|---|---|---|
| (positional) | string | Command to run (interpreted by bash -c) |
packages | list | Nix packages to make available. Entries containing # are treated as explicit flake refs (e.g. "github:user/repo#tool"); bare names become nixpkgs#<name>. |
install | boolean | Auto-install Nix runtime if missing |
Build a Nix derivation on the remote host.
nix ".#myapp" { action "build" out-link "/opt/myapp"}| Parameter | Type | Description |
|---|---|---|
| (positional) | string | Flake reference or derivation path |
out-link | string | Output symlink path (default: "result") |
install | boolean | Auto-install Nix runtime if missing |
channel
Section titled “channel”Manage Nix channels.
nix "nixpkgs-unstable" { action "channel" url "https://nixos.org/channels/nixpkgs-unstable"}
nix "old-channel" { action "channel" state "absent"}| Parameter | Type | Description |
|---|---|---|
| (positional) | string | Channel name |
url | string | Channel URL (required for state="present") |
state | string | "present" (default) or "absent" |
update | boolean | Run nix-channel --update after (default: true) |
install | boolean | Auto-install Nix runtime if missing |
flake-update
Section titled “flake-update”Update flake inputs on the remote host.
nix "/etc/nixos" { action "flake-update" input "nixpkgs"}| Parameter | Type | Description |
|---|---|---|
| (positional) | string | Flake directory path |
input | string | Specific input to update (optional — updates all if omitted) |
install | boolean | Auto-install Nix runtime if missing |
Garbage collect the Nix store.
nix "cleanup" { action "gc" older-than "30d"}| Parameter | Type | Description |
|---|---|---|
| (positional) | string | Label (not used functionally) |
older-than | string | Delete paths older than this (e.g. "30d") |
install | boolean | Auto-install Nix runtime if missing |
Auto-Install
Section titled “Auto-Install”When install=#true is set and Nix is not found on the target, the module installs it using the Determinate Systems installer. This works on all major Linux distributions.
step "Install tools via Nix" { // First task auto-installs Nix, subsequent tasks reuse it nix "htop" install=#true nix "ripgrep" nix "jq"}Running installed Nix tools from shell
Section titled “Running installed Nix tools from shell”After nix "…" install=#true places a binary in the user’s profile, it lives at ~/.nix-profile/bin/<tool> (or /nix/var/nix/profiles/default/bin/<tool> in the multi-user default profile). SSH non-interactive sessions do not source the profile scripts that put these on PATH, so a plain shell "mytool" will fail.
Three options, in order of preference:
-
Use the
shellmodule withlogin=#true— sources/etc/profileand picks up the Nix PATH automatically:shell "rg TODO" login=#true -
Call the absolute path — reliable and what you want inside
systemdunit files:shell "/nix/var/nix/profiles/default/bin/rg TODO" -
Use
action "shell"on this module for an ephemeral environment (no persistent install):nix "rg TODO" {action "shell"packages { - "ripgrep" }}
Using Nix-installed binaries in systemd units
Section titled “Using Nix-installed binaries in systemd units”Systemd runs commands with an empty environment and no PATH, so unit files must reference absolute paths. Two styles:
Follow the profile symlink — auto-upgrades when the package is reinstalled:
file "/etc/systemd/system/mytool.service" { content "[Unit]Description=My toolAfter=network-online.target
[Service]ExecStart=/nix/var/nix/profiles/default/bin/mytool --flagRestart=on-failure
[Install]WantedBy=multi-user.target"}systemd "mytool" state="started" enabled=#truePin the store path (reproducible but requires a unit rewrite on every upgrade) — resolve with readlink -f and write the exact /nix/store/<hash>-mytool-<ver>/bin/mytool path into the unit.
Idempotency
Section titled “Idempotency”- install: Checks if the package is already installed before acting. For the user profile the check matches either a
nix profileor a legacynix-envinstall; for a non-user profile (profile="default"/ custom path) only the profile manifest is consulted. - shell: Always runs (imperative command).
- build: Always rebuilds (cannot cheaply determine if the derivation changed).
- channel: Checks if the channel already exists.
- flake-update: Always runs.
- gc: Always runs.
Example
Section titled “Example”See the nix-deploy example for a complete plan using multiple Nix actions.