DevOps

GitOps in 2026: Managing Kubernetes Infrastructure Through Git with ArgoCD and Automated CI/CD

Ruslan Ismailov Published 18 min read
G

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 main
  • apps/api-service/overlays/staging/ — sync after successful dev testing
  • apps/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 edit or kubectl scale commands 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 revert followed 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 →