Blog · 13. Mai 2025 · Aktualisiert am 7. September 2026 · 13 Min. Lesezeit

ConfigMap und Secret in Kubernetes

Ein braunes Lederjournal, mit einem Lederriemen verschlossen und mit einem alten Schlüssel als Anhänger
Foto: RDNE Stock project / Pexels

Eine Kubernetes ConfigMap speichert Konfiguration als Schlüssel-Wert-Paare oder ganze Dateien, getrennt vom Container-Image. Ein Secret tut dasselbe für Passwörter, Tokens und Zertifikate. Beide bringst du auf denselben Wegen in den Pod: als Umgebungsvariable oder als Datei in einem Volume. Der Unterschied liegt nicht im Aufbau, sondern darin, wie Kubernetes die Daten behandelt: Ein Secret ist base64-kodiert, wird nur an die Nodes verteilt, die es brauchen, und lässt sich mit Encryption at rest und RBAC absichern. Verschlüsselt ist es von Haus aus nicht.

Genau daran scheitern viele Projekte: Ich sehe immer wieder Secrets im Klartext in Git, weil jemand base64 für Verschlüsselung gehalten hat. Der Fehler ist mit wenig Aufwand zu beheben, wenn du die Grenzen der beiden Objekte kennst.

Ich betreibe Kubernetes-Cluster in Produktion und habe das Kubernetes-Praxisbuch bei Rheinwerk geschrieben (2024); ConfigMaps und Secrets haben dort einen eigenen Abschnitt in Kapitel 3. Hier steht die Kurzfassung mit dem Workflow, den ich selbst in meinen Clustern fahre. Alle meine Beiträge zu Kubernetes findest du gesammelt auf der Seite Kubernetes.

Warum Konfiguration nicht ins Image gehört

Ein Container-Image enthält alles, was deine Anwendung zum Laufen braucht: Laufzeit, Bibliotheken, Code. Zwei Dinge gehören trotzdem nicht hinein: die Konfiguration, die sich je Umgebung unterscheidet, und alles, was geheim ist. Sonst baust du für Entwicklung, Test und Produktion drei Images, die sich nur in einer Datenbank-Adresse unterscheiden, und dein Passwort liegt in jeder Image-Schicht, die jemals in die Registry geladen wurde.

Kubernetes trennt das mit zwei Objekten. Die ConfigMap hält die Konfiguration, das Secret die Zugangsdaten. Dasselbe Image läuft dann in jeder Umgebung, nur die ConfigMap und das Secret daneben unterscheiden sich. Das ist auch die Grundlage für vieles, was danach kommt: Ein Deployment lässt sich nur deshalb so leicht ausrollen, weil das Image nichts über seine Umgebung weiß. Wie das Deployment mit der Pod-Vorlage umgeht, steht in Kubernetes Deployment: Rollout erklärt.

Kubernetes ConfigMap: Aufbau und drei Wege in den Pod

Eine ConfigMap hat zwei Datenfelder: data für Text und binaryData für base64-kodierte Binärdaten. Unter data legst du einzelne Werte ab, etwa ein Log-Level, oder eine ganze Datei als mehrzeiligen String. Alle Werte sind Strings, ein Port bekommt deshalb Anführungszeichen. Für das Beispiel lege ich zwei ConfigMaps an: eine mit Werten für Umgebungsvariablen und eine mit einer JSON-Datei.

apiVersion: v1
kind: ConfigMap
metadata:
  name: shop-env
data:
  LOG_LEVEL: "info"
  DB_HOST: "postgres.shop.svc"
  DB_PORT: "5432"
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: shop-files
data:
  config.json: |
    {
      "currency": "EUR",
      "featureFlags": ["checkout-v2"]
    }

In den Pod kommen diese Daten auf drei Wegen. Erstens als einzelne Umgebungsvariable über env mit configMapKeyRef: Du wählst gezielt einen Key aus einer ConfigMap. Zweitens alle Keys einer ConfigMap auf einmal über envFrom mit configMapRef. Drittens als Volume: Jeder Key wird eine Datei im Mount-Verzeichnis, der Wert ist der Dateiinhalt. Das folgende Pod-Manifest zeigt envFrom und Volume zusammen und setzt außerdem schon ein Passwort aus einem Secret, das ich im nächsten Abschnitt anlege.

