Day 35 — agenix comparison
Day 35 — agenix comparison
Stage IV · ~4h
Goal: Implement the same lab secret once with agenix, compare trade-offs to sops-nix, and declare a house default (this volume: sops-nix primary).
Comparison day ≠ dual-stack forever. After the lab, remove or disable the non-default tool so you maintain one system.
Why this day exists
Both sops-nix and agenix solve “encrypted secrets in git + decrypt on activation.” Teams pick based on:
| Factor | Why it matters |
|---|---|
| File format UX | YAML multi-secret files vs one age file per secret |
| Tooling | sops editor vs agenix CLI |
| Ecosystem fit | Docs you read; modules you copy |
| Complexity | Bootstrap and key distribution |
You should not choose from Twitter vibes alone. Touch both once.
Theory 1 — agenix mental model
agenix (and compatible forks/community usage) roughly:
| Concept | Meaning |
|---|---|
secrets.nix |
Maps secret files → public keys allowed to decrypt |
*.age files |
Ciphertext in repo |
age identities |
Private keys on machines/admins |
| NixOS module | age.secrets.<name>.file = ./secret.age; → runtime path |
Flow
echo 'PLACEHOLDER' | agenix -e secret.age
git add secret.age secrets.nix
nixos-rebuild → decrypt to /run/agenix/... (path scheme per version)
service reads path
Theory 2 — Flake input sketch
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
agenix.url = "github:ryantm/agenix";
agenix.inputs.nixpkgs.follows = "nixpkgs";
};
# modules list:
# inputs.agenix.nixosModules.defaultPin and follows like any other input (Day 24 discipline).
Theory 3 — secrets.nix shape
# secrets/secrets.nix — public keys only
let
# replace with your real age public keys
admin = "age1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
host = "age1BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
in
{
"lab-demo-token.age".publicKeys = [ admin host ];
}No private keys in this file.
Theory 4 — NixOS declaration
{ config, inputs, ... }:
{
imports = [ inputs.agenix.nixosModules.default ];
age.secrets.lab-demo-token = {
file = ../../secrets/lab-demo-token.age;
# mode / owner as needed
};
# config.age.secrets.lab-demo-token.path → runtime path
}Consumer pattern matches Day 34: path at runtime, never embed content in derivations.
Theory 5 — Side-by-side comparison
| Dimension | sops-nix | agenix |
|---|---|---|
| Multi-key structured file | Natural (YAML tree) | Usually one file per secret |
| Edit UX | sops file in-place decrypt editor |
agenix -e |
| Creation rules | .sops.yaml |
secrets.nix publicKeys |
| HM integration | Strong story | Available patterns |
| Learning transfer | SOPS used outside Nix too | Age-centric, Nix-flavored |
| House default (this book) | Primary | Compare once |
When agenix might still win for you
- You want the simplest possible “one secret, one file” model
- Your team already standardized on agenix
- You dislike SOPS YAML tooling
When sops-nix wins for this curriculum
- Multiple secrets per environment file
- Same SOPS skills in non-Nix contexts
- Syllabus / house default consistency
Theory 6 — Dual-running risks
Running both modules on one host:
- Two key bootstraps
- Two runtime directories
- Cognitive load on incident response
Acceptable for one lab day. Not acceptable as permanent undefined state—pick a default by end of day.
Theory 7 — Declaring house default
Write in docs/secrets.md:
## House default
**sops-nix** for all new secrets.
agenix: lab comparison only; module removed after Day 35
(or: we chose agenix because … — only if you override the book).If you override the book, do so explicitly so future chapters’ sops examples are translated consciously.
Worked example — Same placeholder secret both ways
| Tool | Artifact |
|---|---|
| sops | secrets/lab.yaml key lab.demo_token |
| agenix | secrets/lab-demo-token.age |
Same placeholder value during local edit; different ciphertext formats in git. Runtime paths differ; consumer scripts should use module-provided .path.
Lab 1 — Add agenix input
# flake input + lock update agenix only
nix flake update agenix
nix flake metadataLab 2 — Public keys and secrets.nix
Reuse public age keys from Day 34 if possible (same admin/host). Write secrets/secrets.nix with publicKeys only.
Lab 3 — Encrypt one age secret
Using current agenix/age CLI docs for your pin:
# conceptual — see agenix README for exact flags
nix shell github:ryantm/agenix#agenix nixpkgs#age
# agenix -e secrets/lab-demo-token.agePut only a lab placeholder value inside.
Lab 4 — NixOS module + rebuild
Declare age.secrets.…, rebuild, list runtime secrets dir for agenix:
sudo nixos-rebuild switch --flake .#lab
sudo ls -la /run/agenix 2>/dev/null || sudo ls -la /run/secrets 2>/dev/null || trueConfirm path exists; do not paste secret contents into notes.
Lab 5 — Consumer parity
Point a oneshot (or temporary) at config.age.secrets.lab-demo-token.path similar to Day 34. Prove readable by the intended user.
Lab 6 — Comparison write-up
One page in the journal:
- Bootstrap friction: sops vs agenix
- Day-2 secret add friction
- Multi-secret ergonomics
- Failure modes observed
- Decision: house default = …
Lab 7 — Decommission the non-default
If house default is sops-nix:
- Remove agenix module from active host imports
- Optionally leave encrypted
.agefile as historical artifact or delete
- Remove agenix input or keep input unused (prefer remove to simplify lock)
- Rebuild clean
Opposite steps if you chose agenix (then translate later chapters).
Common gotchas
| Symptom / mistake | What to do |
|---|---|
| Private key in secrets.nix | Remove; rotate |
| Wrong public key list | Re-encrypt with correct recipients |
| Both tools half-configured | Finish Lab 7 |
| Assuming identical paths | Always use .path from the module |
| Editing ciphertext as plaintext | Use tool entrypoints only |
| Forgetting follows on agenix input | Fix lock discipline |
Checkpoint
- agenix encrypt + decrypt-on-activate worked once
- Comparison table completed in your words
- House default declared in
docs/secrets.md
- Non-default tool removed or clearly disabled
- Still no plaintext secrets in git
- Rebuild succeeds with chosen stack only
Commit
git add .
git commit -m "day35: agenix comparison; house default documented"Write three personal gotchas before continuing.
Tomorrow
Day 36 — Hardening baseline: SSH, sudo, disable unused services, careful kernel defaults—as a small reusable module.