GitOps in 2026: Managing Kubernetes Infrastructure Through Git with ArgoCD and Automated CI/CD
Introduction: What Is GitOps and How Does It Differ from Classic CI/CD
GitOps is an operational model in which a Git repository serves as the single source of truth for the desired state of infrastructure and applications. Every change in a Kubernetes cluster happens through a pull request — not through a manual kubectl apply or a direct command from a pipeline.
The key architectural difference between GitOps and classic CI/CD is the push model vs. pull model. In a classic CI/CD setup, the pipeline itself "pushes" changes to the cluster: it obtains access to the Kubernetes API, authenticates, and applies manifests. GitOps works differently:
- Push model (classic): Jenkins/GitLab CI → kubectl apply → cluster. The pipeline has direct access to the cluster; secrets are stored in the CI system.
- Pull model (GitOps): Git repository → ArgoCD inside the cluster → cluster. An operator running inside the cluster pulls the desired state from Git itself.
The pull model has fundamental advantages: the cluster does not need to be exposed externally, the external pipeline does not store a kubeconfig with broad permissions, and every change goes through a code review in Git.
In 2026, GitOps has become the de facto standard for teams working with Kubernetes. ArgoCD and Flux are the two most widely used tools. In this article, we will take a deep dive into the ArgoCD approach, build a complete CI/CD pipeline with GitHub Actions, and show how to organize a GitOps repository for multiple environments.
GitOps Principles
GitOps is built on four foundational principles formulated by Weaveworks and codified in OpenGitOps 1.0:
1. Declarative
The entire desired state of the system is described declaratively — through Kubernetes manifests, Helm charts, or Kustomize configurations. You describe what should exist, not how to achieve it.
2. Single Source of Truth
The Git repository is the only place where the current state of infrastructure is stored. No "manual patches" applied directly to the cluster, no changes made via kubectl without a commit to Git.
3. Automatic Synchronization
The operator (ArgoCD) continuously compares the desired state (Git) with the actual state (cluster) and automatically reconciles the cluster to match the desired state.
4. Audit Trail via Git History
Every infrastructure change is a commit with an author, timestamp, and description. A complete audit trail at no extra cost. Rollback is simply git revert.
ArgoCD: Cluster Installation, Application and AppProject Concepts
Installing ArgoCD
ArgoCD is installed into a dedicated namespace in your Kubernetes cluster. The official Helm chart is recommended for production installations:
# Create the namespace
kubectl create namespace argocd
# Install via Helm
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm install argocd argo/argo-cd \
--namespace argocd \
--set server.service.type=LoadBalancer \
--set configs.params."server.insecure"=true \
--version 6.7.0
# Retrieve the initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d
After installation, ArgoCD provides a web UI and the argocd CLI tool. The CLI is installed separately and allows you to manage applications, synchronization, and projects from the terminal.
The Application Concept
The core unit in ArgoCD is an Application. An Application object describes where to fetch manifests from (source) and where to apply them (destination):
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-service
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/gitops-repo.git
targetRevision: HEAD
path: apps/my-service/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
The AppProject Concept
An AppProject is a grouping of applications with constraints: which repositories are allowed as sources, which namespaces and clusters are valid deployment targets, and which resource types are permitted. This is essential for multi-tenant clusters where different teams operate in isolation.
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: backend-team
namespace: argocd
spec:
description: Backend microservices team
sourceRepos:
- https://github.com/myorg/gitops-repo.git
destinations:
- namespace: production
server: https://kubernetes.default.svc
- namespace: staging
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: ''
kind: Namespace
GitOps Repository Structure
Mono-repo vs. Multi-repo
There are two primary approaches to organizing a GitOps repository:
- Mono-repo: all manifests for all services and environments live in a single repository. Easier to get started, convenient for smaller teams, and provides unified code review.
- Multi-repo: a separate repository for each service or each environment. Scales better and offers clear access boundaries, but dependency management across services becomes more complex.
For most teams starting with GitOps in 2026, we recommend a mono-repo with a well-defined directory structure. Here is the recommended layout:
gitops-repo/
├── apps/
│ ├── api-service/
│ │ ├── base/
│ │ │ ├── deployment.yaml
│ │ │ ├── service.yaml
│ │ │ ├── configmap.yaml
│ │ │ └── kustomization.yaml
│ │ └── overlays/
│ │ ├── dev/
│ │ │ ├── kustomization.yaml
│ │ │ └── patch-replicas.yaml
│ │ ├── staging/
│ │ │ ├── kustomization.yaml
│ │ │ └── patch-resources.yaml
│ │ └── production/
│ │ ├── kustomization.yaml
│ │ └── patch-hpa.yaml
│ └── worker-service/
│ ├── base/
│ └── overlays/
├── infrastructure/
│ ├── cert-manager/
│ ├── ingress-nginx/
│ └── monitoring/
├── argocd/
│ ├── projects/
│ │ └── backend-team.yaml
│ └── applications/
│ ├── api-service-dev.yaml
│ ├── api-service-staging.yaml
│ └── api-service-production.yaml
└── helm-charts/
└── base-service/
├── Chart.yaml
├── values.yaml
└── templates/
Kustomize vs. Helm in GitOps
Kustomize is built into kubectl and is natively supported by ArgoCD. The base/overlays pattern lets you maintain a shared base configuration and override only what differs between environments — such as replica counts, resource limits, or image names.
Helm is well suited for complex charts with a large number of parameters. ArgoCD supports Helm charts natively — you specify the chart path and a values file in the Application object. In a GitOps workflow with Helm, the recommended practice is to store the values.yaml for each environment in Git rather than the templates themselves.
Building a CI Pipeline with GitHub Actions
In GitOps, CI and CD are separated into two distinct stages. The CI pipeline is responsible only for building and publishing the Docker image, and for updating the image tag in the GitOps repository. ArgoCD picks up the change and performs the deployment.
Here is a complete GitHub Actions workflow example for the CI portion:
name: CI — Build and Update Manifest
on:
push:
branches: [main]
paths:
- 'src/**'
- 'Dockerfile'
env:
REGISTRY: ghcr.io
IMAGE_NAME: myorg/api-service
GITOPS_REPO: myorg/gitops-repo
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=,suffix=,format=short
type=ref,event=branch
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Update image tag in GitOps repo
env:
IMAGE_TAG: ${{ github.sha }}
GITOPS_TOKEN: ${{ secrets.GITOPS_PAT }}
run: |
SHORT_SHA=$(echo "$IMAGE_TAG" | cut -c1-7)
git config --global user.email "ci-bot@myorg.com"
git config --global user.name "CI Bot"
git clone https://x-access-token:${GITOPS_TOKEN}@github.com/${GITOPS_REPO}.git gitops
cd gitops
# Update the image tag via kustomize
cd apps/api-service/overlays/dev
kustomize edit set image api-service=${REGISTRY}/${IMAGE_NAME}:${SHORT_SHA}
git add .
git commit -m "chore: update api-service image to ${SHORT_SHA}"
git push
Key points about this pipeline: the CI job has no knowledge of the Kubernetes cluster whatsoever. It only updates a manifest in the GitOps repository. The GITOPS_PAT token is a Personal Access Token with write access to the GitOps repository only — not to the cluster.
Deployment Strategies with ArgoCD
Automatic Sync for Dev and Staging
For dev and staging environments, enabling automatic synchronization is recommended. As soon as a new commit appears in the GitOps repository, ArgoCD automatically applies the changes:
syncPolicy:
automated:
prune: true # remove resources no longer present in Git
selfHeal: true # revert manual changes made directly in the cluster
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
Manual Approval for Production
For the production environment, automatic synchronization is disabled. Deployment requires explicit confirmation through the ArgoCD UI or CLI. This allows for a final review before changes are applied:
syncPolicy: {} # no automatic synchronization
# Deployment is triggered manually:
# argocd app sync api-service-production
You can also configure sync windows — time windows during which synchronization is permitted. For example, blocking production deployments on Friday evenings and weekends is a standard practice.
Update Strategies
ArgoCD supports native Kubernetes strategies: RollingUpdate and Recreate. For more advanced scenarios — canary and blue/green — Argo Rollouts is used. This is an ArgoCD add-on that introduces a Rollout CRD as a replacement for the standard Deployment.
Secret Management in GitOps
The central challenge in GitOps is that secrets cannot be stored in Git in plaintext. Several approaches exist to address this.
Sealed Secrets
Sealed Secrets by Bitnami allows you to encrypt a secret using the cluster's public key. The encrypted SealedSecret can be safely committed to Git. A controller running inside the cluster decrypts it using the private key and creates a standard Secret.
# Install the kubeseal CLI
brew install kubeseal
# Fetch the cluster's public key
kubeseal --fetch-cert \
--controller-name=sealed-secrets-controller \
--controller-namespace=kube-system \
> pub-cert.pem
# Create a regular Secret and encrypt it
kubectl create secret generic db-credentials \
--from-literal=password=supersecret \
--dry-run=client -o yaml | \
kubeseal --cert pub-cert.pem \
--format yaml > db-credentials-sealed.yaml
# db-credentials-sealed.yaml can be safely committed to Git
The advantage of Sealed Secrets: it is fully GitOps-native — everything lives in the repository. The downside: if the cluster's private key is lost, all secrets must be recreated from scratch.
External Secrets Operator
External Secrets Operator (ESO) integrates with external secret stores: AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager, and Azure Key Vault. Only an ExternalSecret object — a reference to the secret in the external store — is kept in Git:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: password
remoteRef:
key: prod/api-service/db
property: password
ESO is the preferred approach for production in 2026, especially when an organization already uses a centralized secret store. Only metadata is stored in Git — never the actual secret values.
Multi-Environment: Dev, Staging, Production
There are two popular approaches to managing multiple environments in GitOps:
Directory-Based Approach (Recommended)
Each environment has its own subdirectory in the repository. All environments live on a single main branch. Changes flow through pull requests. Promotion between environments is a commit that updates the image tag in the directory of the next environment:
apps/api-service/overlays/dev/— auto-sync on every push to mainapps/api-service/overlays/staging/— sync after successful dev testingapps/api-service/overlays/production/— manual sync after approval
Branch-Based Approach
Each environment corresponds to a separate branch (dev, staging, main/production). ArgoCD watches a specific branch. Promotion is done via merges between branches. This approach is more complex to manage and is not recommended for new projects, as branches diverge over time and it becomes difficult to track what is deployed where.
For automated promotion between environments, a GitHub Actions workflow can be used to create a PR with the updated image tag targeting the production directory after a successful staging deployment.
Drift Detection and Self-Healing
One of ArgoCD's key capabilities is detecting configuration drift — a situation where the actual state of the cluster diverges from the desired state stored in Git.
Drift can occur due to:
- Manual
kubectl editorkubectl scalecommands run directly against the cluster - Autoscaling (HPA modifying replica counts)
- Kubernetes operators modifying resources
- Failures during previous synchronizations
ArgoCD continuously (every 3 minutes by default) compares the desired and actual states. When drift is detected, the application receives an OutOfSync status.
With selfHeal: true enabled, ArgoCD automatically reconciles the cluster back to the state defined in Git. This means any manual change will be overwritten — which is precisely the goal of GitOps: Git is the single source of truth.
An important nuance: if HPA manages replica counts, you should either omit the replicas field from your Deployment manifest or use an ArgoCD annotation to ignore that field — otherwise ArgoCD will continuously revert HPA's changes:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: api-service
spec:
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas
Measuring GitOps Success: DORA Metrics
The transition to GitOps should be measured through concrete metrics. The industry standard is DORA metrics (DevOps Research and Assessment):
- Deployment Frequency: how often the team deploys to production. GitOps lowers the barrier to deployment — a commit to the repository automatically triggers the process. Elite teams deploy multiple times per day.
- Lead Time for Changes: the time from a code commit to a working deployment in production. With GitOps and automated CI/CD, this can be reduced to minutes.
- Change Failure Rate: the percentage of deployments that cause incidents. Declarative deployments and PR-based code review help reduce this metric.
- Mean Time to Recovery (MTTR): how quickly the team recovers from an incident. In GitOps, a rollback is a
git revertfollowed by an ArgoCD sync — taking just minutes.
To collect these metrics in a Kubernetes environment, teams typically use the Prometheus + Grafana stack. ArgoCD exports metrics in Prometheus format by default: sync counts, application statuses, and last deployment timestamps.
# Example PromQL queries for ArgoCD monitoring
# Number of successful syncs in the last 24 hours
sum(increase(argocd_app_sync_total{phase="Succeeded"}[24h])) by (name)
# Applications in OutOfSync status
argocd_app_info{sync_status="OutOfSync"}
Conclusion: Common Mistakes When Adopting GitOps
Transitioning to GitOps means changing not just tools, but processes. Here are the most common mistakes teams make:
Mistake 1: Storing Secrets in Git in Plaintext
The most critical mistake of all. Use Sealed Secrets or External Secrets Operator from day one. Never commit a kind: Secret with unencrypted data.
Mistake 2: Making Manual Changes to the Cluster Alongside GitOps
If part of the team continues to use kubectl apply directly, GitOps loses its value. You need to establish an organizational policy against direct changes and configure RBAC so that developers lack edit permissions in the production namespace.
Mistake 3: A Single Monolithic GitOps Repository with No Structure
Without a clear directory structure, the repository quickly becomes unmanageable. Establish the base/overlays structure and team-level separation via AppProjects from the very beginning.
Mistake 4: Enabling Automatic Sync for Production from Day One
Start with manual synchronization for production. The team needs to get comfortable with the process and understand what ArgoCD is doing before granting it autonomy over a critical environment.
Mistake 5: Ignoring Drift Detection
If ArgoCD shows an application with an OutOfSync status, treat it as a signal to investigate. Do not simply click "Sync" without first understanding why the drift occurred.
Mistake 6: Not Testing Manifests in CI
Add manifest validation to the CI pipeline of your GitOps repository: kustomize build | kubeval or helm lint. This catches syntax errors before ArgoCD attempts to apply them to the cluster.
GitOps in 2026 is more than a toolset — it is a culture for working with infrastructure. A successful transition requires both the technical setup of ArgoCD and CI/CD pipelines, and a shift in team processes: everything through PRs, everything through Git, no manual changes.
Start small: deploy one non-critical service via ArgoCD in a dev environment. Make sure the team understands the cycle: commit → CI → manifest update → ArgoCD sync. Then gradually migrate the remaining services and environments. GitOps with ArgoCD is an investment that pays off through improved DORA metrics and greater deployment reliability.
Technologies
Tags
Ruslan Ismailov
Senior Web / Backend Developer. Senior web/backend developer with 9 years of experience. Stack: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, microservices, CI/CD. More about me →