k8wiz
Back to Articles

Ready to test your Argocd knowledge? Try our Argocd quiz!

ArgoCD v2 to v3: Every Breaking Change, New Feature, and Migration Step You Need to Know

ArgoCD v3 landed in May 2025 and the v3.x line has been moving fast since then. The maintainers called it 'small but mighty' — and they were right. Most breaking changes are deliberate cleanups of long-standing anti-patterns, not rewrites. But if you run ArgoCD in production and skip the migration checklist, you will hit issues. This article covers everything that changed, what it means operationally, and exactly how to upgrade safely.

11 min read
By k8wiz Team
argocdgitopskubernetesdevopscdcicd

The v3.x Release Timeline at a Glance

Before getting into specifics, here is the release cadence so far:

VersionGA DateTheme
v3.0May 2025Architectural cleanups, breaking changes, security defaults
v3.1August 2025Native OCI registry support, CLI plugins, Source Hydrator improvements
v3.2November 2025ApplicationSet observability, Progressive Sync, advanced health checks
v3.3December 2025PreDelete hooks, CRD-level RBAC, Source Hydrator behavior changes
v3.4RC available (April 2026)Next iteration

ArgoCD 2.14 reached End of Life on November 4, 2025 with the release of v3.2. If you are still running 2.x, you are now in unsupported territory.


What Changed in v3.0: The Breaking Changes

The v3.0 philosophy was to finally enforce what had been recommended but optional in v2. This means configuration patterns that worked in v2 will silently break or explicitly error in v3.

1. Repository Configuration Removed from argocd-cm

This is the single most common migration issue teams hit. In v2, you could configure repositories directly inside the argocd-cm ConfigMap using keys like repositories, repository.credentials, and helm.repositories. This approach was deprecated years ago but never enforced.

v3.0 removes this entirely. All repository configuration must now live in Kubernetes Secrets.

Before upgrading, check if you are still using the old pattern:

kubectl get cm argocd-cm -n argocd \
  -o=jsonpath="[{.data.repositories}, {.data['repository.credentials']}, {.data['helm.repositories']}]"

If this returns non-empty values, you need to migrate each repository to a properly labeled Secret before upgrading. Here is an example:

# v2 style (in argocd-cm) — NO LONGER WORKS IN v3
# repositories: |
#   - url: https://github.com/my-org/my-repo.git
#     sshPrivateKeySecret:
#       name: repo-secret
#       key: sshPrivateKey

# v3 style — use a labeled Secret
apiVersion: v1
kind: Secret
metadata:
  name: my-repo
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: repository
stringData:
  url: https://github.com/my-org/my-repo.git
  type: git
  password: <your-token>
  username: <your-username>

2. Fine-Grained RBAC Is Now the Default

In v2, granting update or delete on an Application automatically applied those permissions to all of its sub-resources (Pods, Deployments, ConfigMaps, etc.). This was convenient but a significant security risk — a user who could delete an Application could also delete any resource inside it.

In v3, update and delete actions apply only to the Application itself. To act on sub-resources, you must define explicit policies.

# v2 behavior — this policy ALSO granted access to sub-resources
p, dev-team, applications, delete, default/prod-app, allow

# v3 — you must be explicit about sub-resources
p, dev-team, applications, delete, default/prod-app, allow
p, dev-team, applications, delete/*/Pod/*, default/prod-app, allow
p, dev-team, applications, delete/*/Deployment/*, default/prod-app, allow

You can also write more targeted policies:

# Allow deleting only Pods in a specific app
p, example-user, applications, delete/*/Pod/*/*, default/prod-app, allow

If you need time to migrate and want to preserve the v2 behavior temporarily, set this flag in argocd-cm:

data:
  server.rbac.disableApplicationFineGrainedRBACInheritance: "false"

Treat this as a temporary bridge, not a permanent solution.

3. Logs RBAC Enforcement Now Mandatory

The ability to toggle log RBAC enforcement via server.rbac.log.enforce.enable in argocd-cm has been removed in v3. Log access is now always enforced as a separate RBAC resource, regardless of configuration.

If any of your roles used applications, get in v2 and relied on that implicitly granting log access, those roles need explicit logs, get policies added:

# Add this for any role that needs log access
p, role:developer, logs, get, */*, allow

After upgrading, remove server.rbac.log.enforce.enable from your argocd-cm if it was there.

4. ApplicationSet applyNestedSelectors Is Now Always true

The applyNestedSelectors field in matrix and merge generators controlled whether label selectors were applied at each nesting level. In v2 this defaulted to false. In v3, it is hardcoded to true and the field itself is removed.

If you had ApplicationSets using matrix or merge generators with complex selector logic that relied on applyNestedSelectors: false, review your ApplicationSet specs before upgrading. The behavior will change automatically and generated Applications may differ.

5. In-Cluster Restriction Is Now Enforced

In v2, you could explicitly disable in-cluster access in your configuration, but ArgoCD would still allow it under certain conditions. In v3, if in-cluster is explicitly disabled, it is truly disabled — no fallback behavior.

