Services and security patterns

Updated

July 30, 2026

Services and security patterns

This part of the standalone NixOS book is organized by topic (normal book chapters), not by calendar day.

Read chapters in order within the part when learning; skip or skim freely when using the book as a reference.

Baseline: NixOS / nixpkgs 26.05, flakes, Home Manager as NixOS module (from the previous part), deliberate firewall and secrets discipline.


What this part is for

Layout and Home Manager made the repo operable. This part makes a service slice operable and defensible:

Theme Chapters
Secrets threat model The secrets problem
Primary secrets tooling sops-nix
Comparison alternative agenix
Host attack surface Host hardening patterns
Network policy Firewall discipline
HTTPS front door TLS and reverse-proxy
Durable state A data service pattern (PostgreSQL)
Isolation literacy NixOS containers
OCI industry path Podman and Docker patterns
See what is running Observability patterns
Optional k8s awareness k3s (optional)

After this part you should be able to run a small app path—proxy → backend → database—with encrypted secrets, documented ports, and a monitoring or logs pattern—without secrets in the Nix store.


Prerequisites

  • Working flake host with hosts/ · modules/ · homes/ layout (or equivalent discipline)
  • Lock policy and selective updates
  • Comfort with systemd units and journalctl
  • Firewall basics from the host networking chapter
  • No reliance on plaintext passwords in the flake

Chapter map

Chapter Focus Outcome
The secrets problem Store ≠ vault Audit + remove anti-patterns
sops-nix Encrypt in git; path at runtime End-to-end secret consumer
agenix (comparison) Same job, different UX House default declared
Host hardening SSH, sudo, surface Hardening module + recovery notes
Firewall discipline Deny by default docs/ports.md living matrix
TLS front door Caddy or Nginx + ACME/lab TLS 443 front; localhost backends
Data service PostgreSQL pattern State, auth, backup story
NixOS containers nspawn containers.* Isolation decision record
Podman/Docker Declarative OCI Localhost publish + comparison
Observability Metrics and/or logs Runbook or Prometheus path
k3s optional Skip or lite install Explicit decision artifact

Mental model — service slice

 clients
    │
    ▼
 :443 reverse proxy (TLS)  ── modules/services/proxy-*.nix
    │
    ▼
 127.0.0.1:app             ── backend unit / OCI / nspawn
    │
    ▼
 127.0.0.1:5432            ── PostgreSQL (localhost)
    │
 secrets via sops-nix      ── /run/secrets/... paths only
 firewall                  ── 22, 80, 443 (documented)
 observability             ── journald and/or Prometheus localhost
Layer Default posture
Edge TLS terminate; minimal open ports
App Bind localhost; no public DB
Secrets Ciphertext in git; decrypt at activation
State Mutable dirs outside store; backup plan
Observe Localhost metrics or excellent logs

Suggested module layout (extends previous part)

modules/
  common/
    hardening/
    firewall.nix
    sops.nix
  services/
    proxy-caddy.nix   # or proxy-nginx.nix
    postgres.nix
    observability.nix
    podman.nix
    oci-hello.nix
secrets/
  lab.yaml            # sops-encrypted
.sops.yaml
docs/
  secrets.md
  ports.md
  proxy.md
  data-service.md
  isolation.md
  observability.md

How to work this part

  1. Threat model and cleanup before tooling
  2. sops-nix end-to-end before real service passwords
  3. agenix once for comparison; pick a house default
  4. Hardening + firewall before public exposure
  5. Proxy + one data service as the spine
  6. Containers literacy without containerizing everything
  7. Observability so the slice is inspectable
  8. k3s only if justified—or skip with a written decision

Prefer small commits: secrets infra, then hardening, then each service.


Cross-cutting conventions (26.05)

Topic Convention
Secrets primary sops-nix (agenix compared once)
Secret consumption Runtime paths, never Nix strings of secret values
DB / metrics / backends Listen on 127.0.0.1 unless documented otherwise
Proxy One of Caddy or Nginx
OCI Prefer Podman; localhost port publishes
Ports Living docs/ports.md
ACME tokens / DNS API sops only

Security non-goals (this part)

Non-goal Note
Full CIS benchmark automation Patterns over checklists
Multi-AZ HA databases Single-node honesty
Corporate SSO platform Mentions only
“Containers make it secure” Kernel shared; be honest
Required Kubernetes Optional chapter

Skills checklist (end of part)

  • Can explain why store ≠ vault in one minute
  • sops-nix: encrypt, activate, consume path
  • House secrets default documented; dual-stack cleaned up
  • Hardening module: no password SSH, recovery path written
  • Firewall on; every open port in docs/ports.md
  • TLS (or documented lab alternative) reverse proxy
  • PostgreSQL (or one DB) with backup/restore story
  • Isolation decision (native vs nspawn vs OCI)
  • One declarative OCI container or justified refusal
  • Observability path: metrics and/or logs runbook
  • k3s decision recorded (skip is first-class)

Reading strategies

Mode Approach
Spine first secrets → sops → harden → firewall → TLS → postgres → observability
Compare tools sops vs agenix; nspawn vs OCI; metrics vs logs
Audit existing host secrets problem + firewall + ports.md first
Resource-constrained skip k3s; logs-first observability; native modules over k8s

Relationship to other parts

Earlier This part uses
Host networking / firewall intro Deepens discipline
Systemd via Nix Service units, ordering
Flake layout / HM / locks Where modules and inputs live
Later: ops, packaging, capstone Deploy, tests, multi-node, wipe proofs

Lab host assumptions

Examples use host lab, user alice, system x86_64-linux, domains like app.example.invalid (replace with real DNS or lab TLS). Never paste production secrets into the book examples or your git history.


Next parts

Packaging, ops and fleet, and capstone assume you can keep secrets out of the store, open only justified ports, and rebuild a service slice from the flake. If this part is thin, those parts become theater.