Upgrade Dapr on a Kubernetes cluster

Follow these steps to upgrade Dapr on Kubernetes and ensure a smooth upgrade.

You can upgrade the Dapr control plane on a Kubernetes cluster using either the Dapr CLI or Helm.

Upgrade using the Dapr CLI

You can upgrade Dapr using the Dapr CLI.

Prerequisites

Upgrade existing cluster to 1.19.0

dapr upgrade -k --runtime-version=1.19.0

You can provide all the available Helm chart configurations using the Dapr CLI.

Troubleshoot upgrading via the CLI

There is a known issue running upgrades on clusters that may have previously had a version prior to 1.0.0-rc.2 installed on a cluster.

While this issue is uncommon, a few upgrade path edge cases may leave an incompatible CustomResourceDefinition installed on your cluster. If this is your scenario, you may see an error message like the following:

❌  Failed to upgrade Dapr: Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
The CustomResourceDefinition "configurations.dapr.io" is invalid: spec.preserveUnknownFields: Invalid value: true: must be false in order to use defaults in the schema

Solution

  1. Run the following command to upgrade the CustomResourceDefinition to a compatible version:

    kubectl replace -f https://raw.githubusercontent.com/dapr/dapr/release-1.19/charts/dapr/crds/configuration.yaml
    
  2. Proceed with the dapr upgrade --runtime-version 1.19.0 -k command.

Upgrade using Helm

You can upgrade Dapr using a Helm v3 chart.

❗Important: The latest Dapr Helm chart no longer supports Helm v2. Migrate from Helm v2 to Helm v3.

Prerequisites

Upgrade existing cluster to 1.19.0

As of version 1.0.0 onwards, existing certificate values will automatically be reused when upgrading Dapr using Helm.

Note Helm does not handle upgrading resources, so you need to perform that manually. Resources are backward-compatible and should only be installed forward.

  1. Upgrade Dapr to version 1.19.0:

    kubectl replace -f https://raw.githubusercontent.com/dapr/dapr/v1.19.0/charts/dapr/crds/components.yaml
    kubectl replace -f https://raw.githubusercontent.com/dapr/dapr/v1.19.0/charts/dapr/crds/configuration.yaml
    kubectl replace -f https://raw.githubusercontent.com/dapr/dapr/v1.19.0/charts/dapr/crds/subscription.yaml
    kubectl apply -f https://raw.githubusercontent.com/dapr/dapr/v1.19.0/charts/dapr/crds/resiliency.yaml
    kubectl apply -f https://raw.githubusercontent.com/dapr/dapr/v1.19.0/charts/dapr/crds/httpendpoints.yaml
    kubectl apply -f https://raw.githubusercontent.com/dapr/dapr/v1.19.0/charts/dapr/crds/mcpservers.yaml
    kubectl apply -f https://raw.githubusercontent.com/dapr/dapr/v1.19.0/charts/dapr/crds/workflowaccesspolicy.yaml
    
    helm repo update
    
    helm upgrade dapr dapr/dapr --version 1.19.0 --namespace dapr-system --wait
    

    If you’re using a values file, remember to add the --values option when running the upgrade command.*

  2. Ensure all pods are running:

    kubectl get pods -n dapr-system -w
    
    NAME                                     READY   STATUS    RESTARTS   AGE
    dapr-operator-5cdd6b7f9c-9sl7g           1/1     Running   0          41s
    dapr-placement-server-0                  1/1     Running   0          41s
    dapr-sentry-84565c747b-7bh8h             1/1     Running   0          35s
    dapr-sidecar-injector-68f868668f-6xnbt   1/1     Running   0          41s
    
  3. Restart your application deployments to update the Dapr runtime:

    kubectl rollout restart deploy/<DEPLOYMENT-NAME>
    

Scheduler RBAC

From Dapr 1.19, the Scheduler needs get, list and watch on pods in the Dapr control plane namespace to detect whether a Placement service is deployed. It needs this on every 1.19 upgrade, whether or not you enable scheduler placement. The Helm chart and dapr upgrade -k add it as the dapr-scheduler-pods Role and RoleBinding in the release namespace. The dapr-scheduler ClusterRole (or Role, in namespaced RBAC mode) keeps namespaces only.

