sops-nix without the panic
30 August 2025 · 5 min read
- NixOS
- sops
- Secrets
- Security
Why sops
sops encrypts values in place in YAML/JSON files that still diff nicely in
git. sops-nix decrypts them at NixOS activation time into /run/secrets/,
owned by root, optionally chmod’d to a service user. Your deployed machine is
a bundle of encrypted blobs plus a key reference.
The minimal setup
# .sops.yaml — use age keys, not globs
creation_rules:
- path_regex: secrets/.*.yaml$
age: age1qycv3xn2earr5l2zec9mub3xjnxn3tube5xsfws6n7t98c7z2yweqqfs8w Then in your NixOS config:
sops.secrets."db/password" = {
owner = "postgres";
sopsFile = ./secrets/db.yaml;
}; That’s it. On rebuild, /run/secrets/db/password exists, owned by postgres,
and your service unit does EnvironmentFile=... or reads via LoadCredential.
Things I learned the hard way
- Key rotation is not free. Rotate before you have to. Have a second admin key ready.
- The git history contains the plaintexts. If you committed before you sops-encrypted, consider history burned and rotate.
- Document what each secret is for. A README per
./secrets/file — “this key is consumed by which unit” — saves hours of guessing in a bad week. - Keys are infrastructure. Back up the age private key to a second medium, offline. If you lose it and the encrypted files exist, you’ve deleted prod.
Now go read the sops-nix README anyway — this post is the orientation, not the manual.