Skip to content

ADR-001: Contributory Infrastructure Pattern

Status: Accepted Date: December 9, 2025 Context: NixOS homelab configuration

Context

As the homelab grew to 80+ services, a pattern emerged where services needed to integrate with shared infrastructure (reverse proxy, backup, monitoring, alerting). The traditional approach of configuring everything in infrastructure modules led to:

  • Large, monolithic infrastructure files
  • Merge conflicts when multiple services changed
  • Difficulty understanding what a service requires
  • Risk of forgetting to add backup/monitoring for new services

Decision

Adopt a contributory pattern where services declare their infrastructure needs, and infrastructure modules aggregate these contributions.

Pattern Structure

# Service module (e.g., modules/nixos/services/sonarr/default.nix)
config = lib.mkIf cfg.enable {
  # Contribute to Caddy reverse proxy
  modules.services.caddy.virtualHosts.sonarr = {
    hostName = "sonarr.example.com";
    backend = { port = 8989; };
  };

  # Contribute backup policy
  modules.backup.sanoid.datasets."tank/services/sonarr" = {
    useTemplate = [ "services" ];
  };

  # Contribute alert rules
  modules.alerting.rules."sonarr-down" = {
    expr = ''container_service_active{name="sonarr"} == 0'';
    severity = "high";
  };
};
# Infrastructure module (e.g., modules/nixos/services/caddy/default.nix)
options.modules.services.caddy.virtualHosts = lib.mkOption {
  type = lib.types.attrsOf virtualHostType;
  default = {};
  description = "Virtual hosts contributed by services";
};

config = lib.mkIf cfg.enable {
  # Generate Caddyfile from all contributions
  services.caddy.configFile = generateCaddyfile cfg.virtualHosts;
};

Consequences

Positive

  • Self-documenting: Reading a service module shows all its infrastructure requirements
  • Reduced merge conflicts: Services don't share files
  • Completeness: Adding infrastructure is part of adding a service
  • Discoverability: rg "modules.alerting.rules" finds all alerts

Negative

  • Requires documentation: Pattern is non-obvious to newcomers
  • Discovery tools needed: Must use grep/rg to find all contributions
  • Implicit dependencies: Service depends on infrastructure module existing

Mitigations

  • Document pattern in docs/repository-architecture.md
  • Provide examples in docs/modular-design-patterns.md
  • Use consistent option naming across all contributory systems

Systems Using This Pattern

System Option Path Purpose
Caddy modules.services.caddy.virtualHosts.* Reverse proxy routes
Backup modules.backup.sanoid.datasets.* ZFS snapshot policies
Alerting modules.alerting.rules.* Prometheus alert rules
Gatus modules.services.gatus.contributions.* Health check endpoints
Grafana modules.services.grafana.integrations.* Datasources and dashboards
Impermanence modules.system.impermanence.directories Persistence paths