# Migrate with Diagrid-native tooling

With Diagrid-native tooling you create your Diagrid resources yourself — using the UI, CLI, or Terraform — then wire each workload to Diagrid. This requires a little more work than the Diagrid Kubernetes Operator but gives you more control over how you interact with and automate your project setup.

## 1. Create your Diagrid resources

Diagrid resources can be created imperatively using the CLI or web console, or declaratively using Terraform or YAML manifests. Pick the tooling that best fits how you manage infrastructure today.

**UI**

1. Visit the Diagrid Web Console at [catalyst.diagrid.io](https://catalyst.diagrid.io) and create a new project.
2. Create an app for each Dapr-enabled application. If you want to use local location, you must enable the `external` flag on the app.
3. Recreate each Dapr component using the components catalog and scope it to the relevant apps.
4. Recreate any other pub/sub subscriptions, configurations, and resiliency policies in the console as well.
5. If your app receives pub/sub messages, bindings, or actor invocations, configure the App Connection for each app so Diagrid can deliver them.

**CLI**

The fastest path is [`diagrid apply`](https://docs.diagrid.io/operate/project-operations/declarative-management), which mirrors `kubectl apply` and accepts a directory of YAML resources. Authenticate and create the target project once:

```bash
diagrid login
diagrid project create my-project --wait --use
```

Apply your existing Dapr OSS Component, Subscription, Configuration, and Resiliency manifests:

```bash
diagrid apply --project my-project -f ./dapr-resources/
```

You still need an App ID per workload, since Dapr OSS identifies apps via the `dapr.io/app-id` annotation rather than a CRD. Add them declaratively alongside your Dapr CRDs:

```yaml
# ./dapr-resources/orders-appid.yaml
apiVersion: cra.diagrid.io/v1beta1
kind: AppIdentity
metadata:
  name: orders
spec:
  protocol: http
  appEndpoint:
    url: https://orders.example.com # where Diagrid calls your app (optional)
  external: true # only if using local location
```

`diagrid apply -f` does not descend into subdirectories, so keep these manifests beside the rest or pass the subdirectory as its own `-f`. A file it does not read is not reported.

Or create them imperatively:

```bash
diagrid app create orders --project my-project \
  --endpoint https://orders.example.com \
  --endpoint-token $APP_TOKEN \
  --endpoint-protocol http
```

`diagrid app create` sets the endpoint, the token that secures it, and the protocol. `external` has no flag on this command, so set it in the declarative form above. In a public region, an App ID with `external: true` cannot run workflows or use the managed KV store and pub/sub — see [Dapr Server location](https://docs.diagrid.io/dapr-open-source/migrate-to-diagrid#dapr-server-location-remote-or-local).

Plaintext secrets in component metadata are automatically extracted into a managed secret store on apply — see [Managing secrets](https://docs.diagrid.io/operate/project-operations/secrets).

See the full [CLI reference](https://docs.diagrid.io/references/diagrid/diagrid-cli-intro) for every command and flag.

**Terraform**

Use the [Diagrid Terraform provider](https://registry.terraform.io/providers/diagridio/catalyst/latest/docs) to model Diagrid resources alongside the rest of your infrastructure. Authenticate by setting `CATALYST_API_KEY` (or passing `api_key` to the provider block).

```hcl
terraform {
  required_providers {
    catalyst = {
      source = "diagridio/catalyst"
    }
  }
}

provider "catalyst" {
  api_key = var.catalyst_api_key
}

resource "catalyst_project" "my_project" {
  name   = "my-project"
  region = "region1"
}

resource "catalyst_appid" "orders" {
  project_id = catalyst_project.my_project.name
  name       = "orders"
  protocol   = "http"

  app_endpoint = {
    url = "https://orders.example.com"
  }

  external = true # only if using local location
}

resource "catalyst_component" "orderstate" {
  project_name = catalyst_project.my_project.name
  name         = "orderstate"
  spec = {
    type    = "state.redis"
    version = "v1"
    metadata = [
      { name = "redisHost", value = "redis:6379" },
      {
        name = "redisPassword"
        secret_key_ref = {
          name = "redis-secret"
          key  = "password"
        }
      },
    ]
  }
  scopes = [catalyst_appid.orders.name]
}
```

## Connecting Diagrid to your app endpoints

Some Dapr APIs require the Dapr server to be able to call endpoints on your application, such as service invocation, actor invocations, input bindings or non-streaming pub/sub subscriptions.
When using the Diagrid-native tooling, you will need to manually connect Diagrid to your workload's app endpoints.

For **local** location (external apps), you will be able to set the `appEndpoint` field on the app to point to the local address you app listens on e.g. `http://localhost:8080`.

For **remote** location, you need to expose your app to the Diagrid network and set the `appEndpoint` to that address.

If it is not possible to establish a direct connection, you may want to consider using the [Diagrid Kubernetes Operator](https://docs.diagrid.io/dapr-open-source/migrate-to-diagrid/kubernetes-operator) which automates establishing a secure tunnel between Diagrid and your app without requiring you to expose the app directly to the Diagrid network.
A similiar technique can be achieved manually by installing the Diagrid CLI in your app container and setting the entry point command to:

```bash
diagrid dev run \
  --project ${PROJECT_NAME} \
  --id ${APP_ID} \
  --app-port ${APP_PORT} \
  --skip-managed-kv \ # omit if you want to use Diagrid managed kvstore
  --skip-managed-pubsub \ # omit if you want to use Diagrid managed pubsub
  --approve -- <your app command>
```

Ensuring that you set the env vars `DIAGRID_API_KEY`, `DAPR_API_TOKEN`, `PROJECT_NAME`, `APP_ID`, and `APP_PORT` appropriately.

## 2. Connect your workloads

### Remote: Diagrid manages the Dapr server

Update each Deployment:

1. Remove the `dapr.io/*` annotations and the Dapr OSS sidecar from the workload spec (deployment, statefulset, etc.).
2. Fetch the Diagrid Dapr HTTP/gRPC endpoints and API token for the corresponding app.

```bash
# Get the project endpoints
diagrid project get my-project -a

# Get the appid token
diagrid app get my-app --project my-project
```

Alternatively, you can find these in the web console, or output them from your Terraform resources.

3. Inject the Diagrid endpoints and token as environment variables into your workload spec, typically via a Kubernetes Secret:

   | Env var | Value |
   | --- | --- |
   | `DAPR_HTTP_ENDPOINT` | Project HTTP endpoint |
   | `DAPR_GRPC_ENDPOINT` | Project gRPC endpoint |
   | `DAPR_API_TOKEN` | App API token |

4. Roll the workload out. The app now calls Diagrid over HTTP/gRPC; no sidecar runs in the pod.

### Local: the Diagrid Dapr server runs as a app sidecar in your cluster

If you need to run Dapr in the app pod but don't want to use the Diagrid Kubernetes Operator, you can set it up manually:

1. Create an app with the `external` flag enabled and wait for it to become ready.
2. Grab the **bootstrap config** from the app status and store it somewhere your pod can mount it (a `ConfigMap` or `Secret`).
3. Create or reuse an existing **certificate issuer CA** (`issuer.pem`, `issuer.key` and `ca.pem`) that can be used to establish trust for your app workload identities.
4. Mount three things into the app sidecar container: the bootstrap config, the issuer cert and key, and the trust anchor bundle.
5. Set these environment variables on the sidecar container:
   - `BOOTSTRAP_CONFIG_PATH` — path to the bootstrap config inside the container.
   - `WORKLOAD_IDENTITY_ISSUER_CERT` — path to the issuer certificate.
   - `WORKLOAD_IDENTITY_ISSUER_KEY` — path to the issuer private key.
   - `WORKLOAD_IDENTITY_TRUST_ANCHORS` — path to the trust anchor bundle.

The sidecar uses these to establish workload identity with Diagrid and pull down its configuration. Your app keeps talking to `localhost` on the standard Dapr ports.

**Diagram: Local location: how the sidecar establishes its identity.** The mounted bootstrap config and issuer credentials let the sidecar get one identity from the public Sentry for the control plane and one from the private Sentry for the data plane.

- **Kubernetes pod**: App, Dapr server, Bootstrap config (Issuer cert Issuer key Trust anchors)
- **Control plane**: Public Sentry
- **Data plane**: Private Sentry

- App → Dapr server (localhost)
- Bootstrap config (Issuer cert Issuer key Trust anchors) → Dapr server (mounted into)
- Dapr server → Public Sentry (control plane identity)
- Dapr server → Private Sentry (data plane identity)

## Next steps

Once your workloads are running on Diagrid, [cut over your traffic and decommission Dapr OSS](https://docs.diagrid.io/dapr-open-source/migrate-to-diagrid/cut-over).
