DevOps & SRE No. 147

Argo CD GitOps skill for Claude Code

Every cluster change is a reviewed, revertable commit. Claude lays out a config repo with overlays per environment, generates Applications from ApplicationSets inside locked-down AppProjects, and orders syncs with waves and PreSync hooks. It explains OutOfSync drift instead of papering over it, keeps secrets out of Git with External Secrets or Sealed Secrets, and makes production promotion a pull request that moves one image tag.

  • nameargocd-gitops
  • size269 lines · ≈2,800 tokens
  • files2
  • licenseMIT
  • updated

What Claude sees before loading it

This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.

description: Sets up and operates GitOps delivery with Argo CD (and Flux where already in use) - repository layout, Application and ApplicationSet manifests, app-of-apps bootstrapping, AppProjects, sync policies with prune and self-heal, sync waves and hooks, drift and ignoreDifferences, secrets with Sealed Secrets, SOPS or External Secrets, and image promotion between environments through pull requests. Use when installing Argo CD or Flux, writing Application YAML, debugging OutOfSync or Degraded apps, or designing dev-staging-prod promotion.

Try asking

  • Set up Argo CD with an ApplicationSet that deploys our service to dev, staging and prod
  • This Argo app is always OutOfSync even right after a sync. Figure out why
  • Design an image promotion flow from staging to prod that goes through pull requests

Use it when

  • Installing or restructuring Argo CD or Flux
  • Writing Application, ApplicationSet or AppProject manifests
  • Apps are OutOfSync, Degraded, or syncing in the wrong order
  • Designing environment promotion and secrets for a GitOps repo

Skip it when

  • You need traffic-shifted canaries within one environment — use blue-green-deploys
  • The problem is the chart itself rather than how it is delivered — use helm-charts

How to check the result

argocd app wait reports Synced and Healthy for each environment, argocd app diff is empty, and reverting the promotion commit restores the previous version.

The common mistake it prevents

Turning on prune or deleting an Application with the resources finalizer without reviewing the diff, which deletes live workloads the moment it syncs.

~/.claude/skills/argocd-gitops/SKILL.md

--- name: argocd-gitops description: Sets up and operates GitOps delivery with Argo CD (and Flux where already in use) - repository layout, Application and ApplicationSet… license: MIT ---

GitOps is working when every change to a cluster arrives as a reviewed commit, the controller reconciles it within minutes, drift is either auto-corrected or explained, no plaintext secret exists in Git, and promoting to production is a pull request that changes one image tag or one directory.

Ground rules

  • Git is the source of truth. Never kubectl apply/edit resources owned by Argo CD except during an incident, and then commit the same change immediately.
  • Separate the config repo (manifests) from application source repos. CI builds images and opens PRs against the config repo; it does not run kubectl.
  • Pin everything: chart versions, image tags (or digests), targetRevision for production apps to a branch or tag you control.
  • Each team gets an AppProject that restricts source repos, destination namespaces and cluster-scoped resource kinds. Never give apps the default project in shared clusters.
  • Enabling prune: true on an existing app or deleting an Application with a resources finalizer deletes live resources. Confirm with the user and review the diff before either.

Workflow

- [ ] 1. Inspect: controller version, existing apps, repo layout, sync status
- [ ] 2. Lay out the config repo (base + overlays or chart + values per env)
- [ ] 3. Define AppProjects and the bootstrap (app-of-apps or ApplicationSet)
- [ ] 4. Write Applications with explicit sync policy and ordering
- [ ] 5. Handle secrets without plaintext in Git
- [ ] 6. Wire promotion (CI -> PR -> merge) per environment
- [ ] 7. Verify sync, health, and drift; document rollback

1. Inspect

argocd version
argocd app list -o wide
argocd app get <app> --refresh            # sync + health + conditions
argocd app diff <app>                     # live vs desired, exit code 1 when different
kubectl get applications,applicationsets -n argocd
flux get all -A                           # if Flux is in use instead

