Skip to content

Sync Strategies, GitOps Repo Patterns & Image Updater


The Two GitOps Repo Patterns

In real-world GitOps, there are always two repositories:

Repo Contains Updated by
Source / App Repo Application source code (Go, Python, Node, etc.) Developers (feature branches, PRs)
CD / Config Repo Kubernetes manifests (Helm charts, Kustomize overlays, plain YAML) CI pipeline or image updater

ArgoCD only ever watches the CD / Config Repo — it does not care about source code.

Source Repo ──► CI (build + push image) ──► CD Repo ──► ArgoCD ──► Cluster

Sync Strategy 1 — Manifest-Based (Git-Driven)

How it works:

  1. CI builds a new Docker image and pushes it to a registry
  2. CI (or a bot) updates the CD repo — either:
  3. Changes the image tag directly in a manifest YAML
  4. Updates an image.env / IMAGE_TAG variable file that the manifest references
  5. Bumps values.yaml (for Helm) or kustomization.yaml image field (for Kustomize)
  6. ArgoCD detects the Git change (polling every 3 min, or instant via webhook)
  7. ArgoCD applies the updated manifests to the cluster

Pros: Full audit trail in Git. Every deployment is a Git commit. Easy rollback (revert commit).

Example — Kustomize image update in CI:

# In CI pipeline after building image:
cd cd-repo/overlays/production
kustomize edit set image myapp=myregistry.io/myapp:${GIT_SHA}
git commit -am "chore: update myapp image to ${GIT_SHA}"
git push
# ArgoCD picks this up and syncs automatically

Example — Helm values update in CI:

# Using yq to update image tag in values.yaml
yq e '.image.tag = strenv(IMAGE_TAG)' -i values-prod.yaml
git commit -am "chore: bump image tag to ${IMAGE_TAG}"
git push


Sync Strategy 2 — Image Updater (Registry-Driven)

How it works:

  1. CI builds and pushes a new image to a container registry
  2. ArgoCD Image Updater (a separate component installed alongside ArgoCD) watches the registry for new image tags
  3. When a new tag matching a pattern appears, Image Updater:
  4. Option A: commits the updated tag back to Git (write-back to CD repo)
  5. Option B: updates the ArgoCD Application in-memory (no Git commit, less audit trail)
  6. ArgoCD picks up the change and syncs

No one manually updates the CD repo — the image updater does it.

Installing ArgoCD Image Updater

kubectl apply -n argocd \
  -f https://raw.githubusercontent.com/argoproj-labs/argocd-image-updater/stable/manifests/install.yaml

Annotating the Application for Image Updater

Image Updater is annotation-driven on the Application CR:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: argocd
  annotations:
    argocd-image-updater.argoproj.io/image-list: myapp=myregistry.io/myapp
    argocd-image-updater.argoproj.io/myapp.update-strategy: semver
    argocd-image-updater.argoproj.io/write-back-method: git
    argocd-image-updater.argoproj.io/git-branch: main
    argocd-image-updater.argoproj.io/myapp.kustomize.image-name: myregistry.io/myapp

Update Strategies

Strategy Behavior
semver Update to the newest tag that satisfies a semver constraint (e.g. ~1.2)
latest Always use the most recently pushed tag
digest Track a specific tag (e.g. latest) by its immutable digest
name Alphabetically newest tag

Sync Waves & Hooks — Ordering Within a Sync

ArgoCD lets you control the order in which resources are applied during a sync.

Sync Waves

Annotate resources with a wave number. Lower numbers sync first.

metadata:
  annotations:
    argocd.argoproj.io/sync-wave: "1"

Typical ordering: - Wave 0: Namespaces, CRDs - Wave 1: RBAC, ConfigMaps, Secrets - Wave 2: Databases, StatefulSets - Wave 3: Application Deployments - Wave 4: Ingress, Services

Sync Hooks

Hook When it runs
PreSync Before any resources are applied
Sync During the sync, in wave order
PostSync After all resources are Healthy
SyncFail If the sync fails
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
  template:
    spec:
      containers:
        - name: migrate
          image: myapp:latest
          command: ["python", "manage.py", "migrate"]
      restartPolicy: Never

ApplicationSet — Generating Many Applications

ApplicationSet generates multiple Application objects from a single manifest.

List Generator Example

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: microservices
  namespace: argocd
spec:
  generators:
    - list:
        elements:
          - app: frontend
            namespace: prod-frontend
          - app: backend
            namespace: prod-backend
  template:
    metadata:
      name: '{{app}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/my-org/cd-repo
        targetRevision: HEAD
        path: 'apps/{{app}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{namespace}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

Git Generator (auto-discover apps from folders)

generators:
  - git:
      repoURL: https://github.com/my-org/cd-repo
      revision: HEAD
      directories:
        - path: apps/*

Health Checks

Resource Healthy when
Deployment availableReplicas >= minReadyReplicas
StatefulSet All replicas ready
DaemonSet All desired pods ready
Pod Phase = Running, all containers ready
Service Always healthy (unless LoadBalancer with no IP)

Custom health checks can be written in Lua and registered in argocd-cm.


Rollback

argocd app history my-app
argocd app rollback my-app 3

Rolling back while selfHeal: true is enabled will cause ArgoCD to immediately re-sync forward. Pause the app first:

argocd app patch my-app --patch '{"spec":{"syncPolicy":null}}' --type merge