Migrate from Dapr OSS on Kubernetes to Catalyst
Catalyst is built on Dapr OSS, so it supports the same APIs, resource types, and a selection of Dapr components*. As a result, your existing Dapr resources, such as policies and configurations, work without modification, and your application code can keep using the same Dapr SDKs.
This guide walks you through the process of migrating your workloads from Dapr OSS on Kubernetes to Catalyst.
- Components: *Catalyst supports a curated subset of Dapr OSS components — state stores, pub/sub brokers, secret stores, bindings, conversation, and so on. Check the Catalyst component specs reference and confirm that every component type you rely on is supported. Components that aren't listed won't work on Catalyst and need an alternative or an exception before you can complete the migration.
- Secret stores: Catalyst does not support
secretstores.kubernetes, the secret store most Dapr applications on Kubernetes use. See Secret stores for what to do instead — depending on how you use it, you may need no secret store at all, or you may need to provision one before you migrate. - Configuration: In Catalyst you can only configure some fields within a Dapr
Configurationresource; others are reserved for Catalyst's internal use. See Dapr configuration compatibility for the full list of editable and reserved fields.
Prerequisites
-
A Kubernetes cluster running Dapr open source.
-
A Diagrid Catalyst organization you have global
adminoreditoraccess to. You can sign up for free at Diagrid Catalyst. -
The Diagrid CLI installed and authenticated.
-
A secret store Catalyst supports — AWS Secrets Manager, Azure Key Vault or HashiCorp Vault — but only if one of your applications calls the Dapr Secrets API. See Secret stores.
-
cert-manager in the cluster — required only if you migrate with the Catalyst Kubernetes operator, whose mutating webhook needs it to issue a serving certificate. Install it before the operator:
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yamlkubectl wait --for=condition=Available -n cert-manager deploy --all --timeout=120s -
Every service your components connect to must be reachable from Catalyst — if you run the Dapr server remotely, which is the common choice. See Reachable backing services.
Reachable backing services
With a remote Dapr server, the server runs in Catalyst's region. It cannot resolve an address that only exists inside your cluster, so a component pointing at redis.demo.svc.cluster.local:6379 will not work once migrated.
This is not an edge case. If your Redis, Kafka or Postgres runs in the same cluster as your applications — which is common — every component is affected at once.
Migration tooling cannot fix this for you: the service is yours, and the change is to your infrastructure. Before you migrate, either expose each backing service on an address Catalyst can reach, or move it to one that already is. Then update the component's metadata to that address.
spec:
type: state.redis
metadata:
- name: redisHost
value: my-redis.example.com:6379 # reachable from Catalyst
Two things to watch:
- Check the credentials the new endpoint expects. A managed Redis that authenticates with a username needs
redisUsernameset as well asredisPassword; Dapr does not infer it from the connection string. - Repointing a state store does not move the data in it. If the component holds actor state you still need, migrate that data yourself.
Check before you start
Two commands answer different questions, and both are worth running:
# What would migrate, and what has to be fixed first.
# Reads the cluster only: no login, no operator, no Catalyst project needed.
diagrid k8s-operator compatibility <namespace>
# Exactly what would be copied, and under which App IDs.
# Needs the operator installed and a Catalyst project.
diagrid k8s-operator migrate <namespace> --dry-run
Run the first while you are still deciding whether to migrate at all — it reports unsupported component types, component settings Catalyst rejects, addresses it cannot reach, the Configuration fields that will not survive, and what the cluster itself still needs.
Run the second immediately before the real migration. Note what it can and cannot tell you: migrating in place, it lists every workload and resource it would change; with --new-namespace it lists what it would copy, but reports no workloads migrated, because the target namespace does not exist yet and there is nothing there for it to annotate.
Starting point: Dapr OSS on Kubernetes
A typical Dapr OSS setup looks like this:
- The Dapr control plane installed in the
dapr-systemnamespace via Helm or the Dapr CLI. - Application pods annotated with
dapr.io/enabled: "true", which get a Dapr sidecar injected by the Dapr operator. - Dapr CRDs such as Components, Configurations, Subscriptions, and Resiliency policies, stored in the Kubernetes cluster.
Two decisions shape your migration
Before you start, you need to make two decisions — one architectural, one operational:
- Where the Dapr server runs — in Catalyst (remote) or in your application Kubernetes cluster (local).
- Which tools you want to use — with Catalyst-native tooling (UI, CLI, Terraform) or with the Catalyst Kubernetes operator.
Dapr Server location: remote or local
- Remote — Catalyst's Dapr server runs in Catalyst. Your app calls the Dapr API via Catalyst's endpoint and no Dapr server runs inside your app pod.
- Local — Catalyst's Dapr server runs as a sidecar in your app pod, configured to talk to Catalyst's Dapr control plane. Your app continues to talk to the Dapr APIs over
localhostthe same as Dapr OSS.
Pick remote for zero server ops and the lowest infrastructure footprint. Pick local when the Dapr server must stay inside your network boundary or be co-located with the workload.
In a public region, a local Dapr server gets none of the Diagrid-managed services. Their credentials reach the region's own database and message broker, so Catalyst does not send them to a sidecar that runs on your compute. That means:
- Workflows and agent state are unavailable to that App ID.
- The managed KV store and managed pub/sub are not loaded into its sidecar.
- State store and pub/sub components you bring yourself still load as usual.
If the app needs workflows or the managed services in a public region, choose remote. Dedicated and self-hosted regions serve a single organization, so a local Dapr server there keeps all of them.
Local Dapr Server networking
If you choose the local location for your Dapr Server, note that it will run inside your application Kubernetes cluster and is thus subject to your cluster's networking rules and policies.
All traffic between the app, the Dapr Server sidecar, and any infrastructure is fully within your control and cannot be influenced by Catalyst. You are responsible for configuring appropriate network policies to isolate your workloads and allow access to the services and infrastructure they need.
Tooling: Catalyst-native or Kubernetes-native
You can choose the tooling that best suits your setup for migrating and managing your workloads on Catalyst. For existing Dapr apps running on Kubernetes, the Catalyst Kubernetes Operator provides a simpler migration path. Once you've migrated your workloads, you can switch to using the native Catalyst tooling if you prefer.
Select from one of the options below to see how you can use that tooling to migrate your workloads.
| Path | Best when | What it does |
|---|---|---|
| Catalyst-native | Catalyst is your source of truth and you drive infra from the UI, CLI, or Terraform | You create Catalyst resources yourself, then wire each workload to Catalyst |
| Kubernetes-native | Your platform team already manages Dapr declaratively in-cluster | The Catalyst Kubernetes operator reconciles Dapr's K8s native resources into Catalyst |
Secret stores
Catalyst supports three secret store types: aws.secretsmanager, azure.keyvault and hashicorp.vault. It does not support secretstores.kubernetes, the default secret store for a Dapr application on Kubernetes. Applying a component of that type is rejected:
invalid value of 'secretstores.kubernetes' provided for 'spec.type' field:
please use a supported type. See our docs @ https://docs.diagrid.io.
What to do about it depends on how your applications use the store, and the two cases are independent. Check both.
Components that reference a secret
A component whose metadata uses secretKeyRef needs no secret store on Catalyst. Supply the value as plaintext instead:
# Dapr OSS
- name: redisPassword
secretKeyRef:
name: redis-secret
key: password
# Catalyst
- name: redisPassword
value: "<password>"
Catalyst identifies sensitive fields from the Dapr component metadata schema and extracts them into a Catalyst-managed secret store before the resource is persisted, so the plaintext value is never stored in the control plane. See Managing secrets.
This is a change of secrets model rather than a component swap: the value moves out of your cluster and into Catalyst.
That store resolves component configuration. It is not a store your application can read through the Secrets API, so extraction does not cover the case below.
Applications that call the Secrets API
An application that calls GetSecret or GetBulkSecret needs a real secret store, so secretstores.kubernetes has to be replaced with one of the three supported types. Provision it before you migrate, because the application fails at runtime without it.
Two things to plan for:
- Move the secret data. The contents of your Kubernetes
Secretobjects have to exist in the new store before the migrated application reads them. - Keep the component name. Your application names the store in every call, so reusing the original component name — only changing
spec.typeand its metadata — means the application code does not change.
Dapr configuration compatibility
Catalyst supports the Dapr Configuration resource, but some fields are reserved for Catalyst's internal use and cannot be changed. Behavior can also differ between public (multi-tenant SaaS) regions and self-hosted (private) regions. Each field falls into one of these categories per region:
- 🔒 Locked — Catalyst manages this field. Any value you supply is ignored or overwritten.
- ✏️ Editable — You can set this field, subject to the validation noted.
| Field | Public regions | Self-hosted regions | Notes |
|---|---|---|---|
accessControl.trustDomain | 🔒 Locked | 🔒 Locked | Reserved by Catalyst; set automatically to your organization's SPIFFE trust domain. |
accessControl.policies[].trustDomain | 🔒 Locked | 🔒 Locked | Forced to your organization's trust domain. |
accessControl.policies[].namespace | 🔒 Locked | 🔒 Locked | Forced to your project namespace (prj-<namespace>). |
features | 🔒 Locked | 🔒 Locked | Preview feature toggles are managed by Catalyst. |
api.allowed | 🔒 Locked | 🔒 Locked | Restricted to the Catalyst platform API allow-list. |
metrics | 🔒 Locked | 🔒 Locked | Catalyst applies its own metric cardinality rules. |
tracing (otel / zipkin) | ✏️ Editable | ✏️ Editable | Requires otel.isSecure. Custom/internal endpoints are rejected in public regions. |
workflow.maxConcurrentWorkflowInvocations, workflow.maxConcurrentActivityInvocations | 🔒 Locked | ✏️ Editable | Per-sidecar concurrency limits. Discarded in public regions. In self-hosted regions you can lower them, but not past Catalyst's ceilings of 5000 workflows and 10000 activities, which also apply when you omit them. |
workflow.workflowConcurrencyLimits, workflow.activityConcurrencyLimits | ✏️ Editable | ✏️ Editable | Per-name concurrency limits, enforced across every replica. The way to bound concurrency in a public region. |
workflow.globalMaxConcurrentWorkflowInvocations, workflow.globalMaxConcurrentActivityInvocations | 🔒 Locked | 🔒 Locked | Accepted and stored, but not passed to the workflow engine, so setting either changes nothing in any region today. |
workflow.stateRetentionPolicy (anyTerminal, completed, failed, terminated) | ✏️ Editable | ✏️ Editable | Per-terminal-state retention durations (Go duration strings). Added in Dapr 1.17. |
httpPipeline / appHttpPipeline handlers (user middleware) | ✏️ Editable | ✏️ Editable | User handlers are appended after Catalyst's reserved base handler, which is always present and cannot be removed. |
accessControl.defaultAction + accessControl.policies[] (appId, defaultAction, operations with name / httpVerb / action) | ✏️ Editable | ✏️ Editable | Validated as allow/deny rules. |
Next steps
- Migrate your workloads with the Catalyst Kubernetes operator or Catalyst-native tooling.
- Cut over and decommission Dapr OSS — move traffic onto Catalyst and remove the Dapr OSS control plane.