6. Dex Subject Claim Change

If you use Dex for SSO, the RBAC subject used to be derived from the sub claim. In v3 it now comes from federated_claims.user_id. If you have user-specific RBAC policies (not group-based), you need to decode your existing sub claim values and update policies to use the new user ID.

7. Default Resource Exclusions Added

v3.0 ships with a default resource.exclusions list in argocd-cm that excludes high-churn, controller-managed Kubernetes resources that are typically not managed through Git. The exclusion list includes:

  • Endpoints, EndpointSlice, Lease
  • SelfSubjectReview, TokenReview, LocalSubjectAccessReview
  • CertificateSigningRequest, PolicyReport

This reduces unnecessary API server load. If you explicitly need ArgoCD to manage any of these resource types, you will need to override the exclusions.


New Features Across the v3.x Line

Source Hydrator (v3.0+)

The Source Hydrator is one of the most architecturally significant additions to ArgoCD in years. It adds a rendering layer between your source repository and the final manifests that ArgoCD syncs — allowing you to use "dry" source manifests (Helm templates, Kustomize overlays) and have ArgoCD hydrate and commit the rendered output to a separate Git branch.

This solves the long-standing problem of drift between templated sources and the actual state committed to Git. It is particularly useful for teams that need full auditability on rendered manifests.

To enable it:

# argocd-cmd-params-cm
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cmd-params-cm
  namespace: argocd
data:
  hydrator.enabled: "true"

Or install with the hydrator-enabled manifest:

kubectl apply -n argocd --server-side --force-conflicts \
  -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install-with-hydrator.yaml

An Application spec using the hydrator looks like this:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: argocd
spec:
  source:
    repoURL: https://github.com/my-org/my-app
    targetRevision: main
    path: config/base
  destination:
    server: https://kubernetes.default.svc
    namespace: my-app
  hydrator:
    enabled: true
    outputPath: config/rendered
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Note (v3.3 behavior change): Prior to v3.3, the hydrator would delete the output path before writing new rendered manifests. Starting with v3.3, it no longer auto-clears the path. If your downstream tooling (policy scanners, CI scripts) expected a clean output directory before hydration, you must handle cleanup explicitly.

Native OCI Registry Support (v3.1)

