Inhoud van dit artikel
Een Kubernetes Secret is standaard alleen base64-gecodeerd, niet versleuteld, en hoort dus niet in Git. De twee gangbare oplossingen zijn External Secrets Operator (ESO), dat secrets ophaalt uit een externe secret manager, en Sealed Secrets, dat versleutelde secrets in Git zet die alleen je cluster kan ontsleutelen. Voor de meeste webshops en Laravel-platformen met meer dan één omgeving kiezen wij ESO; Sealed Secrets is prima voor één klein cluster zonder externe secret store.
Waarom een gewoon Secret niet genoeg is
Wat veel teams verrast: dit is geen versleuteling.
kubectl get secret shop-db -n shop -o jsonpath='{.data.password}' | base64 -d
Iedereen met leesrechten op Secrets in die namespace ziet het wachtwoord. Wie het YAML-bestand in Git zet, publiceert het wachtwoord aan iedereen met toegang tot de repository, inclusief de historie.
Voor een webshop gaat het om gevoelige waarden:
- Databasewachtwoorden voor MySQL of MariaDB.
- De Magento-encryptiesleutel (
crypt/key) waarmee onder meer betaalgegevens en integratietokens versleuteld zijn. - Laravel
APP_KEY. - API-sleutels van betaalproviders, ERP-koppelingen, mailproviders en zoekdiensten.
- Credentials voor object storage waar
pub/mediaof Laravel-uploads staan.
Met GitOps, waarbij Argo CD je cluster gelijk houdt aan een Git-repository, wil je alles in Git. Behalve de geheimen zelf. Daar zijn de twee tools voor.
Sealed Secrets: versleuteld in Git
Sealed Secrets (bitnami-labs/sealed-secrets) werkt met asymmetrische encryptie. Een controller in je cluster heeft een private key. Jij versleutelt lokaal met de publieke sleutel via kubeseal. Het resultaat, een SealedSecret, mag in Git. Alleen de controller kan het terugzetten naar een gewoon Secret.
kubectl create secret generic shop-db -n shop \
--from-literal=password='hier-het-echte-wachtwoord' \
--dry-run=client -o yaml \
| kubeseal --format yaml > k8s/shop/shop-db.sealed.yaml
Het resultaat ziet er zo uit:
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: shop-db
namespace: shop
spec:
encryptedData:
password: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq...
Sterke punten:
- Geen externe dienst nodig. Alles zit in je cluster en je Git-repo.
- Eenvoudig te begrijpen en snel op te zetten.
- Een versleuteld secret is gebonden aan naam en namespace. Kopiëren naar een andere namespace werkt niet zonder herversleutelen.
Zwakke punten in de praktijk:
- De private key is je single point of failure. Ben je het cluster kwijt zonder backup van de sleutels, dan kun je je SealedSecrets niet meer ontsleutelen. De controller vernieuwt zijn sleutel periodiek; oude sleutels moet je bewaren.
- Per cluster versleutelen. Drie omgevingen (test, acceptatie, productie) betekent drie keer versleutelen, of één gedeelde sleutel, wat het voordeel deels tenietdoet.
- Rotatie is handwerk. Wachtwoord wijzigen betekent opnieuw
kubeseal, committen en syncen. - Geen centrale bron. Het secret leeft in Git, niet in een systeem met audit log en toegangsbeleid.
Een punt van aandacht sinds 2025: Broadcom heeft de gratis Bitnami-catalogus grotendeels naar een legacy-repository verplaatst die niet meer wordt bijgewerkt. Sealed Secrets is daarop een uitzondering en wordt nog onderhouden, maar controleer welk image en welke tag je precies trekt. Een image uit bitnamilegacy krijgt geen updates meer.
External Secrets Operator: de bron staat buiten het cluster
ESO draait het model om. Het secret staat niet in Git, ook niet versleuteld. Het staat in een secret manager: AWS Secrets Manager, Google Secret Manager, Azure Key Vault, HashiCorp Vault, 1Password, Bitwarden, Infisical en nog een lange rij andere providers.
In Git zet je alleen een verwijzing: haal dit secret op uit die store en maak er een Kubernetes Secret van. Die verwijzing bevat geen geheimen.
Twee resources:
- SecretStore of ClusterSecretStore: hoe ESO bij de externe store komt.
- ExternalSecret: welk secret, welke sleutels, welk Kubernetes Secret als resultaat.
Een ClusterSecretStore voor AWS Secrets Manager, met authenticatie via een service account (IRSA of Pod Identity op EKS):
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: aws-secrets
spec:
provider:
aws:
service: SecretsManager
region: eu-central-1
auth:
jwt:
serviceAccountRef:
name: external-secrets
namespace: external-secrets
En een ExternalSecret voor de databasegegevens van een Laravel-app:
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: app-db
namespace: platform
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: aws-secrets
target:
name: app-db
creationPolicy: Owner
data:
- secretKey: DB_PASSWORD
remoteRef:
key: prod/platform/database
property: password
- secretKey: APP_KEY
remoteRef:
key: prod/platform/app
property: app_key
ESO maakt een gewoon Secret app-db aan en ververst het elk uur. Je Deployment gebruikt het zoals elk ander Secret:
envFrom:
- secretRef:
name: app-db
Let op de API-versie. Sinds ESO v0.17 is external-secrets.io/v1 de API. Veel tutorials gebruiken nog v1beta1; die voorbeelden moet je omzetten. De huidige releases zitten inmiddels in de 2.x-reeks.
Hele bestanden opbouwen met templates
Niet elke applicatie leest losse environment variables. Soms wil je een compleet configuratiebestand. ESO heeft daar templates voor:
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: shop-env
namespace: shop
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: aws-secrets
target:
name: shop-env
template:
engineVersion: v2
data:
DB_HOST: "{{ .host }}"
DB_USER: "{{ .username }}"
DB_PASSWORD: "{{ .password }}"
MAGENTO_CRYPT_KEY: "{{ .crypt_key }}"
dataFrom:
- extract:
key: prod/shop/magento
dataFrom.extract haalt alle velden van een JSON-secret op. Het template bepaalt wat er in het Kubernetes Secret komt.
Voor Magento werkt dit goed in combinatie met een app/etc/env.php in het image die zijn gevoelige waarden via getenv() leest. Zo blijft env.php zelf vrij van geheimen en kan hetzelfde image op elke omgeving draaien. Wat je verder met de encryptiesleutel moet, lees je in Magento encryptiesleutel en env.php: secrets beheren.
Rotatie: wat ESO wel en niet doet
ESO ververst het Kubernetes Secret op het refreshInterval. Wijzig je het wachtwoord in AWS Secrets Manager, dan staat het binnen dat interval in je cluster.
Wat ESO niet doet: je pods herstarten. Een PHP-FPM-container die zijn environment bij het opstarten heeft gelezen, blijft het oude wachtwoord gebruiken. Twee oplossingen:
- Stakater Reloader. Een kleine controller die Deployments herstart als een gekoppeld Secret verandert:
metadata:
annotations:
secret.reloader.stakater.com/reload: "app-db"
- Secrets als bestand mounten en ze in de applicatie bij gebruik lezen. Kubernetes werkt gemounte Secrets na verloop van tijd bij, maar niet als je
subPathgebruikt.
Bij databasewachtwoorden roteer je in twee stappen: eerst een tweede gebruiker of tweede wachtwoord toevoegen, dan de applicatie omzetten, dan het oude intrekken. Anders heb je een venster waarin pods met het oude wachtwoord niet meer verbinden.
De vergelijking
| Sealed Secrets | External Secrets Operator | |
|---|---|---|
| Waar staat het geheim | Versleuteld in Git | In een externe secret manager |
| Externe dienst nodig | Nee | Ja |
| Meerdere clusters | Per cluster versleutelen | Zelfde store, aparte paden per omgeving |
| Rotatie | Handmatig opnieuw versleutelen | Wijzigen in de store, ESO synchroniseert |
| Audit log | Git-historie | Audit log van de secret manager |
| Grootste risico | Verlies van de private key | Toegang tot de store verkeerd ingericht |
| Kosten | Alleen je cluster | Kosten van de secret manager per secret en per API-call |
Over die kosten: cloud secret managers rekenen meestal per secret per maand en per aantal API-calls. Een te kort refreshInterval bij veel secrets loopt dan op. Kijk op de prijspagina van je provider en kies een interval dat past, een uur is voor de meeste waarden ruim voldoende.
Wanneer wat
Kies Sealed Secrets als:
- Je één cluster hebt en dat zo blijft.
- Je geen cloud secret manager hebt en die ook niet wilt beheren.
- Het aantal secrets klein is en ze zelden veranderen.
Kies External Secrets Operator als:
- Je meerdere omgevingen of clusters hebt.
- Je al in een cloud draait met een eigen secret manager, of Vault gebruikt.
- Je wilt roteren zonder Git-commits.
- Je een audit trail nodig hebt van wie wanneer welk secret heeft gelezen of gewijzigd.
Een eerlijke kanttekening over ESO. In augustus 2025 pauzeerden de maintainers alle releases omdat het team te klein was. Daarna zijn er nieuwe maintainers bijgekomen en lopen de releases weer: in oktober 2026 verschenen meerdere 2.x-versies kort na elkaar. Dat incident laat zien dat je bij elke open-source component naar het onderhoud moet kijken, net als bij ingress-nginx dat in 2026 stopte.
Inpassen in GitOps met Argo CD
Met Argo CD wil je dat de SecretStore bestaat voordat de ExternalSecrets worden aangemaakt, en dat Secrets bestaan voordat de applicatie start. Dat regel je met sync waves:
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: app-db
namespace: platform
annotations:
argocd.argoproj.io/sync-wave: "-1"
De ExternalSecret in wave -1, de migratie-Job in wave 0, de Deployments daarna. Hoe je die volgorde voor migraties opbouwt, beschrijven we in Argo CD sync waves voor migraties.
Argo CD beheert alleen de ExternalSecret, niet het Secret dat ESO aanmaakt; dat staat immers niet in Git. Gebruik creationPolicy: Owner. Dan is het Secret eigendom van de ExternalSecret en ruimt Kubernetes het op zodra Argo CD de ExternalSecret verwijdert.
Overige maatregelen die je niet overslaat
ESO of Sealed Secrets lost het Git-probleem op. Het Secret in je cluster blijft een Secret. Zorg daarom ook voor:
- Encryptie at rest in etcd. Bij managed Kubernetes staat dat vaak standaard aan of is het een vinkje. Bij k3s of Talos op eigen servers moet je het zelf configureren.
- RBAC. Applicatie-service-accounts krijgen geen
getoflistop Secrets. Mensen alleen waar nodig. - Aparte stores per omgeving. Productie-secrets in een eigen pad of eigen account, met een eigen ClusterSecretStore. Een testcluster kan dan niet bij productie.
- Policy-checks. Met Kyverno kun je afdwingen dat niemand een gewoon Secret met data in Git zet.
Het bredere plaatje, inclusief image-scanning met Trivy en policies met Kyverno, staat op Secrets en security in Kubernetes. Voor het Laravel-deel van de stack zie Laravel op Kubernetes.
Twijfel je welke aanpak bij jouw platform past? Neem contact op. We zeggen het ook als een eenvoudiger opzet volstaat.
Veelgestelde vragen
Zijn Kubernetes Secrets versleuteld?+
Standaard alleen base64-gecodeerd, wat geen versleuteling is. Of ze in etcd versleuteld worden opgeslagen hangt af van je clusterconfiguratie. Bij managed Kubernetes is dat vaak geregeld; bij een zelf beheerd cluster moet je het zelf instellen.
Kan ik Sealed Secrets en External Secrets Operator samen gebruiken?+
Technisch wel, ze bijten elkaar niet. Het maakt je setup alleen lastiger te begrijpen. Kies er één als standaard en gebruik de ander hooguit tijdelijk tijdens een migratie.
Herstart ESO mijn pods als een secret verandert?+
Nee. ESO werkt alleen het Kubernetes Secret bij. Voor een herstart gebruik je een tool als Stakater Reloader, of je leest secrets als gemount bestand in plaats van als environment variable.
Welke API-versie moet ik gebruiken voor ExternalSecret?+
external-secrets.io/v1. Die is sinds ESO v0.17 de API-versie. Voorbeelden met v1beta1 zijn verouderd en moet je omzetten voordat je naar een actuele release gaat.
Wat als ik de sleutel van Sealed Secrets kwijtraak?+
Dan kun je de bestaande SealedSecrets niet meer ontsleutelen. Je moet alle secrets opnieuw aanmaken en versleutelen met een nieuwe sleutel. Maak daarom een backup van de sleutel-Secrets in de namespace van de controller en bewaar die buiten het cluster.