apiVersion: v1
kind: Pod
metadata:
  name: shop
spec:
  containers:
    - name: shop
      image: nginx
      envFrom:
        - configMapRef:
            name: shop-env
      env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: shop-db
              key: DB_PASSWORD
      volumeMounts:
        - name: config
          mountPath: /etc/shop
          readOnly: true
  volumes:
    - name: config
      configMap:
        name: shop-files
        items:
          - key: config.json
            path: config.json

Nach kubectl apply prüfst du mit kubectl exec shop -- env, ob LOG_LEVEL, DB_HOST und DB_PORT gesetzt sind, und mit kubectl exec shop -- cat /etc/shop/config.json, ob die Datei angekommen ist. Mit items schränkst du ein, welche Keys im Volume landen; ohne items erscheinen alle. Beide Referenzen kannst du mit optional: true markieren, dann startet der Pod auch, wenn die ConfigMap fehlt.

Meine Faustregel für ConfigMap Environment Variables gegenüber Dateien: Kurze Werte wie Log-Level, Hostnamen oder Feature-Flags als Umgebungsvariablen, alles mit Struktur wie JSON, YAML oder eine nginx.conf als Datei. Ein JSON-Objekt in eine Umgebungsvariable zu quetschen funktioniert, aber niemand will es debuggen. Den vierten Weg, die ConfigMap über die Kubernetes-API aus der Anwendung zu lesen, sehe ich selten: Er koppelt die Anwendung an Kubernetes.

Kubernetes Secrets: base64 ist keine Verschlüsselung

Ein Secret sieht fast aus wie eine ConfigMap. Drei Dinge sind anders: Es hat ein Feld type, die Werte unter data müssen base64-kodiert sein, und für Klartext gibt es stringData, das Kubernetes beim Anlegen selbst kodiert. Im Manifest nehme ich immer stringData, weil dann kein Fehler beim Kodieren von Hand passiert.

apiVersion: v1
kind: Secret
metadata:
  name: shop-db
type: Opaque
stringData:
  DB_USER: shop
  DB_PASSWORD: nur-ein-beispiel

Damit sind wir beim wichtigsten Punkt des Beitrags: base64 ist eine Kodierung, keine Verschlüsselung. Jeder, der das Secret lesen darf, kommt mit einem Befehl an den Klartext:

kubectl get secret shop-db -o jsonpath='{.data.DB_PASSWORD}' | base64 -d

Warum dann überhaupt ein eigenes Objekt? Weil Kubernetes ein Secret anders behandelt als eine ConfigMap. Es wird nur an die Node geschickt, auf der ein Pod es braucht. Als Volume eingebunden legt das Kubelet es dort in einem tmpfs im Arbeitsspeicher ab statt auf der Platte, und es verschwindet, sobald kein Pod es mehr referenziert. Berechtigen über RBAC und verschlüsseltes Speichern im API-Server kommen dazu, beides aber nicht von allein.

Statt eines Manifests kannst du ein Secret auch imperativ anlegen. Das ist in einer CI-Pipeline sinnvoll, wenn das Passwort zur Laufzeit aus einem Tresor kommt und nie in einer Datei landen soll:

kubectl create secret generic shop-db \
  --from-literal=DB_USER=shop \
  --from-literal=DB_PASSWORD='nur-ein-beispiel'

Im Pod bindest du Secrets genauso ein wie ConfigMaps, nur heißen die Referenzen secretKeyRef und secretRef, im Volume secret mit secretName. Für Secrets bevorzuge ich das Volume: Umgebungsvariablen erbt jeder Kindprozess, sie tauchen in Crash-Dumps und Debug-Ausgaben auf, und manche Frameworks loggen beim Start ihre komplette Umgebung. Eine Datei unter /etc/secrets liest nur, wer sie gezielt öffnet.

Das Feld type sagt Kubernetes und deinen Kollegen, was im Secret steckt. Die wichtigsten Typen:

