Here’s how an ISO 27001 audit usually goes. Weeks before the auditor shows up, someone starts collecting screenshots. Configuration panels, firewall rules, a dashboard showing patches applied. Then come the Word documents describing what the systems are supposed to do. Then the change tickets, dug out of a ticketing system, each one referencing a vague “server maintenance” that nobody can fully reconstruct six months later. Everyone treats this as the cost of doing business. I did too, for years.

Then I ran Talos and NixOS long enough to notice something. The auditor’s whole job is asking “prove that the system is configured the way you claim it is.” And with declarative infrastructure, that question stops having a gap in it. The configuration and the proof are the same file.

That’s the argument I want to make here: the properties that made me choose these systems for sovereignty and resilience (immutability, reproducibility, a Git history you can actually trust) happen to be exactly what an auditor is fishing for. Compliance stops being a separate project. It falls out of doing infrastructure right.

What auditors actually ask

Strip away the framework-specific jargon and ISO 27001, SOC 2, NIS2, and PCI-DSS all circle the same four questions:

  1. What is your system configuration? (Asset management, configuration management)
  2. How do you control changes? (Change management, access control)
  3. Can you prove it? (Audit trails, evidence)
  4. Is it consistent? (No drift, no undocumented changes)

In a traditional shop, every one of these gets answered with paper. A Word document describing what should be configured. A screenshot proving it looked that way on one specific Tuesday. A change ticket pointing at “server updates” that may or may not have happened the way the ticket says.

The rot is in the gap. The document claims automatic updates are enabled. Nobody has checked whether that’s still true since the last reorg. The screenshot was accurate the day it was taken and tells you nothing about today. You’re not documenting the system. You’re documenting a memory of the system, and memories drift.

When the config is the documentation

Run Talos or NixOS and that gap closes, because the configuration is the system. There’s no separate document that can quietly disagree with reality, because the machine refuses to boot in any state other than the one you declared.

Talos

# This isn't documentation about the system.
# This IS the system.
machine:
  type: controlplane
  network:
    hostname: prod-control-01
  install:
    disk: /dev/sda
    image: ghcr.io/siderolabs/installer:v1.6.0
  sysctls:
    net.ipv4.ip_forward: "1"
  kubelet:
    extraArgs:
      rotate-server-certificates: "true"
cluster:
  clusterName: production
  apiServer:
    admissionControl:
      - name: PodSecurity
        configuration:
          defaults:
            enforce: "restricted"

When the auditor asks “how are your Kubernetes nodes configured?”, you hand them this. Not an approximation of the config, not last quarter’s snapshot of it. The exact, current, enforced state.

NixOS

{
  # Security hardening - this is both config AND documentation
  security.sudo.wheelNeedsPassword = true;
  security.auditd.enable = true;
  security.audit.rules = [
    "-w /etc/passwd -p wa -k identity"
    "-w /etc/group -p wa -k identity"
  ];

  # Firewall configuration
  networking.firewall = {
    enable = true;
    allowedTCPPorts = [ 22 443 6443 ];
    logRefusedConnections = true;
  };

  # Automatic security updates
  system.autoUpgrade = {
    enable = true;
    allowReboot = false;
    dates = "04:00";
  };
}

Every control sits in code, and the auditor knows it’s live because NixOS won’t build a generation that doesn’t match. There’s no “yes it’s configured but we disabled it temporarily and forgot.” The build is the enforcement.

Mapping declared state to the controls

The window is open. Now let me walk through how specific config maps to specific requirements, because “trust me, it’s all in Git” doesn’t pass an audit on its own. You have to do the mapping work.

ISO 27001: A.12.1.2 - Change Management

Requirement: Changes to systems shall be controlled.

Traditional approach: Change tickets, approval workflows, manual documentation.

Declarative approach:

  • All changes are Git commits
  • Pull requests require review
  • Merge to main triggers deployment
  • Git history IS the change log
# Your change management evidence
git log --oneline --since="2025-01-01" -- infrastructure/

2d8d00e Add NixOS vs Talos comparison
e49f5bd Add Talos Linux blog post
defb991 Switch image generation to DALL-E
...

Every change carries who made it, when, what changed, who approved it (the PR reviewers), and why (the commit message). A ticketing system gives you a worse version of the same thing, decoupled from the actual change.

ISO 27001: A.12.6.1 - Management of Technical Vulnerabilities

Requirement: Information about technical vulnerabilities shall be obtained and appropriate measures taken.

Declarative approach with Talos:

machine:
  install:
    image: ghcr.io/siderolabs/installer:v1.7.0  # Specific version
  • Exact OS version is in Git
  • Updating means changing version and deploying
  • You can prove which version ran at any point in time
  • Rollback is trivial if a vulnerability is found in a new version

NIS2: Article 21 - Cybersecurity Risk Management

Requirement: Appropriate technical and organizational measures to manage risks.

Declarative evidence:

{
  # Network segmentation
  networking.vlans = {
    management = { id = 10; interface = "eth0"; };
    production = { id = 20; interface = "eth0"; };
  };

  # Encryption at rest
  boot.initrd.luks.devices."encrypted".device = "/dev/sda2";

  # Access control
  services.openssh = {
    enable = true;
    settings = {
      PermitRootLogin = "no";
      PasswordAuthentication = false;
    };
  };
}

Each control is visible, auditable, and enforced.

SOC 2: CC6.1 - Logical Access Controls

Requirement: The entity implements logical access security software, infrastructure, and architectures.

This is the one I enjoy most. The Talos approach to logical access control is to not have a login.

  • No SSH access at all
  • All management through authenticated API
  • mTLS for all API communication
  • Role-based access via Kubernetes RBAC
