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

GitOps mit ArgoCD: Deployments aus Git

Leere Aluminiumdosen auf einem automatisierten Förderband in einer Fabrikhalle
Foto: cottonbro studio / Pexels

Kubernetes GitOps heißt: Der gewünschte Zustand deines Clusters liegt vollständig in einem Git-Repository, und ein Controller im Cluster sorgt dafür, dass der tatsächliche Zustand diesem Repository entspricht. Du deployst nicht mehr mit kubectl oder aus einer Pipeline heraus, sondern mit einem Merge. ArgoCD ist das Werkzeug, das diesen Abgleich übernimmt: Es liest das Repository, vergleicht mit dem Cluster und wendet die Differenz an.

Ich betreibe Kubernetes-Cluster in Produktion und habe das Kubernetes-Praxisbuch bei Rheinwerk geschrieben (2024). In meinen Clustern auf Hetzner laufen produktive Anwendungen und produktive Änderungen über ArgoCD: Vom cert-manager über das Monitoring bis zu den Anwendungen kommt alles aus Git. Kleine Tests und kurzfristige Versuche fahre ich auch mal direkt über Helm, produktiv landet das aber nur über das Repository. Auf dem Weg dorthin bin ich an einigen Stellen selbst gestolpert. Alle meine Beiträge zum Thema findest du gesammelt auf der Seite Kubernetes.

Was Kubernetes GitOps löst: Pull statt Push

Bei den meisten Teams, die ich kennengelernt habe, sah das Deployment so aus: Eine CI-Pipeline baut das Image, und am Ende führt dieselbe Pipeline kubectl apply oder helm upgrade gegen den Cluster aus. Dafür braucht die Pipeline einen technischen Benutzer mit weitreichenden Rechten im Cluster, und dessen Zugangsdaten liegen im CI-System. Das ist das Push-Modell.

GitOps dreht die Richtung um. Im Cluster läuft ein Controller, der regelmäßig das Git-Repository abholt und mit dem Ist-Zustand vergleicht. Weicht etwas ab, korrigiert er. Das ist dasselbe Prinzip, nach dem Kubernetes intern arbeitet: Ein ReplicaSet zählt seine Pods und startet einen neuen, wenn einer fehlt. GitOps zieht diese Schleife eine Ebene höher und macht Git zur Quelle der Wahrheit für den ganzen Cluster.

Für dich ändert sich damit vor allem eines: Für produktive Änderungen gibt es keinen zweiten Weg mehr in den Cluster. Das klingt hart, bringt aber drei Dinge mit, die ich nicht mehr hergeben möchte. Jede Änderung hat einen Commit, einen Autor und im besten Fall ein Review. Der Cluster braucht keine Zugangsdaten mehr in der Pipeline, weil niemand von außen hineinschreibt. Und Abweichungen fallen auf, weil der Controller sie sieht und meldet.

Der Preis dafür ist Disziplin. Wer gewohnt ist, im Notfall schnell per kubectl ein Deployment zu skalieren, muss sich umgewöhnen: Die Änderung landet zuerst im Repository, sonst dreht ArgoCD sie wieder zurück.

ArgoCD installieren und die erste Application anlegen

ArgoCD läuft selbst als Anwendung im Cluster, in einem eigenen Namespace. Ich installiere es über das offizielle Helm-Chart, weil ich später auch ArgoCD selbst über ArgoCD verwalten will und Helm-Values dafür der einfachste Weg sind. Für den ersten Versuch reichen drei Befehle:

helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm install argocd argo/argo-cd --namespace argocd --create-namespace

Das Admin-Passwort für die Oberfläche liegt danach in einem Secret im Namespace argocd. Für die ersten Schritte reicht ein Port-Forward auf den Server-Service; in Produktion hängt bei mir ein Ingress mit Zertifikat davor, und der Login läuft über den Identity-Provider des Kunden statt über das lokale Admin-Konto.

