Skip to main content
RunBook Academy

Git, CI/CD & GitOpsCV · GitOps Anti-PatternsUnsafePrune

Unsafe prune — automated deletion without a diff, a sandbox, or an opt-out

Advanced⏱ ~24 mingitargocdflux

What you'll learn

  • Identify the three controls that must accompany an enabled prune policy
  • Apply dry-run diff before every prune-enabled sync and read the delete lines
  • Configure a sandbox cluster for first-prune verification of a refactored chart
  • Use the Prune=false sync option override for the Application or resource that must not be deleted

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

The default sync policy in a freshly-installed Argo CD or Flux is prune-disabled. The first thing many teams do is flip it on, because the controller has reconciled adds and updates for weeks and the symmetry seems obvious. It is not. Prune is a deletion primitive; the controller applies it on every sync, without prompting.

The three controls prune needs

Prune is survivable when paired with three controls. Remove any one and the pattern becomes unsafe.

    flowchart LR
        A["Prune-enabled Application"] --> B["Diff"]
        B --> C["Sandbox sync"]
        C --> D["Production sync"]
        D --> E["Cluster matches Git"]
        B -.->|"missing"| F["Typo deletes needed resource"]
        C -.->|"missing"| G["Refactor breaks consumers"]
        D -.->|"missing"| H["Hotfix reverted by next sync"]
  • Dry-run diff. Before every prune-enabled sync, the team runs argocd app diff $APP_NAME --server-side-generate. The diff prints every create, update, and delete the sync will execute.
  • Sandbox cluster. The first prune-enabled sync of an Application is run against a non-production cluster that mirrors the production structure but carries no real traffic.
  • Prune=false opt-out. The Application that must not prune carries --sync-option Prune=false (set with argocd app set); the single resource that must survive carries the argocd.argoproj.io/sync-options: Prune=false annotation.

The team that skips all three runs argocd app sync $APP_NAME --prune and trusts the chart. The team that has all three runs the diff, reads the delete lines, confirms the sandbox behaviour, and only then runs the production sync.

Why the diff is the cheapest control

The diff runs in seconds, has no side effects, and produces the exact delete list the next sync will execute. A team that skips the diff on a prune-enabled sync has chosen to trust the chart without verifying it. The diff is also the audit trail: a CI job that runs argocd app diff on every pull request produces a record of what the sync would do.

argocd app diff "$APP_NAME" --server-side-generate

The --server-side-generate flag matches the apply mode the controller will use. The output includes a line for each delete candidate with the resource’s Kind, name, and namespace.

The sandbox cluster as first-prune verification

A sandbox cluster is a non-production Kubernetes cluster where the first prune-enabled sync is run end-to-end. The cluster mirrors production’s structure but carries no real traffic. A failed sandbox sync catches the unintended delete before it reaches production.

argocd app sync "$APP_NAME" \
  --prune \
  --server "$SANDBOX_ARGOCD"

A sandbox sync that deletes an unintended resource costs the team an hour; the same deletion in production costs the team a customer.

The Prune=false escape hatch

The escape hatch for the Application or resource that must not be deleted:

argocd app set "$APP_NAME" \
  --sync-option Prune=false

The flag sets the Prune=false sync option on the Application’s standing configuration: syncs stop deleting its resources until the option is removed with argocd app set "$APP_NAME" --sync-option '!Prune=false' (the ! prefix removes the option). A single resource opts out with the argocd.argoproj.io/sync-options: Prune=false annotation. Flux supports the same standing pattern through spec.prune: false on the Kustomization.

A team that abandons pruning wholesale to protect a single risky resource has lost the prune discipline; the Application-level option and the per-resource annotation are the scoped opt-outs, and a manual argocd app sync deletes nothing unless it is passed --prune.

Production discipline

  1. Diff is paired with every prune-enabled sync.
  2. The first prune-enabled sync of an Application runs in a sandbox first.
  3. Prune=false is a scoped, reversible opt-out.
  4. Branch protection is the upstream safety.

Cross-course references

  • This course, Part LXXX (GitOpsPruning) - the prune model, the safety mechanisms, the incident walkthrough.
  • This course, Part LXXXVII (IncidentGitOps) - break-glass procedures and the reconcile-back requirement.

Quiz

Knowledge check · 4 questions

  1. Q1. An Application is configured to prune. A chart refactor removes a manifest the cluster still consumes. What is the cheapest way to confirm the next sync will delete only the intended resources?

  2. Q2. Setting `Prune=false` on an Application permanently disables prune for all future syncs.

  3. Q3. Name the three controls that must accompany an enabled prune policy.

  4. Q4. Combine the three controls into a procedure for a chart refactor that removes two deprecated resources.

    A team is preparing a chart refactor that removes two deprecated ConfigMaps. The Application is configured with automated sync and `--auto-prune`. The team needs to land the refactor without deleting a third ConfigMap that is also consumed by an admission controller outside the chart's inventory.

Passing score: 75%. Answers are checked in this browser.