Zo wordt er in 2026 nog steeds veel infrastructuur gebouwd. Iemand opent een cloud console, klikt door een wizard, kiest wat defaults, en er verschijnt een resource. Het werkt. Het dashboard wordt groen. Iedereen gaat door met de volgende taak.
Zo kan ik niet werken. Als ik op een knop klik en er verschijnt infrastructuur, voelt het alsof ik het geleend heb. Ik wil zien wat er gebeurt, ik wil de configuratie ergens opgeschreven hebben waar ik het terug kan lezen, en ik wil weten waarom iets bestaat in plaats van alleen dat het bestaat. De console geeft me een groen vinkje. Begrip geeft hij niet.
Dat is de kloof waar ik het over wil hebben: de afstand tussen infrastructuur die je overkomt en infrastructuur die je echt in je hoofd kunt houden.
Wat click-ops echt kost
Klik je naar een werkend systeem, dan krijg je een werkend systeem. Je krijgt er ook een stapel beslissingen bij die niemand heeft opgeschreven.
Elke resource die via een GUI ontstaat draagt instellingen die je niet bewust koos, dependencies die je nooit zag vormen, en state die zich stilletjes ophoopt waar je hem niet kunt auditen. Op dag één doet dat geen pijn. Het doet pijn zes maanden later, als iemand vraagt waarom een security group 47 regels heeft, of die orphaned load balancer veilig te verwijderen is, en het eerlijke antwoord is dat niemand het weet.
De documentatie redt je ook niet. Docs beschrijven wat dingen zouden moeten zijn. Het moment dat je iets in de console verandert zonder de wiki bij te werken, lopen die twee uit elkaar, en ze lopen altijd uit elkaar. Je vertrouwt uiteindelijk op een beschrijving van je infrastructuur in plaats van op je infrastructuur zelf.
Hoe het eruitziet als alles is opgeschreven
Stel je nu hetzelfde systeem voor, maar elk onderdeel ervan bestaat als tekst die je kunt lezen, reviewen en opnieuw aanmaken.
# Dit bestaat omdat we verkeer moeten toestaan van de monitoring namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-prometheus
namespace: production
spec:
podSelector:
matchLabels:
app: api
ingress:
- from:
- namespaceSelector:
matchLabels:
name: monitoring
ports:
- port: 9090
protocol: TCP
De comment legt uit waarom. De code toont wat. Beide leven in hetzelfde bestand, beide version controlled, beide leesbaar voor iedereen die de repo cloont. Er valt geen archeologie te bedrijven, omdat het artefact en de reden erachter nooit van elkaar gescheiden zijn.
Die ene eigenschap, expliciet boven impliciet, maakt dat Infrastructure as Code voor mij minder als een best practice voelt en meer als een manier van werken die ik echt kan vertrouwen.
De echte kost is cognitief
Even terug naar de rommelige kant, want de oppervlakkige kost van click-ops is niet het interessante deel. Het interessante deel is wat het met je hoofd doet.
Je nam een configuratiebeslissing om een reden. Maanden gaan voorbij. Was die timeout-waarde dragend? Beschermde die IP whitelist tegen iets specifieks, of voegde iemand hem tijdens een incident toe en vergat het daarna? Niemand weet het nog, dus de instelling blijft voor altijd staan en verandert langzaam in het soort technical debt dat iedereen net iets te eng vindt om aan te raken.
En er is altijd één iemand in het team die toevallig nog weet waarom het netwerk eruitziet zoals het eruitziet. Als er iets breekt, kijkt iedereen naar die persoon. Die wordt een single point of failure gemaakt van geheugen, en geheugen is het minst betrouwbare opslagsysteem dat we hebben.
Opgeschreven infrastructuur haalt die kennis uit het hoofd van één vermoeide collega en zet het in een bestand dat het hele team kan lezen.
Wat je ervoor terugkrijgt
Zodra het systeem in Git leeft, stoppen een paar dingen heldendaden te zijn en worden ze gewoon.
Geschiedenis is geen gok meer. Elke wijziging draagt een commit message en een auteur, dus je kunt een beslissing terug in de tijd traceren:
git log --oneline -- network-policy.yaml
a1b2c3d Voeg network policy toe voor Prometheus scraping
d4e5f6g Beperk tot specifieke poort (was alles toestaan)
g7h8i9j Initiële creatie voor monitoring toegang
Een tweede paar ogen krijgt een kans voordat productie die krijgt. De klassieke “sta alles toe”-fout is moeilijk te vangen in een console, maar in een diff springt hij van het scherm:
# Pull request diff
- cidrBlocks:
- - 0.0.0.0/0 # GEVAAR: staat al het verkeer toe
+ cidrBlocks:
+ - 10.0.0.0/8 # Alleen intern netwerk
Herstel wordt een commando in plaats van een lange, gespannen middag. Als een cluster sterft, reconstrueer je het niet uit je geheugen:
# Herstel alles
terraform apply
kubectl apply -k ./infrastructure
argocd app sync --all
Het declaratieve deel houdt dit allemaal bij elkaar. Je schrijft de state op die je wilt, en het is de taak van het systeem om de realiteit daarmee overeen te laten komen.
# Ik declareer: er moeten 3 replica's zijn
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 3
Een pod sterft, Kubernetes merkt de afwijking van drie op, en start een nieuwe zonder dat iemand iets klikt. Jij declareerde het contract, en het systeem dwingt het af.
Er is ook ruimte om intentie op te schrijven, niet alleen mechaniek. Tags en comments maken van configuratie een boodschap aan wie het hierna leest, meestal een toekomstige versie van jezelf:
# Terraform: Waarom deze VPC bestaat
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "production"
Environment = "production"
Purpose = "Primaire VPC voor productie workloads"
Owner = "platform-team"
CostCenter = "infrastructure"
}
}
Zo is mijn homelab ruwweg gebouwd. Alles wat daar draait is gedefinieerd in één repo:
homelab/
├── infrastructure/
│ ├── terraform/ # VMs, netwerken, DNS
│ └── kubernetes/ # Cluster configuratie
├── applications/
│ ├── argocd/ # GitOps controller
│ ├── monitoring/ # Prometheus, Grafana, Loki
│ └── services/ # Home Assistant, GitLab, etc.
└── secrets/
└── external-secrets/ # ExternalSecret manifests (secrets staan in Vault)
Als het hele ding morgen afbrandt, kan ik het herbouwen met drie commando’s en een verse schijf, omdat ik mijn eigen geheugen nooit heb vertrouwd om de configuratie vast te houden.
Waarom mensen niet overstappen (en waarom de bezwaren zacht zijn)
Als dit zo duidelijk beter is, waarom overleeft click-ops dan? Omdat de bezwaren op het moment zelf echt voelen, ook als ze geen stand houden.
“Het is langzamer dan klikken.” In het begin, zeker. De YAML één keer schrijven kost tien minuten tegen vijf minuten klikken. De truc is dat je de YAML maar één keer schrijft, en hij werkt over elke omgeving, terwijl het klikken elke keer opnieuw met de hand moet en naar niets overdraagbaar is.
“Niet alles kan Infrastructure as Code zijn.” Klopt. Sommige stappen hebben een mens nodig, en het antwoord daar is om ze eerlijk te documenteren in plaats van te doen alsof ze niet bestaan:
# handmatige-stappen.md
## Externe DNS provider opzetten
1. Log in op Cloudflare dashboard
2. Maak API token met Zone:Edit permissie
3. Voeg token toe aan Vault op `secret/cloudflare/api-token`
Opmerking: Dit is handmatig omdat Cloudflare geen
organisatie-level tokens ondersteunt via API.
Laatst uitgevoerd: 2026-03-15 door @tom
Het doel is dat niets ongedocumenteerd is, ook de delen die je niet kunt automatiseren.
“Mijn team adopteert het niet.” Vraag ze dan niet om het in één keer te adopteren. Automatiseer één pijnlijk ding goed, en laat het verhaal zichzelf verkopen: de drie uur die je niet aan een server herstellen besteedde, de storing die een code review had gevangen. Mensen volgen resultaten sneller dan argumenten.
Begin vanaf waar je al bent
Je hoeft je infrastructuur niet te herschrijven om hier te komen. Je begint door vast te leggen wat er al is.
# Exporteer bestaande Kubernetes resources
kubectl get deployment my-app -o yaml > deployments/my-app.yaml
# Importeer bestaande Terraform resources
terraform import aws_instance.web i-1234567890abcdef0
Geef het daarna wat structuur, gesplitst per omgeving zodat de verschillen zichtbaar zijn in plaats van verstopt:
infrastructure/
├── base/ # Gemeenschappelijke configuratie
│ ├── namespaces.yaml
│ ├── rbac.yaml
│ └── network-policies.yaml
├── environments/
│ ├── development/
│ │ └── kustomization.yaml # Overlays voor dev
│ ├── staging/
│ │ └── kustomization.yaml
│ └── production/
│ └── kustomization.yaml # Productie-specifieke instellingen
└── applications/
├── api/
├── frontend/
└── database/
En stop met het handmatig toepassen. Hang het in CI, of beter, geef het aan GitOps en laat een controller de realiteit synchroon houden met de repo:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure
spec:
source:
repoURL: https://gitlab.internal/infrastructure
path: environments/production
destination:
server: https://kubernetes.default.svc
syncPolicy:
automated:
selfHeal: true
Waarom dit verbonden is met soevereiniteit
Voor mij landt dit op iets groters dan nette YAML. Digitale soevereiniteit gaat veel verder dan hardware bezitten. Het gaat over het begrijpen en controleren van de systemen waar je van afhankelijk bent, en je kunt niet controleren wat je niet kunt zien.
Infrastructure as Code maakt die controle echt. Het geeft je portabiliteit, omdat van provider wisselen een config-wijziging wordt. Het geeft je reproduceerbaarheid, omdat je alles vanuit niets kunt herbouwen. Bovenal geeft het je het vermogen om “waarom bestaat dit” te beantwoorden voor elk stuk van je stack, in plaats van afhankelijk te zijn van wie laatst op de knoppen drukte en te hopen dat die het nog weet.
Dat is het deel waar ik echt om geef. Ik begrijp mijn infrastructuur omdat ik het definieerde, regel voor regel, in bestanden die ik kan lezen. Als er om 2 uur ’s nachts iets breekt, is er geen console om door te spitten, alleen een repo die me precies vertelt wat er zou moeten draaien en waarom. Voor een brein dat onverklaarde systemen als een lichte jeuk behandelt, is dat veel meer waard dan de middag die het kostte om het allemaal op te schrijven.