Der zentrale Begriff in ArgoCD ist die Application. Sie verbindet genau drei Dinge: eine Quelle in Git (Repository, Branch oder Tag, Pfad), ein Ziel (Cluster und Namespace) und eine Sync-Regel. Eine Application für ein Backend, das als Helm-Chart im Repository liegt, sieht so aus:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: shop-backend
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/example-org/gitops.git
    targetRevision: main
    path: workloads/shop-backend
    helm:
      valueFiles:
        - values.yaml
        - values-prod.yaml
  destination:
    server: https://kubernetes.default.svc
    namespace: shop
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

Drei Zeilen in diesem Manifest entscheiden über das Verhalten in Produktion. automated bedeutet, dass ArgoCD Änderungen aus Git ohne Klick anwendet. prune: true löscht Objekte im Cluster, die du aus dem Repository entfernt hast; ohne diese Zeile sammelst du Leichen. selfHeal: true dreht manuelle Änderungen im Cluster zurück, sobald ArgoCD sie bemerkt. Für den Anfang kannst du die Automatik weglassen und in der Oberfläche von Hand synchronisieren, dann siehst du vor jedem Sync den Diff.

Eine Stolperfalle, die viele beim Umstieg von helm install überrascht: ArgoCD rendert das Chart mit helm template und wendet die fertigen Manifeste an. Es gibt danach kein Helm-Release im Cluster, helm list bleibt leer. Was das für Hooks und Upgrades bedeutet, steht in Helm Charts erklärt: Pakete für Kubernetes.

Eine Repo-Struktur, die mit dem Team wächst

Wie du das GitOps-Repository aufbaust, hängt weniger von der Technik ab als von deiner Organisation. Conway's Law gilt auch hier: Ein DevOps-Team mit voller Verantwortung für seine Anwendung packt Code und Manifeste gern in ein Repository. Sind Entwicklung und Betrieb getrennt, gibt es meist ein eigenes Infrastruktur-Repository mit anderen Berechtigungen. Beide Wege funktionieren, wichtig ist, dass du dich bewusst entscheidest.

Für die Struktur innerhalb des Repositories gibt es zwei Grundmuster. Anwendungsorientiert legst du je Anwendung ein Verzeichnis an und darunter die Umgebungen. Umgebungsorientiert legst du je Umgebung ein Verzeichnis an und darunter die Anwendungen. Das erste Muster passt, wenn Anwendungen unabhängig voneinander ausgerollt werden; das zweite, wenn ein ganzes Paket zusammen die Stufen durchläuft. Mit Helm oder Kustomize brauchst du die Manifeste nicht je Umgebung zu kopieren, sondern hältst eine Basis und kleine Overlays oder Values-Dateien je Umgebung. Welches Werkzeug dafür wann passt, vergleiche ich in Kustomize vs Helm: was wann?.

Mein Layout für die Hetzner-Cluster trennt Plattform und Workloads. Plattform sind die Bausteine, die jeder Cluster braucht: cert-manager, Ingress-Controller, Monitoring, Policies. Workloads sind die Anwendungen des Kunden. Vereinfacht sieht das so aus:

gitops/
├── bootstrap/
│   └── root.yaml            # eine Application, die alle anderen anlegt
├── apps/
│   ├── cert-manager.yaml    # je Baustein eine Application
│   ├── monitoring.yaml
│   └── shop-backend.yaml
├── platform/
│   ├── cert-manager/        # Values oder Manifeste je Plattform-Baustein
│   └── monitoring/
└── workloads/
    └── shop-backend/        # Chart oder Kustomize-Basis plus Values je Umgebung

Ein Branch je Umgebung habe ich in Projekten gesehen und lange selbst genutzt. Ich rate heute davon ab: Änderungen müssen von Branch zu Branch gemergt werden, Umgebungen driften auseinander, und irgendwann entwickelt jemand direkt auf dem Produktions-Branch. Ein Branch, ein Verzeichnis je Umgebung, ein Pull Request je Änderung: Das ist bei mir die Regel, die am wenigsten Ärger macht.

