← all posts

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.