Upgrade existing Dapr deployment to enable high availability mode

Enable high availability mode in an existing Dapr deployment with a few additional steps.

Enable scheduler placement in an existing Dapr deployment

Actor placement can be served by the Scheduler service instead of the standalone Placement service by upgrading with global.scheduler.placement.enabled=true. The placement StatefulSet is removed by the same upgrade, and running sidecars adopt scheduler placement on their own: no sidecar restarts are needed, in either direction. The schedulers detect the placement pods leaving and take over within a few seconds; outside of this upgrade, a placement pod that crashes or restarts does not move the placement authority. Learn more about serving placement from the Scheduler service.

Before you enable it:

  • Upgrade every sidecar to 1.19 or later: restart your applications after the control plane upgrade.
  • Keep global.scheduler.enabled=true, global.actors.enabled=true and global.actors.serviceName=placement (the defaults). The Helm chart fails to render the setting without them.
  • If you manage RBAC outside the Helm chart, add the Scheduler RBAC Role.

Enable it with helm upgrade and --set global.scheduler.placement.enabled=true plus your usual values, or with the CLI:

dapr upgrade -k --runtime-version=1.19.0 --set global.scheduler.placement.enabled=true

The same upgrade restarts the Schedulers to pick up --placement-enabled=true, so expect actor and workflow activity to pause until the Schedulers are ready again, longer than the few seconds of the takeover itself.

To verify, check that sidecars log Connected to scheduler placement leader <address> and that exactly one Scheduler reports dapr_scheduler_placement_leader as 1. If dapr_scheduler_placement_incapable_sidecars is above 0, or a Scheduler logs A sidecar running an older Dapr version is connected, upgrade those pods: their actor and workflow APIs stall.

To roll back, upgrade with the setting false: the placement StatefulSet is redeployed and the schedulers hand placement back to it, again without sidecar restarts. In both directions, the change reassigns actors once because the two services place actors with different algorithms. Actor state is unaffected.

Because helm upgrade resets values not passed on the command line, pass the setting on every subsequent upgrade (or keep it in your values file), otherwise the placement StatefulSet is redeployed and placement hands back to it.

Downgrade

Dapr 1.19 still issues the Ed25519 workload identity keys that Dapr 1.18 introduced, and Sentry persists the Ed25519-keyed CA in the dapr-trust-bundle secret. Sentry versions before 1.17.7 cannot parse this bundle and crash on startup with:

fatal: error creating CA: failed to get CA bundle:
failed to verify CA bundle: unsupported key type ed25519.PrivateKey
FromToResult
1.191.18.xSafe. 1.18 and 1.19 write the same trust bundle format.
1.191.17.7 or a later 1.17.xSafe. 1.17.7 reads the Ed25519 trust bundle (#9904).
1.191.17.6 or earlierSentry crash-loops at startup. Roll back to 1.17.7 first, confirm dapr status -k is healthy, then downgrade from there.

A downgrade to 1.18.x or 1.17.7 needs no CA rotation. Rotate the CA only if your target is below 1.17.7 or your security posture requires it.

Downgrade in this order:

  1. If you enabled scheduler placement, upgrade with global.scheduler.placement.enabled=false and wait for the Placement pods to be ready. Check that your sidecars log Connected to placement service: <address>.

  2. If you created WorkflowAccessPolicy rules that use requires, remove or narrow them. A 1.18 control plane and sidecar do not know the field and apply those rules without the gate.

  3. Roll back the control plane with Helm:

    helm history dapr -n dapr-system
    helm rollback dapr <REVISION> -n dapr-system --wait
    

    Or pin a specific version:

    helm upgrade dapr dapr/dapr --version 1.18.7 --namespace dapr-system --wait
    
  4. Restart your application deployments, so the sidecars pick up the older injector image and get new identities from the rolled-back Sentry:

    kubectl rollout restart deploy/<DEPLOYMENT-NAME>