App-of-Apps und Sync-Waves: der Cluster baut sich selbst auf

Wenn du zwanzig Applications von Hand anlegst, hast du das GitOps-Prinzip an der ersten Stelle wieder gebrochen. Die Lösung heißt App-of-Apps: Eine einzige Application zeigt auf ein Verzeichnis, in dem lauter Application-Manifeste liegen. ArgoCD legt diese an, und jede davon zieht wiederum ihren Teil der Plattform oder eine Anwendung in den Cluster.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: root
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/example-org/gitops.git
    targetRevision: main
    path: apps
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Diese eine Datei ist das Einzige, was ich nach der ArgoCD-Installation noch per kubectl anwende. Ab dann bootstrappt sich der Cluster aus dem Repository. Ein neuer Cluster heißt bei mir: Terraform baut die Nodes, k3s kommt hoch, ArgoCD wird installiert, die Root-Application wird angewendet, und danach warte ich, bis alles grün ist. Für Cluster-Flotten gibt es zusätzlich ApplicationSets, die Applications aus einer Liste oder aus Verzeichnissen generieren; für einen einzelnen Cluster reicht App-of-Apps.

Reihenfolge ist dabei ein echtes Thema. Der cert-manager muss laufen, bevor ein Ingress ein Zertifikat anfordert, und eine Custom Resource Definition muss existieren, bevor du ein Objekt dieses Typs anlegst. ArgoCD löst das über Sync-Waves: eine Annotation mit einer Zahl, und ArgoCD arbeitet die Wellen aufsteigend ab und wartet, bis eine Welle gesund ist, bevor die nächste startet.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: cert-manager
  namespace: argocd
  annotations:
    argocd.argoproj.io/sync-wave: "-2"
spec:
  project: default
  source:
    repoURL: https://charts.jetstack.io
    chart: cert-manager
    targetRevision: "*"
    helm:
      values: |
        crds:
          enabled: true
  destination:
    server: https://kubernetes.default.svc
    namespace: cert-manager
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

Ohne Annotation gilt Welle null. Ich gebe der Plattform negative Wellen und den Workloads positive; damit steht die Grundlage immer, bevor die erste Anwendung startet. Ein Hinweis zu targetRevision: "*": Damit folgt ArgoCD jeder neuen Chart-Version automatisch, für Testumgebungen bequem. In Produktion pinne ich die Version und hebe sie per Pull Request an.

Secrets in Kubernetes GitOps: verschlüsselt im Repository

Die häufigste Frage, die ich zu GitOps bekomme, lautet: Wenn alles in Git liegt, wo liegen dann die Passwörter? Ein Kubernetes-Secret ist nur base64-kodiert, das darf so nicht ins Repository. Es gibt drei Wege, und alle drei habe ich im Einsatz gesehen.

Weg Was in Git liegt Wo entschlüsselt wird Passt, wenn
SOPS mit age oder KMS die verschlüsselte YAML-Datei im Cluster, Schlüssel liegt nur dort du ohne Zusatzdienst auskommen willst und das Repo die Wahrheit bleiben soll
Sealed Secrets ein SealedSecret-Objekt, mit dem Cluster-Zertifikat verschlüsselt vom Controller im Cluster du je Cluster einen eigenen Schlüssel akzeptierst
External Secrets Operator nur ein Verweis auf den Eintrag im Vault oder Cloud-Secret-Store der Operator holt den Wert zur Laufzeit es ohnehin einen zentralen Secret-Store gibt

In meinen Clustern nutze ich SOPS mit age. Die Werte werden verschlüsselt, die Schlüsselnamen bleiben lesbar, damit ein Diff im Pull Request noch zeigt, welches Secret sich geändert hat. Der private age-Schlüssel liegt ausschließlich im Cluster, nie im Repository und nie im CI-System. ArgoCD kann SOPS-Dateien nicht von Haus aus entschlüsseln; du brauchst ein Config-Management-Plugin im Repo-Server, das beim Rendern entschlüsselt, oder einen Operator im Cluster, der aus dem verschlüsselten Objekt ein normales Secret erzeugt. Beides gehört vor das erste Produktionsdeployment.