Typ Wofür
Opaque Standard, beliebige Schlüssel-Wert-Paare
kubernetes.io/tls Zertifikat und privater Schlüssel, etwa für einen Ingress
kubernetes.io/dockerconfigjson Zugangsdaten für eine private Registry, als imagePullSecret im Pod
kubernetes.io/basic-auth Benutzername und Passwort in den Keys username und password
kubernetes.io/service-account-token Token eines Service Accounts für die Kubernetes-API

Kubernetes ConfigMap vs. Secret: der Unterschied auf einen Blick

ConfigMap Secret
Inhalt Konfiguration, die jeder im Team sehen darf Passwörter, Tokens, Zertifikate, Registry-Zugänge
Datenfelder data, binaryData data (base64), stringData (Klartext)
Typ keiner type, Standard Opaque
Ablage im etcd Klartext Klartext, bis du Encryption at rest aktivierst
Verteilung an Nodes wie jedes Objekt nur an Nodes mit einem Pod, der es braucht; dort im tmpfs
Einbindung in den Pod env, envFrom, Volume env, envFrom, Volume, imagePullSecrets
Größenlimit 1 MiB 1 MiB
Unveränderlich möglich ja, mit immutable: true ja, mit immutable: true

Die Entscheidung ist damit einfach: Alles, was in einem Pull Request für jeden lesbar sein darf, ist eine ConfigMap. Alles, was du nicht in einen Team-Chat posten würdest, ist ein Secret. Wenn du unsicher bist, nimm das Secret; der Aufwand ist derselbe, und du kannst es später mit RBAC und Verschlüsselung schützen, eine ConfigMap nicht.

Was sich bei einer Änderung aktualisiert

Die häufigste Frage, die ich zu ConfigMaps bekomme: Ich habe den Wert geändert, warum sieht mein Pod noch den alten? Die Antwort hängt davon ab, auf welchem Weg die Daten in den Pod gekommen sind.

Als Volume eingebunden aktualisiert das Kubelet die Dateien von selbst. Es prüft in regelmäßigen Abständen, ob sich das Objekt geändert hat, und tauscht dann die Dateien aus. Das dauert je nach Cluster-Einstellung einen Moment, aber es passiert ohne Neustart. Deine Anwendung muss die Datei allerdings neu lesen; wer die Konfiguration beim Start einliest und dann nie wieder, merkt trotzdem nichts. Eine Ausnahme sind Mounts mit subPath: Sie bekommen keine Updates.

Als Umgebungsvariable gibt es kein Update. Ein Prozess bekommt seine Umgebung beim Start und behält sie. Erst ein neuer Pod sieht den neuen Wert. In der Praxis heißt das: Nach einer Änderung der ConfigMap ein kubectl rollout restart deployment <name> ausführen, oder den Rollout automatisch auslösen, indem du einen Hash der ConfigMap als Annotation in die Pod-Vorlage schreibst. Viele Helm-Charts machen das so, und es hat einen angenehmen Nebeneffekt: Jede Konfigurationsänderung läuft wie ein normales Rollout durch die Readiness-Prüfung, und ein kaputter Wert fällt auf, bevor er alle Pods erreicht.

Wer Änderungen ausdrücklich verhindern will, setzt immutable: true. Eine unveränderliche ConfigMap oder ein unveränderliches Secret lässt sich nur noch löschen und neu anlegen, und das Kubelet muss das Objekt nicht mehr auf Änderungen beobachten, was bei vielen Pods den API-Server entlastet. Ich nutze das für Konfigurationen, die pro Release neu erzeugt werden und einen versionierten Namen tragen.

Encryption at rest, RBAC und Audit: Secrets im Cluster absichern

Standardmäßig liegt ein Secret im etcd genauso im Klartext wie eine ConfigMap. Wer ein etcd-Backup in die Hände bekommt oder direkten Zugriff auf die etcd-Datenbank hat, liest alle Secrets des Clusters. Drei Maßnahmen gehören deshalb in jeden Cluster, der mehr als eine Spielwiese ist.

Erstens Encryption at rest. Der API-Server verschlüsselt Secrets, bevor er sie in etcd schreibt, gesteuert über eine EncryptionConfiguration:

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: "BASE64_KODIERTER_32_BYTE_SCHLUESSEL"
      - identity: {}

