Hi everyone,
I’ve been working on nixos-artifacts for a while now, a small framework that gives you one common interface for managing secrets and generated files in NixOS — regardless of which backend stores them (agenix, sops-nix, …).
Status: experimental (like “nix flakes”
). The user-facing API is usable today; backend internals may still change as new features land, but those changes shouldn’t affect your declarations.
Why?
In larger setups, secret management often gets in my way when spinning up test scenarios. I lean heavily on NixOS modules, but they are not useful when I’m developing or updating modules. So I deploy test setups, but usually I don’t want to deploy real secrets (sometimes I do, e.g. API keys, but most of the time I don’t), So I needed to separate secret generation from secret serialization/deployment. nixos-artifacts pulls the declaration apart from the storage:
- You describe what the secret is and how it should be created (files, prompts, generator).
- The backend decides how it’s stored.
- You can mix backends per artifact in the same configuration.
Inspired by Clan vars and NixOS PR #370444.
What it looks like
A simple secret with a user prompt:
artifacts.store.test = {
files.secret = { };
prompts.test.description = "test input";
generator = pkgs.writers.writeBash "test" ''
cat $prompts/test > $out/secret
'';
};
A generated secret used by a service:
artifacts.store.attic = {
files.env = {
owner = "atticd";
group = "atticd";
path = "/var/lib/attic/secrets/env";
};
generator = pkgs.writers.writeBash "generate-attic" ''
cat >"$out/env" <<EOF
ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64=$(${pkgs.openssl}/bin/openssl genrsa -traditional 4096 | base64 -w0)
EOF
'';
};
services.atticd.environmentFile =
config.artifacts.store.attic.files.env.path;
Same declaration — change the backend, not the code.
Pick a default backend for the whole machine:
artifacts.default.backend = "agenix";
…or override it for a single artifact (e.g. keep most secrets in agenix, but
ship one through sops-nix):
artifacts.store.attic.backend = "sops-nix";
Status
Working today
agenix backend
Home Manager support
Per-machine and shared artifacts
CLI written in Rust
Planned
sops-nix backend
On-the-fly “dummy” backend — generates secrets at activation time, no persistence; great for testing more complex setups
Public / private artifact parts — e.g. a WireGuard pair where the public key is shareable and the private key is sealed by the backend
Artifact dependencies — one artifact can consume another’s output (CA chains, or config templates that need a generated secret in place)
Systemd integration — per-artifact units so your service can BindsTo=“this secret is present” and start/stop accordingly
The backend interface is small (check + serialize scripts), so adding new backends is straightforward. There’s a BACKEND_GUIDE if you want to write one.
What I’m looking for
- Feedback on the design — Does the abstraction make sense? What’s missing?
- Use cases — does this fit your setup? What would block adoption?
Thanks!