Most Linux users install OpenClaw once and hope nothing breaks. NixOS users declare it, version-control it, and rebuild the exact same setup on any machine in 90 seconds flat. The tradeoff is real — you need to write a derivation. But that investment pays back every single time you need to reproduce your setup, migrate to new hardware, or roll back a bad update.
Why NixOS Makes Sense for OpenClaw
OpenClaw runs as a persistent gateway daemon. It needs a stable Node.js runtime, a gateway.yaml config, and environment variables for LLM API keys. On traditional distros, you manage these by hand. On NixOS, every one of these dependencies is declared in code.
The practical benefits are significant. When you upgrade NixOS, your OpenClaw setup upgrades with it — and if something breaks, nixos-rebuild switch --rollback brings back the previous working state. No manual package pinning, no version conflicts, no "works on my machine" surprises.
As of early 2025, OpenClaw is not available in nixpkgs. This means you write a derivation yourself. It's less than 30 lines of Nix code — and once written, it works forever.
Writing the OpenClaw Derivation
The simplest approach fetches OpenClaw from npm and wraps it in a Nix derivation. Create a file called openclaw.nix in your config directory:
{ lib, stdenv, nodejs_20, fetchurl }:
let
nodeEnv = nodejs_20;
in stdenv.mkDerivation rec {
pname = "openclaw";
version = "1.0.0";
src = fetchurl {
url = "https://registry.npmjs.org/openclaw/-/openclaw-${version}.tgz";
sha256 = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
};
buildInputs = [ nodeEnv ];
buildPhase = ''
mkdir -p $out/lib/node_modules/openclaw
cp -r . $out/lib/node_modules/openclaw
cd $out/lib/node_modules/openclaw
${nodeEnv}/bin/npm install --production --ignore-scripts
'';
installPhase = ''
mkdir -p $out/bin
cat > $out/bin/openclaw << 'EOF'
#!/usr/bin/env bash
exec ${nodeEnv}/bin/node ${out}/lib/node_modules/openclaw/bin/openclaw.js "$@"
EOF
chmod +x $out/bin/openclaw
'';
meta = with lib; {
description = "OpenClaw AI agent gateway";
license = licenses.mit;
platforms = platforms.linux;
};
}
Replace the sha256 placeholder with the actual hash. To get it, run nix-prefetch-url --unpack https://registry.npmjs.org/openclaw/-/openclaw-1.0.0.tgz and paste the result.
Flake Setup
A flake ties everything together and pins nixpkgs to an exact commit. Your flake.nix:
{
description = "NixOS system with OpenClaw";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, ... }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
openclaw = pkgs.callPackage ./openclaw.nix {};
in {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
{
environment.systemPackages = [ openclaw ];
}
];
};
};
}
Run nixos-rebuild switch --flake .#myhost from your config directory. Nix will build the derivation, install the binary, and apply your configuration.
Declaring the Systemd Service
Add this block to your configuration.nix to run OpenClaw as a persistent system service:
systemd.services.openclaw = {
description = "OpenClaw AI Agent Gateway";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
environment = {
OPENCLAW_GATEWAY_TOKEN = "your-token-here"; # use sops-nix in production
NODE_ENV = "production";
};
serviceConfig = {
Type = "simple";
User = "openclaw";
Group = "openclaw";
WorkingDirectory = "/var/lib/openclaw";
ExecStart = "${openclaw}/bin/openclaw gateway start";
Restart = "on-failure";
RestartSec = "5s";
StateDirectory = "openclaw";
};
};
users.users.openclaw = {
isSystemUser = true;
group = "openclaw";
home = "/var/lib/openclaw";
};
users.groups.openclaw = {};
After rebuilding, verify the service: systemctl status openclaw. You should see active (running). Logs go to the journal: journalctl -u openclaw -f.
Home-Manager Option
If you prefer OpenClaw scoped to a single user rather than system-wide, home-manager is the cleaner path. Add to your home-manager config:
home.packages = [ openclaw ];
systemd.user.services.openclaw = {
Unit = {
Description = "OpenClaw AI Agent Gateway";
After = "network.target";
};
Service = {
ExecStart = "${openclaw}/bin/openclaw gateway start";
Restart = "on-failure";
Environment = "NODE_ENV=production";
};
Install = {
WantedBy = [ "default.target" ];
};
};
Enable and start it: systemctl --user enable openclaw && systemctl --user start openclaw. This approach is ideal for multi-user NixOS machines where each user runs their own OpenClaw instance with separate gateway configurations.
systemd.user.startServices = "sd-switch" to your home-manager config. This starts and stops user services automatically when you run home-manager switch, eliminating the manual systemctl step.Managing Secrets Declaratively
Hardcoding OPENCLAW_GATEWAY_TOKEN directly in configuration.nix is a security mistake — the value ends up in the world-readable Nix store at /nix/store. Use sops-nix instead.
Install sops-nix as a flake input, create an encrypted secrets file with your token, and reference it in your service config:
sops.secrets.openclaw_token = {
sopsFile = ./secrets/openclaw.yaml;
};
systemd.services.openclaw.environment = {
OPENCLAW_GATEWAY_TOKEN_FILE = config.sops.secrets.openclaw_token.path;
};
The secret file is decrypted at runtime using your machine's SSH host key. The plaintext value never appears in the Nix store. This is the production-grade approach — anything else creates a secrets leak.
Common Mistakes on NixOS
- Wrong sha256 hash — always regenerate with nix-prefetch-url after version bumps, never copy-paste from other derivations
- Forgetting StateDirectory — without it,
/var/lib/openclawwon't exist and OpenClaw can't write its config files - Using nix-env instead of flakes — nix-env installs are not tracked in configuration.nix and break reproducibility
- Hardcoded secrets in configuration.nix — these land in the Nix store under /nix/store and are world-readable by all system users
- Missing network.target ordering — OpenClaw needs network access at startup; without
after = [ "network.target" ]the service may start before networking is ready
Frequently Asked Questions
Can I install OpenClaw on NixOS using nix-env?
OpenClaw is not in nixpkgs yet, so nix-env won't find it directly. Use a custom derivation that fetches the npm package or wraps the nodejs runtime. Flakes are the cleanest approach — pin nixpkgs, write a mkDerivation, and add it to your system packages.
How do I run OpenClaw as a NixOS systemd service?
Define a systemd.services.openclaw block in your NixOS configuration. Set User, WorkingDirectory, ExecStart to the openclaw binary from your derivation, and Restart to on-failure. Run nixos-rebuild switch and systemctl status openclaw to verify the service is active.
Does OpenClaw work with home-manager on NixOS?
Yes. Add a home.packages entry for your openclaw derivation and a systemd.user.services.openclaw block in home-manager config. This keeps OpenClaw scoped to your user rather than system-wide — useful for multi-user NixOS machines.
How do I store OpenClaw environment variables declaratively in NixOS?
Use environment.systemPackages for the binary and systemd.services.openclaw.environment for runtime vars like OPENCLAW_GATEWAY_TOKEN. For secrets, use sops-nix or agenix to encrypt values in your Nix config — never hardcode tokens in configuration.nix.
What Node.js version does OpenClaw need on NixOS?
OpenClaw requires Node.js 18 or newer. In your NixOS derivation, specify nodejs_20 from nixpkgs as the runtime. Pin nixpkgs to a stable channel that includes Node.js 20 LTS — nixos-23.11 or newer all include it.
How do I update OpenClaw on NixOS without breaking reproducibility?
Update the version string and sha256 hash in your derivation, then run nixos-rebuild switch. The old version stays in the Nix store until you run nix-collect-garbage. This gives you trivial rollback — just point your flake input back to the previous revision.