Die Datei gibst du dem kube-apiserver über den Parameter --encryption-provider-config mit. Der erste Provider in der Liste wird zum Schreiben verwendet, alle weiteren zum Lesen; identity steht für unverschlüsselt und sorgt dafür, dass vorhandene Secrets lesbar bleiben. Bestehende Secrets werden erst beim nächsten Schreiben verschlüsselt, deshalb schreibst du sie einmal komplett neu:

kubectl get secrets --all-namespaces -o json | kubectl replace -f -

Der Schlüssel liegt dabei auf der Control-Plane-Node neben der Konfiguration. Das schützt gegen kopierte Backups und direkten etcd-Zugriff, nicht gegen jemanden mit Root auf der Control Plane; wer mehr will, hängt über den KMS-Provider einen externen Schlüsseldienst an. Bei verwalteten Clustern ist Encryption at rest meist ein Schalter oder schon aktiv, bei k3s ein Startparameter des Servers. Die vollständige Anleitung steht in der Kubernetes-Dokumentation zu Encryption at rest.

Zweitens RBAC. Wer get, list oder watch auf Secrets hat, liest die Inhalte; list allein reicht, weil die Liste die Daten enthält. Vergib diese Rechte nur an die Service Accounts und Menschen, die sie brauchen, und getrennt von ConfigMaps. Und behalte im Kopf: Wer in einem Namespace Pods anlegen darf, kann jedes Secret dieses Namespace in einen Pod mounten und so lesen. Pod-Rechte sind damit Secret-Rechte. Wie du Rollen sauber zuschneidest, steht in Kubernetes RBAC: Rollen und Rechte.

Drittens die Audit-Sicht. In meinen Clustern will ich beantworten können, wer wann welches Secret gelesen hat. Das liefert das Audit-Log des API-Servers, wenn du für Secrets die Stufe Metadata aktivierst; die Inhalte selbst dürfen nicht ins Log. Dazu kommt ein Detail, das viele überrascht: Ein Container mit privileged: true kann alle Secrets lesen, die auf seiner Node im Einsatz sind. Privilegierte Pods zu verbieten, bei mir per Gatekeeper-Policy, ist deshalb auch Secret-Schutz.

Secrets in Git: SOPS, Sealed Secrets und External Secrets Operator

Wer Kubernetes deklarativ betreibt, will auch Secrets in Git versionieren. Ein Secret-Manifest mit stringData gehört aber nie unverschlüsselt ins Repository, und ein base64-kodiertes data-Feld ist, wie oben gezeigt, genauso Klartext. Drei Ansätze haben sich in meinem Umfeld bewährt.

Ansatz Wie es funktioniert Wofür es passt
SOPS verschlüsselt die Werte im Manifest mit age, PGP oder einem Cloud-KMS; die Keys bleiben lesbar, entschlüsselt wird beim Ausrollen GitOps-Teams, die Secrets im Pull Request reviewen wollen und keinen externen Tresor betreiben
Sealed Secrets ein Controller im Cluster hält ein Schlüsselpaar; du verschlüsselst mit kubeseal gegen den öffentlichen Schlüssel, aus dem SealedSecret-Objekt entsteht im Cluster ein Secret Teams mit einem Cluster, die alles in Kubernetes halten wollen
External Secrets Operator ein Operator synchronisiert Secrets aus AWS Secrets Manager, HashiCorp Vault, Azure Key Vault und anderen in Kubernetes-Secrets; in Git liegt nur der Verweis Unternehmen, die schon einen zentralen Tresor betreiben und mehrere Cluster daraus versorgen

In meinen Clustern nutze ich SOPS. Das Secret-Manifest liegt mit verschlüsselten Werten im selben Repository wie der Rest der Infrastruktur, die Schlüsselnamen bleiben lesbar, und im Pull Request sehe ich, welches Secret sich geändert hat, ohne den Inhalt zu sehen. Der private Schlüssel liegt nie im Repository. Beim Ausrollen über ArgoCD wird entschlüsselt, und im Cluster entsteht ein ganz normales Kubernetes-Secret. Für einen Betrieb mit einem oder zwei Clustern ist das der Weg mit dem geringsten Aufwand. Das Werkzeug findest du auf GitHub unter getsops/sops.