Was ich nicht mehr mache: Secrets an ArgoCD vorbei per Hand in den Cluster legen. Es funktioniert, aber genau diese Objekte fehlen dann beim nächsten Cluster-Neuaufbau, und niemand erinnert sich an den Wert.

Drift, Health-Checks und Rollback in der Praxis

ArgoCD zeigt für jede Application zwei Zustände. Der Sync-Status sagt, ob der Cluster dem Repository entspricht. Der Health-Status sagt, ob die Objekte auch funktionieren: Ein Deployment gilt erst als gesund, wenn alle Replicas bereit sind, ein Ingress erst, wenn er eine Adresse bekommen hat. Für die gängigen Ressourcen bringt ArgoCD diese Prüfungen mit, für eigene Custom Resources kannst du sie in Lua nachrüsten.

Drift entsteht, wenn jemand am Repository vorbei ändert. Mit selfHeal dreht ArgoCD das automatisch zurück; ohne diese Option zeigt es die Application als OutOfSync und wartet auf dich. Ich empfehle, mit der Anzeige zu starten und selfHeal erst einzuschalten, wenn kubectl im Team nur noch zum Lesen und Debuggen dient. Manche Abweichungen sind gewollt, etwa die Replica-Zahl eines Deployments, das ein Horizontal Pod Autoscaler steuert; solche Felder nimmst du über ignoreDifferences in der Application vom Vergleich aus.

Rollback ist der Punkt, an dem GitOps seine Stärke am deutlichsten zeigt. Ein Rollback ist ein git revert auf den Commit, der das Problem eingeführt hat. ArgoCD sieht den neuen Stand und rollt zurück, so nachvollziehbar wie jede andere Änderung. ArgoCD bietet zusätzlich einen Rollback auf einen früheren Sync in der Oberfläche an, schaltet dabei aber die automatische Synchronisation ab, weil Git sonst sofort wieder gewinnen würde. Ich nutze das nur, um in einer akuten Störung Zeit zu gewinnen, und räume danach in Git auf.

Die CI-Pipeline verschwindet dabei nicht, sie endet nur früher. Sie baut und testet das Image, prüft die Manifeste mit einem Linter und schreibt am Ende den neuen Image-Tag ins GitOps-Repository, per Commit oder per Pull Request. Ab da übernimmt ArgoCD. Wie die Pipeline davor aussieht, zeige ich in CI/CD für Kubernetes: die Pipeline.

ArgoCD vs Flux: welches Werkzeug passt zu dir

Die beiden großen GitOps-Werkzeuge sind ArgoCD und Flux, beide bei der CNCF. Sie lösen dieselbe Aufgabe mit unterschiedlichem Charakter. Ich habe mich für ArgoCD entschieden, weil die Oberfläche bei Kunden den Einstieg in GitOps deutlich leichter macht: Ein Entwickler sieht seinen Diff, den Sync-Status und die Logs, ohne kubectl anzufassen. Das ist bei gemischten Teams ein Argument, das mehr wiegt als technische Feinheiten.

Kriterium ArgoCD Flux
Oberfläche eigene Web-UI mit Diff, Status und Logs keine eigene UI im Kern, Bedienung über CLI und kubectl
Bedienmodell Application-Objekte, Projekte für Mandantentrennung Kustomization- und HelmRelease-Objekte, ein Controller je Aufgabe
Helm rendert Templates, kein Helm-Release im Cluster echter Helm-Release über den helm-controller
Mehrere Cluster eine Instanz verwaltet viele Cluster üblicherweise eine Flux-Instanz je Cluster
Image-Updates Zusatzprojekt Argo CD Image Updater Image-Automation-Controller als Teil von Flux
Einstieg schneller durch die Oberfläche schlanker, fühlt sich stärker nach Kubernetes-Bordmitteln an