# Talos machine config
machine:
  features:
    rbac: true  # Kubernetes RBAC enabled
  # No SSH configuration because SSH doesn't exist

The auditor can verify there’s literally no SSH daemon to break into. Access control is enforced by architecture, not by a policy document that says please don’t.

The audit that runs itself

Here’s where the gap really starts to close. Because everything lives in code, the evidence collection that used to eat weeks becomes a handful of scripts you run on a schedule.

Configuration drift report

#!/bin/bash
# For Talos: compare declared vs actual

for node in $(talosctl get members -o json | jq -r '.[] | .hostname'); do
  echo "=== $node ==="
  talosctl get machineconfig -n $node -o yaml > /tmp/actual.yaml
  diff infrastructure/talos/$node.yaml /tmp/actual.yaml
done

No output means zero drift, and that empty output is the proof. Try producing that with screenshots.

NixOS system closure

# Generate a complete list of all software and versions
nix-store -q --requisites /run/current-system | \
  xargs -I {} nix-store -q --binding name {} 2>/dev/null | \
  sort -u > system-inventory.txt

One command, a complete software inventory: every package, every library, every version actually running. Auditors who are used to chasing this by hand light up when you hand them a file.

Git-based audit trail

# Generate change report for audit period
git log --pretty=format:"%h|%an|%ad|%s" \
  --date=short \
  --since="2025-01-01" \
  --until="2025-12-31" \
  -- infrastructure/ > annual-changes.csv

Import into Excel, hand to auditor. Done.

Compliant all the time, not at audit time

There’s a deeper version of this. Pair declarative systems with GitOps and you stop being compliant in bursts around audit season. The cluster continuously corrects itself back to the declared state.

# ArgoCD Application for infrastructure
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: infrastructure
spec:
  source:
    repoURL: https://github.com/company/infrastructure
    path: kubernetes/
  destination:
    server: https://kubernetes.default.svc
  syncPolicy:
    automated:
      prune: true
      selfHeal: true  # Automatically fix drift

Someone makes a manual change at 2am during an incident? ArgoCD reverts it. The declared state is the only state the system will tolerate. So the question “were you compliant on March 14th” stops being a guess. The system was compliant on March 14th because it’s incapable of being anything else.

Doing the boring parts well

None of this works on its own. Declarative infrastructure makes great compliance evidence possible, but you still have to be disciplined about a few habits. Here are the ones that pay off.

1. Write meaningful commit messages

# Bad
git commit -m "Update config"

# Good
git commit -m "Enable audit logging per ISO 27001 A.12.4.1

- Added auditd configuration for file access monitoring
- Configured log retention for 90 days
- Addresses finding from Q3 security review

Ticket: SEC-1234"

Your Git history is going to be read by an auditor someday. Write it for that reader.

{
  # ISO 27001 A.12.4.1 - Event Logging
  security.auditd.enable = true;

  # ISO 27001 A.13.1.1 - Network Controls
  networking.firewall.enable = true;

  # NIS2 Art.21(2)(d) - Supply Chain Security
  nix.settings.require-sigs = true;
}

A comment linking a line of config to a specific control turns the audit from a scavenger hunt into a grep.

3. Pin every version

# Talos machine config
machine:
  install:
    image: ghcr.io/siderolabs/installer:v1.6.0  # Not :latest

Auditors want to know exactly what ran on a specific date. Pinned versions plus Git history gives you that answer for free; :latest throws it away.

4. Automate evidence collection

#!/bin/bash
# Quarterly compliance report generator

REPORT_DIR="compliance-reports/$(date +%Y-Q%q)"
mkdir -p $REPORT_DIR

# System inventory
nix-store -q --requisites /run/current-system > $REPORT_DIR/inventory.txt

# Configuration snapshots
cp -r /etc/nixos $REPORT_DIR/nixos-config/

# Change log
git log --since="3 months ago" -- infrastructure/ > $REPORT_DIR/changes.txt

# Drift check
./scripts/check-drift.sh > $REPORT_DIR/drift-report.txt

echo "Report generated in $REPORT_DIR"

Run it quarterly, archive the output, hand it over when the auditor shows up.

Be honest about the cost

I’m not going to pretend this is free. Getting here is real work. Talos and NixOS both have a learning curve that’s steeper than installing Ubuntu and clicking through a hardening guide. NixOS especially will fight you for the first month while you learn the language. You’re trading a familiar, forgiving setup for one that’s stricter and less forgiving up front.

There’s also the mapping work. The system gives you enforced, version-controlled state, but somebody still has to sit down and connect each piece of config to the control it satisfies. The auditor won’t do that translation for you, and neither will the tooling. That’s genuine effort, and on day one it can feel like you’ve added a compliance task rather than removed one.

The reason it’s worth it: that cost is paid once, up front, and then it amortizes across every audit forever. The traditional approach pays the screenshot-and-Word-document tax every single audit cycle, and the tax goes up as the system grows.

Closing the gap

You don’t have to convert the whole estate to find out whether this holds. Pick one cluster. Stand it up on Talos, put the config in Git, point ArgoCD at it with selfHeal on. Run it for a quarter and watch what your evidence collection looks like when audit time comes around. Then compare that against the team still building a slide deck out of screenshots.

What you’re really doing is making non-compliance structurally impossible rather than promising to avoid it. Traditional compliance asks people to behave correctly and then hopes the documentation matches. Declarative infrastructure removes the gap where the lie lives. The system can’t drift from its declared state, the history can’t be quietly rewritten, and the evidence generates itself as a side effect of running the thing.

That’s the same reason I run this stack for everything else: I want to understand and trust what I’m running, all the way down. Passing the audit is just what that trust looks like from the outside.