DevOps

Setting Up GitOps Workflows with Argo CD or Flux

Setting Up GitOps Workflows With Argo CD Or Flux

Introduction

In modern DevOps, Git isn’t just for version control—it’s the single source of truth for your entire infrastructure. GitOps takes this concept further by automating deployments through Git repositories. Instead of manually pushing updates with kubectl, your Kubernetes environment automatically syncs with the desired state defined in Git. Tools like Argo CD and Flux make this process seamless. In this comprehensive guide, you’ll learn how GitOps works, implement both tools step-by-step, and understand when to choose each approach.

What Is GitOps?

GitOps is a DevOps practice where Git acts as the central control plane for infrastructure and application deployment. The desired state of your system—configurations, manifests, and policies—lives in Git. Whenever someone pushes a change, a GitOps controller automatically reconciles that state to your cluster.

Core Principles

  • Declarative – Everything described as code (YAML, HCL, etc.)
  • Versioned and Immutable – Git history provides complete audit trail
  • Pulled Automatically – Agents pull desired state from Git
  • Continuously Reconciled – Drift is automatically corrected

Push vs Pull Deployment

Traditional CI/CD uses push deployments—the pipeline pushes changes to the cluster. GitOps uses pull deployments—an in-cluster agent pulls the desired state from Git. This is more secure because cluster credentials never leave the cluster.

Repository Structure

A well-organized GitOps repository is crucial for maintainability:

gitops-repo/
├── apps/
│   ├── base/
│   │   ├── deployment.yaml
│   │   ├── service.yaml
│   │   └── kustomization.yaml
│   ├── staging/
│   │   ├── kustomization.yaml
│   │   └── patches/
│   │       └── replicas.yaml
│   └── production/
│       ├── kustomization.yaml
│       └── patches/
│           ├── replicas.yaml
│           └── resources.yaml
├── infrastructure/
│   ├── controllers/
│   │   ├── cert-manager/
│   │   ├── ingress-nginx/
│   │   └── monitoring/
│   └── crds/
├── clusters/
│   ├── staging/
│   │   ├── apps.yaml
│   │   └── infrastructure.yaml
│   └── production/
│       ├── apps.yaml
│       └── infrastructure.yaml
└── README.md

Setting Up Argo CD

Argo CD is a Kubernetes-native GitOps tool with a powerful web UI. It continuously monitors Git repositories and syncs application manifests to clusters.

Install Argo CD

# Create namespace
kubectl create namespace argocd

# Install Argo CD
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Wait for pods to be ready
kubectl wait --for=condition=available --timeout=300s deployment/argocd-server -n argocd

# Get initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

# Access the UI (port-forward)
kubectl port-forward svc/argocd-server -n argocd 8080:443

Install Argo CD CLI

# macOS
brew install argocd

# Linux
curl -sSL -o argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
chmod +x argocd
sudo mv argocd /usr/local/bin/

# Login
argocd login localhost:8080 --username admin --password <password> --insecure

Create an Application

# application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  
  source:
    repoURL: https://github.com/myorg/gitops-repo.git
    targetRevision: main
    path: apps/production
  
  destination:
    server: https://kubernetes.default.svc
    namespace: my-app
  
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
      allowEmpty: false
    syncOptions:
      - CreateNamespace=true
      - PrunePropagationPolicy=foreground
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m
# Apply the application
kubectl apply -f application.yaml

# Or use CLI
argocd app create my-app \
  --repo https://github.com/myorg/gitops-repo.git \
  --path apps/production \
  --dest-server https://kubernetes.default.svc \
  --dest-namespace my-app \
  --sync-policy automated \
  --auto-prune \
  --self-heal

App of Apps Pattern

Manage multiple applications with a single root application:

# apps/root-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: root-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/gitops-repo.git
    targetRevision: main
    path: clusters/production
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
# clusters/production/apps.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: frontend
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/gitops-repo.git
    path: apps/frontend/production
  destination:
    server: https://kubernetes.default.svc
    namespace: frontend
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: backend
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/gitops-repo.git
    path: apps/backend/production
  destination:
    server: https://kubernetes.default.svc
    namespace: backend
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Projects for Multi-Tenancy

# project.yaml
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: team-alpha
  namespace: argocd