Flux passt gut zu Teams, die ohnehin alles über kubectl und Git erledigen, viele Cluster mit je eigener Instanz betreiben oder Helm-Releases im Cluster behalten möchten. Beide Projekte sind gut dokumentiert, argo-cd.readthedocs.io und github.com/fluxcd/flux2; probiere sie in einem Testcluster aus, bevor du dich festlegst. Falsch ist nur, ab einer Handvoll Diensten weiter mit kubectl aus der Pipeline zu deployen.

Häufige Fragen

Was ist der Unterschied zwischen ArgoCD und Flux?

Beide setzen Kubernetes GitOps um: Git ist die Quelle, ein Controller im Cluster gleicht ab. ArgoCD bringt eine Web-Oberfläche mit Diff und Sync-Status mit und verwaltet aus einer Instanz viele Cluster. Flux besteht aus mehreren kleinen Controllern, hat im Kern keine Oberfläche und läuft meist je Cluster einmal. Für gemischte Teams ist ArgoCD meist der leichtere Einstieg.

Ersetzt ArgoCD meine CI-Pipeline?

Nein. Die Pipeline baut und testet weiterhin dein Image und prüft die Manifeste. Sie hört nur früher auf: Statt kubectl apply gegen den Cluster schreibt sie den neuen Image-Tag ins GitOps-Repository. Das Ausrollen übernimmt ArgoCD im Cluster, und die Pipeline braucht keinen Zugang zum Cluster mehr.

Wie kommen Secrets in ein GitOps-Repository?

Nie im Klartext und nie nur base64-kodiert. Entweder verschlüsselst du die Werte mit SOPS und age, sodass die Datei im Repository liegen darf und erst im Cluster entschlüsselt wird, oder du nutzt Sealed Secrets, oder du lässt den External Secrets Operator die Werte aus einem zentralen Secret-Store holen und legst in Git nur den Verweis ab. Der private Schlüssel gehört in jedem Fall ausschließlich in den Cluster.

Was passiert, wenn ich mit kubectl von Hand etwas ändere?

ArgoCD erkennt die Abweichung beim nächsten Abgleich und markiert die Application als OutOfSync. Ist selfHeal aktiv, setzt es den Zustand aus Git sofort wieder durch, deine Änderung ist dann weg. Ist selfHeal aus, bleibt die Änderung stehen, bis jemand synchronisiert. Gewollte Abweichungen wie die Replica-Zahl unter einem Autoscaler nimmst du mit ignoreDifferences aus dem Vergleich.

Wie rolle ich mit ArgoCD zurück?

Der saubere Weg ist ein git revert des fehlerhaften Commits: ArgoCD rollt den alten Stand automatisch aus, und die Historie bleibt vollständig. Die Oberfläche bietet zusätzlich einen Rollback auf einen früheren Sync an, deaktiviert dabei aber die automatische Synchronisation. Das taugt als Notbremse, nicht als Dauerzustand.

Wie du weitermachst

Wenn du GitOps einführen willst, fang mit einem Testcluster und einer einzigen Anwendung an: ArgoCD installieren, die erste Application ohne Automatik anlegen, vor jedem Sync den Diff anschauen. Sobald das Team dem Fluss vertraut, kommen App-of-Apps, Sync-Waves und die Automatik dazu, und zuletzt die Regel, dass kubectl nur noch liest.

Die Bausteine davor und daneben habe ich in eigenen Beiträgen beschrieben: Welche Schritte in der Pipeline vor dem Merge sinnvoll sind, steht in CI/CD für Kubernetes: die Pipeline. Wie ein Chart aufgebaut ist, das ArgoCD rendert, erkläre ich in Helm Charts erklärt: Pakete für Kubernetes, und die Entscheidung zwischen Overlays und Templates in Kustomize vs Helm: was wann?.

Ausführlich mit Repository-Strukturen, Branching-Strategien und Pipeline-Architekturen steht das in Kapitel 4 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