Einen Vault-Cluster nur zum Injizieren von Passwörtern aufzusetzen halte ich für Over-Engineering. Der External Secrets Operator lohnt sich, wenn der Tresor ohnehin da ist und mehrere Systeme daraus versorgt: Er ist dann die einzige Quelle der Wahrheit, Kubernetes bekommt nur eine Kopie. Was nicht funktioniert: zwei Quellen gleichzeitig. Ich habe Teams erlebt, die Secrets parallel im Cloud-Tresor und per SOPS in Git pflegten, bis niemand mehr wusste, welche Version gilt.

Häufige Fragen

Sind Kubernetes Secrets sicher?

Von Haus aus nur bedingt. Base64 ist Kodierung, keine Verschlüsselung. Sicher wird ein Secret erst durch die drei Dinge oben: Encryption at rest, enge RBAC-Regeln und ein Verbot privilegierter Container.

ConfigMap oder Secret: was nehme ich für eine Datenbank-URL?

Kommt darauf an, ob das Passwort in der URL steckt. Host, Port und Datenbankname sind Konfiguration und gehören in die ConfigMap. Sobald Benutzername und Passwort Teil der URL sind, ist der ganze Wert ein Secret. Sauberer ist es, die URL in der Anwendung aus beiden Quellen zusammenzusetzen: Host aus der ConfigMap, Zugangsdaten aus dem Secret.

Warum sieht mein Pod die geänderte ConfigMap nicht?

Weil die Daten vermutlich als Umgebungsvariablen eingebunden sind. Umgebungsvariablen werden nur beim Start des Prozesses gesetzt und nie aktualisiert; erst ein neuer Pod sieht den neuen Wert. Ein kubectl rollout restart löst das. Bei Volumes aktualisiert das Kubelet die Dateien nach kurzer Zeit von selbst, außer bei Mounts mit subPath und bei Objekten mit immutable: true.

Wie bekomme ich Secrets in Git, ohne sie im Klartext zu speichern?

Mit einem Werkzeug, das die Werte vor dem Commit verschlüsselt: SOPS direkt im Manifest, Sealed Secrets als verschlüsseltes Objekt für einen Controller im Cluster. Wer schon einen Tresor betreibt, nutzt den External Secrets Operator.

Wie groß darf eine ConfigMap sein?

Höchstens 1 MiB, dasselbe Limit gilt für Secrets. Das reicht für Konfigurationsdateien, Zertifikate und Feature-Flags locker aus. Wer an die Grenze kommt, hat meist Daten in der ConfigMap, die in ein Volume oder in eine Datenbank gehören, zum Beispiel Modelle, Bilder oder große Datensätze.

Wie du weitermachst

Zwei Nachbar-Themen liegen jetzt nahe. Wie du Manifeste sauber schreibst und validierst, bevor sie in den Cluster gehen, steht in Kubernetes YAML: Manifeste verstehen. Und weil Pod-Rechte Secret-Rechte sind, ist Kubernetes RBAC: Rollen und Rechte die logische Fortsetzung. Das offizielle Konzeptkapitel zu Secrets in der Kubernetes-Dokumentation listet alle Typen und Sicherheitshinweise, die hier keinen Platz hatten.

Mein Vorschlag für heute: Prüfe mit kubectl get secrets --all-namespaces und einem Blick ins Git-Repository, ob irgendwo ein Passwort im Klartext oder nur base64-kodiert liegt. Wenn ja, ist SOPS der schnellste Ausweg und Encryption at rest der nächste Schritt.

Ausführlich mit allen Beispielen, vom Volume-Mount über Registry-Secrets bis zum Auslesen über die Kubernetes-API, steht das in Kapitel 3 meines Kubernetes-Praxisbuchs bei Rheinwerk.

Kevin Welter

Kevin Welter

Entwickler, IT-Architekt, Fachbuchautor (Kubernetes, Cloud-Infrastrukturen) und Speaker. Betreibt sein Business mit einer KI-Belegschaft aus acht KI-Mitarbeitern und zeigt Selbstständigen in seiner Community, wie sie ihren ersten KI-Mitarbeiter einstellen.

Mehr über KI-Mitarbeiter

Kubernetes von den Grundlagen bis zum produktionsreifen Cluster

Zum Buch