One of the headline features in v3.1 is first-class OCI registry support as an application source — not just for Helm charts (which was already possible via Helm's own OCI support), but for raw Kubernetes manifests and Kustomize bases packaged as OCI artifacts.

You can now store deployment configurations in the same registry infrastructure you use for container images.

Register an OCI repository:

# For raw OCI artifacts
argocd repo add oci://registry.example.com/my-org/my-app \
  --type oci \
  --name my-oci-repo \
  --username myuser \
  --password mytoken

Reference it in an Application:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: nginx
  namespace: argocd
spec:
  project: default
  source:
    repoURL: oci://registry-1.docker.io/bitnamicharts/nginx
    targetRevision: 15.9.0
    path: .
    helm:
      valuesObject:
        replicaCount: 2
  destination:
    name: in-cluster
    namespace: nginx

For pushing OCI artifacts with ORAS:

# Package your manifests directory as an OCI artifact
oras push registry.example.com/my-org/my-app:v1.2.3 \
  --artifact-type application/vnd.cncf.argoproj.argocd.content.v1.tar+gzip \
  ./manifests/:application/vnd.oci.image.layer.v1.tar+gzip

This eliminates the need for a separate Helm chart repository (like ChartMuseum). Unified artifact storage, unified auth, content-addressed by digest for integrity.

CLI Plugin Support (v3.1)

Following the kubectl plugin model, v3.1 introduces extensibility for the argocd CLI. Teams can now write custom commands that integrate directly into the ArgoCD CLI, enabling workflow-specific automation without maintaining separate tools.

Plugins are discovered via the system PATH, following the same convention as kubectl plugins: a binary named argocd-<plugin-name> becomes available as argocd <plugin-name>.

PreDelete Hooks (v3.3)

ArgoCD has had sync lifecycle hooks (PreSync, Sync, PostSync) for a long time. The PreDelete hook is the missing piece — it lets you run Jobs or other workloads before ArgoCD removes an application's resources.

apiVersion: batch/v1
kind: Job
metadata:
  name: cleanup-job
  annotations:
    argocd.argoproj.io/hook: PreDelete
spec:
  template:
    spec:
      containers:
        - name: cleanup
          image: alpine:3.19
          command:
            - sh
            - -c
            - |
              echo "Draining connections..."
              # Your cleanup logic here
      restartPolicy: Never

When the Application is deleted, ArgoCD will create and run this Job, wait for it to complete successfully, and only then proceed with resource deletion. No more workarounds with external scripts or manual intervention.

CRD-Level RBAC for AppProjects (v3.3)

Before v3.3, clusterResourceWhitelist in AppProjects could restrict by API group and Kind, but if you allowed CustomResourceDefinitions, you allowed access to all CRDs. v3.3 introduces the ability to restrict CRD access at a per-CRD level within AppProject policies.

ApplicationSet Status Visibility (v3.2)

ApplicationSet status transitions were previously silent — changes would happen with no clear indication of what changed or why. v3.2 adds structured debug messages covering status transitions (Pending → Generating → Successful → Failed), step transitions for multi-step generation, and explicit error descriptions. This makes troubleshooting ApplicationSet behavior significantly easier.

v3.2 also adds pprof support for ApplicationSet performance profiling in production environments.


Upgrade Pre-Flight Checklist

ArgoCD v3.1 ships with an argocd upgrade command that validates your configuration against known v3 breaking changes before you touch the installation:

argocd upgrade --insecure --server localhost:8080

Example output:

Performing checks for upgrade from 2.14.6 to 3.1.0...

Check: Fine-Grained RBAC for application update and delete sub-resources
  The default behavior of fine-grained policies have changed...
  Result: WARN — v3 behavior will apply to your RBAC policies

Check: Legacy argocd-cm repository configuration
  Found: 2 repositories defined in argocd-cm
  Result: FAIL — migrate to Secret-based repository config before upgrading

Run this against a copy of your current config before touching your production cluster. Address every FAIL and review every WARN.

Manual checklist before upgrading from v2.14 to any v3.x release:

# 1. Check for legacy repo config in argocd-cm
kubectl get cm argocd-cm -n argocd \
  -o=jsonpath="{.data.repositories}"

# 2. Check for log RBAC flag (remove after upgrading)
kubectl get cm argocd-cm -n argocd \
  -o=jsonpath="{.data.server\.rbac\.log\.enforce\.enable}"

# 3. Review your RBAC policies for broad update/delete grants
kubectl get cm argocd-rbac-cm -n argocd -o yaml

# 4. Check for ApplicationSets using matrix/merge generators
kubectl get applicationset -A -o yaml | grep -A 5 "matrix\|merge"

# 5. Check if you rely on Dex sub-claim in RBAC (user-based, not group-based policies)
kubectl get cm argocd-rbac-cm -n argocd \
  -o=jsonpath="{.data.policy\.csv}" | grep -v "^p.*role:"

How to Upgrade

ArgoCD follows a strict minor-version-at-a-time upgrade path. You cannot skip from 2.12 directly to 3.0. Read the upgrade notes for each minor version in sequence.

The install command for v3.x uses --server-side apply because some CRDs now exceed the client-side apply size limit:

# Latest stable (check GitHub releases for current version)
kubectl apply -n argocd \
  --server-side \
  --force-conflicts \
  -f https://raw.githubusercontent.com/argoproj/argo-cd/v3.3.6/manifests/install.yaml

For HA setups:

kubectl apply -n argocd \
  --server-side \
  --force-conflicts \
  -f https://raw.githubusercontent.com/argoproj/argo-cd/v3.3.6/manifests/ha/install.yaml

If you manage ArgoCD via its own Helm chart, update the chart version and review the values.yaml diff carefully — several previously optional configuration keys have changed or been removed.


Security Posture Improvements

v3 treats security as a first-class concern, not an afterthought. Three changes stand out:

Opinionated secrets guidance. The project now officially recommends using a secrets operator (External Secrets Operator, Sealed Secrets, Vault Secrets Operator) and explicitly discourages using Config Management Plugins to inject secrets. The documentation has been updated with concrete recommendations and anti-patterns to avoid.

Image signing with Cosign. All ArgoCD container images from v3.x are signed by Cosign with provenance meeting SLSA Level 3 specifications. Verify before deploying:

cosign verify \
  --certificate-identity-regexp="https://github.com/argoproj/argo-cd/.*" \
  --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
  quay.io/argoproj/argocd:v3.3.6

Project API sanitization (v3.1). The project API response was sanitized to remove sensitive information that was previously exposed, addressing CVE GHSA-786q-9hcg-v9ff.


What to Watch in v3.4

v3.4 is currently in RC. Based on open PRs and community discussions, expect continued Source Hydrator improvements, additional ApplicationSet generator capabilities, and further RBAC refinements. Monitor the GitHub releases page for the GA announcement.


Key Takeaways

The v2 to v3 jump is less disruptive than a typical major version bump — the maintainers held true to their goal of making it "almost as easy as upgrading between 2.x minor versions." But almost is doing real work in that sentence.

The three things that will catch teams unprepared are the argocd-cm repository config removal, the RBAC inheritance change, and the log RBAC enforcement. Run the pre-flight checklist, use the argocd upgrade validation command if you are on v3.1+, and migrate one minor version at a time.

The v3.x feature additions — Source Hydrator, native OCI support, CLI plugins, PreDelete hooks — are genuinely useful and worth adopting progressively once you are running on v3.

Useful links:

Ready to test your Argocd knowledge? Try our Argocd quiz!