spec:
  description: Project for Team Alpha
  
  # Restrict source repos
  sourceRepos:
    - 'https://github.com/myorg/team-alpha-*'
  
  # Restrict destination clusters/namespaces
  destinations:
    - namespace: 'alpha-*'
      server: https://kubernetes.default.svc
  
  # Allowed cluster resources
  clusterResourceWhitelist:
    - group: ''
      kind: Namespace
  
  # Namespace resources (all by default)
  namespaceResourceWhitelist:
    - group: '*'
      kind: '*'
  
  # Role-based access
  roles:
    - name: developer
      description: Developer access
      policies:
        - p, proj:team-alpha:developer, applications, get, team-alpha/*, allow
        - p, proj:team-alpha:developer, applications, sync, team-alpha/*, allow
      groups:
        - team-alpha-developers

Setting Up Flux

Flux by Weaveworks is a lightweight, Git-native GitOps tool. It works directly with your Git workflow without requiring a separate UI.

Install Flux CLI

# macOS
brew install fluxcd/tap/flux

# Linux
curl -s https://fluxcd.io/install.sh | sudo bash

# Verify installation
flux check --pre

Bootstrap Flux

# GitHub bootstrap
export GITHUB_TOKEN=<your-token>

flux bootstrap github \
  --owner=myorg \
  --repository=gitops-repo \
  --branch=main \
  --path=clusters/production \
  --personal

# GitLab bootstrap
export GITLAB_TOKEN=<your-token>

flux bootstrap gitlab \
  --owner=myorg \
  --repository=gitops-repo \
  --branch=main \
  --path=clusters/production

Configure GitRepository Source

# flux-system/gitrepository.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: app-source
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/myorg/app-manifests.git
  ref:
    branch: main
  secretRef:
    name: git-credentials  # If private repo

Create Kustomization

# clusters/production/apps.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: apps
  namespace: flux-system
spec:
  interval: 10m
  targetNamespace: default
  sourceRef:
    kind: GitRepository
    name: flux-system
  path: ./apps/production
  prune: true
  healthChecks:
    - apiVersion: apps/v1
      kind: Deployment
      name: frontend
      namespace: frontend
  timeout: 2m

Helm Releases with Flux

# infrastructure/monitoring/prometheus.yaml
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
  name: prometheus-community
  namespace: flux-system
spec:
  interval: 1h
  url: https://prometheus-community.github.io/helm-charts
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: prometheus
  namespace: monitoring
spec:
  interval: 1h
  chart:
    spec:
      chart: kube-prometheus-stack
      version: "55.x"
      sourceRef:
        kind: HelmRepository
        name: prometheus-community
        namespace: flux-system
  values:
    prometheus:
      prometheusSpec:
        retention: 15d
        resources:
          requests:
            memory: 1Gi
            cpu: 500m
    grafana:
      enabled: true
      adminPassword: ${GRAFANA_ADMIN_PASSWORD}
  valuesFrom:
    - kind: Secret
      name: prometheus-values
      valuesKey: values.yaml

Image Automation

Automatically update container images when new versions are pushed:

# Install image automation controllers
flux bootstrap ... --components-extra=image-reflector-controller,image-automation-controller
# image-repository.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
  name: myapp
  namespace: flux-system
spec:
  image: ghcr.io/myorg/myapp
  interval: 5m
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
  name: myapp
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: myapp
  policy:
    semver:
      range: 1.x.x
---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
  name: myapp
  namespace: flux-system
spec:
  interval: 5m
  sourceRef:
    kind: GitRepository
    name: flux-system
  git:
    checkout:
      ref:
        branch: main
    commit:
      author:
        email: flux@myorg.com
        name: Flux
      messageTemplate: 'chore: update {{.AutomationObject.Name}} to {{.NewTag}}'
    push:
      branch: main
  update:
    path: ./apps
    strategy: Setters
# deployment.yaml - Add image policy marker
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      containers:
      - name: myapp
        image: ghcr.io/myorg/myapp:1.0.0 # {"$imagepolicy": "flux-system:myapp"}

Argo CD vs Flux: Comparison

Feature Argo CD Flux
UI Rich web UI CLI only (use Weave GitOps UI)
Architecture Single component Modular controllers
Multi-tenancy AppProjects Kustomization namespacing
Helm Support Native HelmRelease CRD
Image Automation Argo CD Image Updater Built-in controllers
Learning Curve Lower (UI helps) Steeper (Git-native)
Best For Teams wanting visibility Teams preferring Git workflows

Common Mistakes to Avoid

1. Not Using Separate Repos for Config

# Wrong - app code and config in same repo
myapp/
  ├── src/
  ├── k8s/
  └── Dockerfile

# Correct - separate config repo
myapp/           # Source code
gitops-config/   # K8s manifests

2. Storing Secrets in Git

# Wrong - plain secrets in Git
apiVersion: v1
kind: Secret
data:
  password: cGFzc3dvcmQxMjM=  # Never do this!

# Correct - use Sealed Secrets or External Secrets
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: db-credentials
spec:
  encryptedData:
    password: AgBy3i4OJSWK+...

3. No Health Checks

# Flux Kustomization with health checks
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
spec:
  healthChecks:
    - apiVersion: apps/v1
      kind: Deployment
      name: frontend
    - apiVersion: apps/v1
      kind: Deployment  
      name: backend
  timeout: 5m

4. Missing Notifications

# Flux notification provider
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Provider
metadata:
  name: slack
  namespace: flux-system
spec:
  type: slack
  channel: deployments
  secretRef:
    name: slack-webhook
---
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Alert
metadata:
  name: on-call
  namespace: flux-system
spec:
  providerRef:
    name: slack
  eventSeverity: error
  eventSources:
    - kind: Kustomization
      name: '*'
    - kind: HelmRelease
      name: '*'

Final Thoughts

GitOps brings reliability, auditability, and automation to Kubernetes deployments. Argo CD excels when you need a rich UI, RBAC, and enterprise features. Flux shines when you prefer a lightweight, Git-native approach with powerful image automation. Both tools follow the same core principles—choose based on your team’s workflow preferences. Start with a single application, implement proper secrets management, and gradually expand as your GitOps practice matures.

To extend your automation, read Infrastructure as Code with Terraform and Blue/Green vs Canary Deployments. For official documentation, visit the Argo CD Documentation and the Flux Documentation.

Leave a Comment