Argo CD 3.x is current. Notable 3.0 defaults: annotation-based resource tracking, update/delete RBAC no longer implicitly covering sub-resources, logs RBAC enforced. Check release notes before upgrading across minor versions.

2. Repository layout

platform-config/
├── bootstrap/root-app.yaml          # app-of-apps entry point (applied once by hand)
├── projects/                        # AppProject per team
├── apps/                            # Application/ApplicationSet definitions
│   ├── checkout.yaml
│   └── cluster-addons.yaml
└── services/checkout/
    ├── base/                        # kustomize base: deployment, service, hpa
    └── overlays/
        ├── dev/kustomization.yaml   # image tag, replicas, env config
        ├── staging/kustomization.yaml
        └── prod/kustomization.yaml
ChoiceUse when
Kustomize overlays per envIn-house services; promotion = change tag in overlay
Helm chart + values-<env>.yamlThird-party software or a shared internal chart
Directory per env, same branchDefault; avoid branch-per-environment (merges drift and conflict)
Separate repo per envStrict separation required by compliance; costs more automation

3. Bootstrap and projects

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata: { name: team-checkout, namespace: argocd }
spec:
  sourceRepos: ["https://github.com/acme/platform-config.git"]
  destinations:
    - { server: https://kubernetes.default.svc, namespace: "checkout-*" }
  clusterResourceWhitelist: []          # no cluster-scoped kinds
  namespaceResourceBlacklist:
    - { group: "", kind: ResourceQuota }

ApplicationSet generating one app per environment directory:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata: { name: checkout, namespace: argocd }
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
    - git:
        repoURL: https://github.com/acme/platform-config.git
        revision: main
        directories:
          - path: services/checkout/overlays/*
  template:
    metadata:
      name: "checkout-{{.path.basename}}"
    spec:
      project: team-checkout
      source:
        repoURL: https://github.com/acme/platform-config.git
        targetRevision: main
        path: "{{.path.path}}"
      destination:
        server: https://kubernetes.default.svc
        namespace: "checkout-{{.path.basename}}"
      syncPolicy:
        automated: { prune: true, selfHeal: true }
        syncOptions: [CreateNamespace=true, ServerSideApply=true]

Set spec.syncPolicy.preserveResourcesOnDeletion: true on ApplicationSets in production so deleting the set (or a generator glitch) does not cascade-delete workloads.

PatternUse when
App-of-apps (root Application pointing at apps/)Small number of hand-written apps, explicit control
ApplicationSet git directory generatorOne app per folder (services, environments)
ApplicationSet cluster generatorSame add-ons on many clusters
Matrix generator (clusters x directories)Fleet of clusters each running many apps

4. Applications, sync policy and ordering

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: checkout-prod
  namespace: argocd
  finalizers: [resources-finalizer.argocd.argoproj.io]
spec:
  project: team-checkout
  source:
    repoURL: https://github.com/acme/platform-config.git
    targetRevision: main
    path: services/checkout/overlays/prod
  destination: { server: https://kubernetes.default.svc, namespace: checkout-prod }
  syncPolicy:
    automated: { prune: true, selfHeal: true }
    syncOptions: [ServerSideApply=true, PruneLast=true]
    retry: { limit: 5, backoff: { duration: 10s, factor: 2, maxDuration: 3m } }
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers: [/spec/replicas]     # HPA owns replicas

Sync policy decisions:

SettingDefault recommendation
automatedOn for dev/staging; on for prod only with PR review on the config repo
selfHealOn; manual hotfixes get reverted, which is the point
pruneOn once the app is stable; review argocd app diff first
ServerSideApply=trueOn for large CRDs and fields managed by multiple controllers
Replace=trueAvoid; deletes and recreates

Ordering inside one sync: phases PreSync -> Sync -> PostSync (SyncFail on failure), and within a phase ascending argocd.argoproj.io/sync-wave (default 0, negatives first). Argo waits for each wave to become healthy before the next.

metadata:
  annotations:
    argocd.argoproj.io/sync-wave: "-1"           # e.g. namespace, CRDs, secrets first
---
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation,HookSucceeded

Migrations as PreSync hooks must be backward-compatible with the running version (see schema-migrations); a failed hook stops the sync, which is the desired safety.

5. Secrets

OptionChoose when
External Secrets Operator + cloud secret managerDefault for cloud clusters; Git holds only ExternalSecret references
Sealed SecretsNo external secret store; encrypted blobs per cluster in Git
SOPS (age/KMS) via Flux native support or an Argo CD pluginAlready using SOPS; Flux handles it natively

Never use Helm values.yaml with plaintext secrets, and never rely on “private repo” as protection. Rotate any secret that was ever committed.

6. Promotion

Default flow: CI builds image:sha-abc123, then opens a PR that changes the tag in overlays/dev; merge auto-syncs dev. Promotion to staging and prod is a PR copying the same tag to the next overlay, gated by checks and required reviewers.

cd services/checkout/overlays/staging
kustomize edit set image ghcr.io/acme/checkout=ghcr.io/acme/checkout:sha-abc123
git commit -am "checkout: promote sha-abc123 to staging" && gh pr create --fill

For progressive delivery inside one environment, pair with Argo Rollouts (see blue-green-deploys). For automated tag bumps, Argo CD Image Updater or Flux image automation can write commits; keep production promotion a human-reviewed PR.

Detailed promotion and multi-cluster patterns: read references/promotion-patterns.md when designing more than two environments, multiple clusters, or Flux-based promotion.

7. Verify and roll back

argocd app wait checkout-staging --sync --health --timeout 300
argocd app history checkout-prod

Rollback = git revert the promotion commit. argocd app rollback works only with auto-sync disabled and is reverted by the next sync, so use it only as an incident stopgap and follow with a revert commit.

Debugging

SymptomLikely causeAction
OutOfSync right after syncController/webhook mutates fields (defaults, HPA replicas, injected sidecars)argocd app diff; add ignoreDifferences with jsonPointers or managedFieldsManagers
Sync stuck “Progressing”Health check waits on a resource that never becomes readyargocd app get resource tree; check pod events; custom Lua health for CRDs
ComparisonError / manifest generation failedBad kustomize/helm render, missing repo credsRender locally: kustomize build / helm template; check repo-server logs
App deleted resources unexpectedlyPrune with a path or generator changeRestore via revert; add Prune=false annotation on critical resources
Changes take minutes to appearPolling interval (default ~3 min)Configure a Git webhook to the Argo CD server
“resource not permitted in project”AppProject whitelistExtend the project deliberately, not by switching to default

Gotchas

  • ignoreDifferences hides drift from the diff, but with RespectIgnoreDifferences=true absent, a sync still overwrites those fields. Add the sync option when another controller owns the field.
  • Helm hooks are translated to Argo hooks; helm.sh/hook: pre-install runs on every sync as PreSync, not only on install.
  • CRDs and their CRs in the same sync fail on first apply unless CRDs are in an earlier wave or SkipDryRunOnMissingResource=true is set on the CRs.
  • Automated sync will not re-sync the same commit after a failure until retry or a new commit; read operationState before assuming it is applied.
  • Argo CD itself should be managed by Argo CD (self-managed app) only after the manual bootstrap is reproducible from the repo.

Output

**Apps:** checkout-{dev,staging,prod} via ApplicationSet (git directories)
**Project:** team-checkout, namespaces checkout-*, no cluster-scoped kinds
**Sync:** automated prune+selfHeal; HPA replicas ignored with RespectIgnoreDifferences
**Secrets:** ExternalSecret -> AWS Secrets Manager; none in Git
**Promotion:** CI PR to dev overlay; manual PR dev->staging->prod with required review
**Verified:** argocd app wait healthy in all envs; diff clean; rollback = git revert

Bundled files

Loaded only when a step in the skill needs them. Review them here before installing.

references/promotion-patterns.md 88 lines · 4.2 KB
# Promotion and multi-cluster patterns

Read this when the setup has more than two environments, more than one cluster, or uses
Flux. The core rule never changes: promotion is a Git change that a human can review and
revert.

## Environment promotion models

| Model | How promotion works | Strengths | Weaknesses |
|---|---|---|---|
| Overlay per environment (recommended) | PR edits the image tag in `overlays/<env>` | Diff shows exactly what moves; one branch | Requires tooling to copy tags between overlays |
| Values file per environment (Helm) | PR edits `values-<env>.yaml` `image.tag` | Familiar to Helm users | Values files drift in non-tag settings |
| Branch per environment | Merge `staging` into `prod` branch | Looks simple | Merges carry unrelated changes; hotfix cherry-picks drift; avoid |
| Release manifest / version file | One `releases.yaml` lists versions per env | Single place to audit | Needs a templating step (ApplicationSet + Helm parameter) |

### Promotion PR checklist

- Same artifact moves forward: tag or digest identical to what passed the previous env.
- PR body links the CI run, the staging verification, and the change list between the
  old and new tag (`git log old..new --oneline` in the source repo).
- Required checks: kustomize/helm render, kubeconform schema validation, policy checks
  (Kyverno/Conftest), and a diff preview comment (`argocd app diff --local` or a
  rendered-manifest diff action).
- Production overlay protected by CODEOWNERS.

### Automating the tag copy

```bash
# promote the exact tag running in staging to prod
TAG=$(yq '.images[] | select(.name=="ghcr.io/acme/checkout") | .newTag' \
  services/checkout/overlays/staging/kustomization.yaml)
(cd services/checkout/overlays/prod && kustomize edit set image ghcr.io/acme/checkout=ghcr.io/acme/checkout:$TAG)
git switch -c promote-checkout-$TAG && git commit -am "checkout: promote $TAG to prod"
gh pr create --title "Promote checkout $TAG to prod" --body "Staging verified: <link>"
```

Kargo (an open source promotion tool from Akuity) models stages and freight for
multi-stage promotion if hand-rolled PR automation becomes unwieldy; evaluate it before
writing a large custom promoter.

## Multi-cluster

- Register clusters with `argocd cluster add <context>` (creates a ServiceAccount in the
  target). Prefer one Argo CD per environment tier (non-prod, prod) over one global
  instance when blast radius matters; a single instance managing prod and dev means a
  dev misconfiguration in Argo itself can touch prod.
- Label cluster secrets (`env=prod`, `region=eu-west-1`) and select them with the
  ApplicationSet cluster generator:

```yaml
generators:
  - clusters:
      selector:
        matchLabels: { env: prod }
```

- Roll out to a fleet in waves: ApplicationSet progressive syncs (RollingSync strategy)
  can order clusters by label steps; confirm the feature status in your Argo CD version
  before relying on it, otherwise stage by separate ApplicationSets per wave.

## Flux equivalents

| Argo CD | Flux |
|---|---|
| Application | `Kustomization` (kustomize.toolkit.fluxcd.io) or `HelmRelease` |
| Repo credentials | `GitRepository` / `OCIRepository` / `HelmRepository` source objects |
| Sync waves | `dependsOn` between Kustomizations, plus `wait: true` |
| selfHeal / prune | Reconciles on `interval`; `prune: true` on the Kustomization |
| Image Updater | `ImageRepository` + `ImagePolicy` + `ImageUpdateAutomation` |
| SOPS plugin | Native: `decryption: { provider: sops, secretRef: ... }` |

```bash
flux bootstrap github --owner=acme --repository=platform-config --path=clusters/prod
flux get kustomizations -A
flux reconcile kustomization checkout --with-source
flux diff kustomization checkout --path ./services/checkout/overlays/prod
flux suspend kustomization checkout     # incident freeze; resume afterwards
```

## Incident freeze

During an incident, stop reconciliation for one app rather than fighting the controller:

- Argo CD: disable auto-sync on the Application (`argocd app set <app> --sync-policy none`),
  apply the fix, then commit it and re-enable auto-sync.
- Flux: `flux suspend` the Kustomization, then `flux resume` after committing.

Record the freeze in the incident timeline so someone re-enables it.

About this skill

Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.

esc