# D3E installation guide

Diagrid Dapr Distribution for Enterprise (D3E) provides security-enhanced Dapr binaries with enterprise features for multi-tenancy, namespace isolation, and reduced permission sets. Choose the installation option that best fits your security and operational requirements.

## Choose your installation path

- [1. Single namespace isolation](#option-1-single-namespace) — Dapr control plane scoped to one namespace. Simplest setup for single-team deployments.
- [2. Multi-namespace isolation](#option-2-multi-namespace) — Dapr control plane scoped to multiple namespaces. Ideal for multi-team environments with shared infrastructure.
- [3. Multiple Dapr Installations](#option-3-multiple-installations) — Deploy multiple isolated Dapr control planes in one cluster. Best for complete tenant separation.
- [4. CRD-Free Installation](#option-4-crd-free) — Install without CRDs/ClusterRoles using the Diagrid Dapr Injector Helm chart. Maximum security, higher operational overhead.

---

## Prerequisites

Before installing D3E, ensure you have:

- Administrator access on a Kubernetes cluster
- Helm 3.x or later
- Cluster outbound network access to Diagrid's artifact repository
- D3E license JWT (provided by Diagrid)

:::tip Need Access?
Don't have a D3E license JWT yet? [Contact Diagrid Sales](https://www.diagrid.io/contact) to get started.
:::

---

## Access details

D3E is packaged as a Helm chart and comes with custom container images for the Dapr control plane and sidecars. All deployment resources are published on Diagrid's public artifact repository.

### Helm chart

- **Public gallery**: [gallery.ecr.aws/diagrid](https://gallery.ecr.aws/diagrid)
- **Chart URL**: `public.ecr.aws/diagrid/d3e-charts/d3e-dapr`

### Container images

The following images are public and can be pulled without an image-pull secret:

- [public.ecr.aws/diagrid/d3e/daprd](https://gallery.ecr.aws/diagrid/d3e/daprd)
- [public.ecr.aws/diagrid/d3e/operator](https://gallery.ecr.aws/diagrid/d3e/operator)
- [public.ecr.aws/diagrid/d3e/placement](https://gallery.ecr.aws/diagrid/d3e/placement)
- [public.ecr.aws/diagrid/d3e/scheduler](https://gallery.ecr.aws/diagrid/d3e/scheduler)
- [public.ecr.aws/diagrid/d3e/injector](https://gallery.ecr.aws/diagrid/d3e/injector)
- [public.ecr.aws/diagrid/d3e/sentry](https://gallery.ecr.aws/diagrid/d3e/sentry)

### D3E license JWT

A signed D3E license JWT (`diagrid.token`) is required for D3E installation. This license key is issued by Diagrid when your contract is activated and should be stored securely (e.g., in a secrets manager).

The license JWT encodes your customer name, a unique license ID, and an expiration date. All D3E control plane components validate the license signature at startup using an embedded public key — no network call is required.

**License lifecycle:**

- **Active**: Components start normally without warnings.
- **Expiring soon**: Within 6 months of token expiry, a warning is printed at startup. It is recommended at this time to begin your contract renewal process with Diagrid.
- **Grace period**: Between 0 - 90 days after contract end, components continue running but print a prominent warning on startup. Contact your support representative immediately to retrieve your new key once Diagrid contract renewal has completed.
- **Expired + grace period elapsed**: 90+ days after expiry components refuse to start. A new license is required.

To renew your license, contact [support@diagrid.io](mailto:support@diagrid.io) or your Diagrid account team.

---

## Installation Setup

Follow these steps to prepare your cluster for D3E installation:

1. Remove an existing Dapr installation from your cluster to avoid conflicts:

   ```
   helm uninstall dapr -n dapr-system
   kubectl delete pvc -n dapr-system --all
   kubectl delete crds subscriptions.dapr.io resiliencies.dapr.io \
   configurations.dapr.io components.dapr.io httpendpoints.dapr.io
   ```

2. Set your D3E license JWT in your terminal:

   ```
   export D3E_LICENSE=<LICENSE-JWT-PROVIDED-BY-DIAGRID>
   ```

3. Choose your installation option below based on your security and operational requirements.

---

### Option 1: Single namespace isolation

<details>
  <summary><strong>Option 1: Single namespace isolation</strong></summary>

  **Best for:** Single-team deployments where all Dapr applications run in one namespace.

  In this configuration, the Dapr control plane is scoped to a single namespace. Only applications deployed within that namespace can be injected with Dapr sidecars.

  ### Configuration

  | Helm value               | Description                | Default | Options         |
  | ------------------------ | -------------------------- | ------- | --------------- |
  | `global.rbac.namespaced` | Enable namespace isolation | `false` | `true`, `false` |

  ### Installation

  The following command installs D3E version 1.17.0-d3e.1 into the `dapr-system` namespace, allowing only applications in `dapr-system` to use Dapr:

  ```bash
  helm install \
    --create-namespace \
    -n dapr-system dapr \
    --set global.rbac.namespaced=true \
    --set diagrid.token=${token} \
    --set global.tag=1.17.0-d3e.1 \
    oci://public.ecr.aws/diagrid/d3e-charts/d3e-dapr --version 1.17.0-d3e.1
  ```

  ![D3E single namespace isolation](https://docs.diagrid.io/img/enterprise-dapr/d3e-install-option-1.png)
</details>

---

### Option 2: Multi-namespace isolation

<details>
  <summary><strong>Option 2: Multi-namespace isolation</strong></summary>

  **Best for:** Multi-team environments where teams share Kubernetes infrastructure and some namespaces but need isolated Dapr access.

  The Dapr control plane is scoped to a list of namespaces. Only applications in those namespaces can be injected with Dapr sidecars.

  ### Configuration

  | Helm value               | Description                             | Default | Options            |
  | ------------------------ | --------------------------------------- | ------- | ------------------ |
  | `global.rbac.namespaced` | Enable namespace isolation              | `false` | `true`, `false`    |
  | `global.rbac.namespaces` | List of namespaces Dapr can inject into | `[]`    | `["ns-1", "ns-2"]` |

  ### Installation

  The following command installs D3E version 1.17.0-d3e.1 into the `dapr-system` namespace and allows applications in the `crud-app` namespace to use Dapr:

  ```bash
  helm install \
    --create-namespace \
    -n dapr-system \
    --set global.rbac.namespaced=true \
    --set-json 'global.rbac.namespaces=["crud-app"]' \
    --set diagrid.token=${token} \
    --set global.tag=1.17.0-d3e.1 \
    dapr oci://public.ecr.aws/diagrid/d3e-charts/d3e-dapr --version 1.17.0-d3e.1
  ```

  :::tip Add More Namespaces
  To allow more namespaces, extend the JSON array: `'global.rbac.namespaces=["crud-app", "order-app", "payment-app"]'`
  :::

  ![D3E multi-namespace isolation](https://docs.diagrid.io/img/enterprise-dapr/d3e-install-option-2a.png)
</details>

---

### Option 3: Multiple Dapr installations

<details>
  <summary><strong>Option 3: Multiple Dapr installations</strong></summary>

  **Best for:** Complete tenant separation where teams need fully isolated Dapr control planes.

  Deploy multiple Dapr control planes in the same cluster. Each installation is isolated to its own namespace(s), providing complete separation between apps.

  ### Installation

  The following commands install D3E version 1.17.0-d3e.1 twice in the same cluster:

  **First installation:** `dapr-system` control plane namespace serving application namespace `crud-app`:

  ```bash
  helm install \
    --create-namespace \
    -n dapr-system \
    --set global.rbac.namespaced=true \
    --set-json 'global.rbac.namespaces=["crud-app"]' \
    --set diagrid.token=${token} \
    --set global.tag=1.17.0-d3e.1 \
    dapr oci://public.ecr.aws/diagrid/d3e-charts/d3e-dapr --version 1.17.0-d3e.1
  ```

  **Second installation:** `dapr-secondary` control plane namespace serving application namespace `order-app`:

  ```bash
  helm install \
    --create-namespace \
    -n dapr-secondary \
    --set global.rbac.namespaced=true \
    --set-json 'global.rbac.namespaces=["order-app"]' \
    --set diagrid.token=${token} \
    --set global.tag=1.17.0-d3e.1 \
    dapr-secondary oci://public.ecr.aws/diagrid/d3e-charts/d3e-dapr --version 1.17.0-d3e.1
  ```

  ![D3E multi-tenancy](https://docs.diagrid.io/img/enterprise-dapr/d3e-install-option-2b.png)
</details>

---

### Option 4: ClusterRole & CRD free installation

<details>
  <summary><strong>Option 4: ClusterRole & CRD free installation</strong></summary>

  **Best for:** Environments with strict security requirements that prohibit CRDs and ClusterRoles.

  This configuration eliminates CustomResourceDefinitions (CRDs) and Kubernetes ClusterRoles by deploying Dapr in standalone mode without the standard sidecar injector control plane service. Instead, the [Diagrid Dapr Injector Helm library](https://github.com/diagridio/diagrid-dapr-injector-helm-sample) is used as a dependency on your applications to inject sidecars directly into application manifests.

  :::warning High Operational Overhead
  This option requires significant operational overhead. You must configure Dapr sidecars in your application manifests and manage Dapr resources within your application Helm charts. **Only use this option if you have strict requirements against CRDs and ClusterRoles.**
  :::

  ### Configuration

  All standard Helm values from Options 1-3 are supported, plus:

  | Helm value                      | Description                      | Default | Options         |
  | ------------------------------- | -------------------------------- | ------- | --------------- |
  | `dapr_sidecar_injector.enabled` | Deploy standard sidecar injector | `true`  | `true`, `false` |
  | `global.rbac.injector.enabled`  | Deploy injector ClusterRole      | `true`  | `true`, `false` |
  | `global.rbac.crds.enabled`      | Enable CRDs RBAC                 | `true`  | `true`, `false` |
  | `global.rbac.operator.enabled`  | Enable operator RBAC             | `true`  | `true`, `false` |

  ### Installation

  The following installation command installs D3E version 1.17.0-d3e.1 without CRDs or ClusterRoles:

  ```bash
  helm install \
    --skip-crds \
    -n <namespace> \
    --set global.tag=1.17.0-d3e.1 \
    --set global.actors.enabled=false \
    --set global.scheduler.enabled=false \
    --set global.rbac.injector.enabled=false \
    --set global.rbac.createTokenReviewerRole=false \
    --set global.rbac.createTokenReviewerRoleBinding=false \
    --set global.rbac.crds.enabled=false \
    --set global.rbac.operator.enabled=false \
    --set global.rbac.namespaced=true \
    --set global.rbac.sentry.serviceAccount.create=true \
    --set-json 'global.rbac.namespaces=["<namespace_A>","<namespace_B>"]' \
    --set dapr_operator.enabled=false \
    --set dapr_sidecar_injector.enabled=false \
    --set dapr_placement.mode=standalone \
    --set dapr_scheduler.mode=standalone \
    --set dapr_sentry.mode=standalone \
    --set dapr_sentry.injectDaprSystemConfig=true \
    --set dapr_config.dapr_config_chart_included=false \
    --set diagrid.token="${token}" \
    dapr oci://public.ecr.aws/diagrid/d3e-charts/d3e-dapr --version 1.17.0-d3e.1
  ```

  ### Configure your applications

  After installing D3E, configure your applications to use the Diagrid Dapr Injector:

  1. Add the  [Diagrid Dapr Injector Helm library chart](https://github.com/diagridio/diagrid-dapr-injector-helm-sample)  as a dependency to your application's Helm chart.

  2. Test the behavior using the  [sample application](https://github.com/diagridio/diagrid-dapr-injector-helm-sample/blob/main/SAMPLE.md)  to understand the changes that need to be made.

  3. Configure all Dapr-enabled applications with the injector Helm settings to ensure sidecars are injected.

  ![D3E CRD-free installation](https://docs.diagrid.io/img/enterprise-dapr/d3e-install-option-4.png)
</details>

---

## RSA certificate support for sentry CA

D3E Sentry service supports using RSA keys for CA certificate generation, in addition to the [default open-source support](https://docs.dapr.io/concepts/security-concept/#secure-dapr-to-dapr-communication) of ECDSA P-256. This enables deployments in environments with RSA-only PKI requirements.

### Configuration

| Helm value | Description | Default | Options |
|------------|-------------|---------|---------|
| `dapr_sentry.caKeyAlgorithm` | CA key algorithm for self-signed certificates | `ECDSA-P256` | `ECDSA-P256`, `RSA-2048`, `RSA-4096` |

### Self-signed Certificates with RSA

To generate a self-signed RSA CA, set the `--ca-key-algorithm` flag on sentry via Helm:

```bash
helm install \
  ... \
  --set-json 'dapr_sentry.extraArgs=["--ca-key-algorithm=RSA-4096"]' \
  ...
```

On initial startup, the Sentry service generates an RSA-4096 certificate key pair and stores it as a Kubernetes Secret. It then signs all subsequent workload certificates with this CA. For more information, read [Secure Dapr to Dapr communication](https://docs.dapr.io/concepts/security-concept/#secure-dapr-to-dapr-communication).

### Bring Your Own Certificates with RSA

If you provide your own CA certificate chain via `dapr_sentry.tls`, the Sentry service automatically detects the key type (RSA or ECDSA) and selects the correct signature algorithm. No additional configuration is needed and the functionality is exactly the same as open source Dapr. For more information read [Dapr Sentry Service](https://docs.dapr.io/concepts/dapr-services/sentry/#kubernetes-mode).

Bring your own certificate with RSA supports:

- Single root CA with RSA key
- Multi-intermediate chains (root → intermediate1 → intermediate2)
- Cross-algorithm signing: an RSA CA can sign ECDSA workload CSRs and vice versa

### Backward compatibility

The default key algorithm remains **ECDSA P-256**. Existing deployments are unaffected. Changing the algorithm on an existing deployment requires rotating the CA — workload certificates are re-issued automatically via mTLS renewal.

---

## File-based log output

D3E supports writing logs to a file instead of stdout. This applies to **all installation options** and is useful in environments where stdout or node-level log collection (`/var/log`) are not accessible.

### Configuration

| Helm value | Description | Default |
|------------|-------------|---------|
| `dapr_sentry.logFilePath` | Write sentry logs to a file | `""` (stdout) |
| `dapr_sentry.logOutputs` | Comma-separated log destinations: `stdout`, `stderr`, or file paths. Merged with `logFilePath` | `""` |
| `dapr_sentry.logTimestampFormat` | Log timestamp as a Go time layout | `""` (RFC3339 with nanoseconds) |
| `dapr_sentry.logFileMaxSize` | Megabytes before the log file rotates | `""` (no rotation) |
| `dapr_sentry.logFileMaxBackups` | Rotated files to keep | `""` (keep all) |
| `dapr_sentry.logFileMaxAge` | Days to retain rotated files | `""` (keep all) |
| `dapr_sentry.logFileCompression` | `none` or `gzip` for rotated files | `""` (none) |

For sidecars, use the `dapr.io/log-file` pod annotation to set the log file path.

### Sentry

Add the `logFilePath` Helm value along with a writable volume mount:

```bash
helm install \
  ... \
  --set dapr_sentry.logFilePath="/var/log/dapr/sentry.log" \
  --set-json 'dapr_sentry.extraVolumeMounts=[{"name":"log-volume","mountPath":"/var/log/dapr"}]' \
  --set-json 'dapr_sentry.extraVolumes=[{"name":"log-volume","emptyDir":{}}]' \
  ...
```

### Sidecars

Add the `dapr.io/log-file` annotation to your application pod spec. A writable volume must be mounted at the target directory.

**With the standard sidecar injector (Options 1-3):**

```yaml
annotations:
  dapr.io/enabled: "true"
  dapr.io/log-file: "/var/log/dapr/daprd.log"
```

**With the Diagrid Dapr Injector (Option 4):**

```yaml
annotations:
  dapr.io/log-file: "/var/log/dapr/daprd.log"
  dapr.io/volume-mounts-rw: "log-volume:/var/log/dapr"
```

:::note
The log file path must point to a writable mounted volume. Sentry and daprd run with `readOnlyRootFilesystem: true`, so paths on the root filesystem will fail.
:::

### Log destinations, rotation, and timestamp format

Available from **v1.17.13-d3e.4** and **v1.18.3-d3e.4**. Logs can be written to multiple destinations at once (for example the console *and* a file), log files rotate, and the timestamp format is configurable. All of it is opt-in: with none of these set, logging behaves exactly as before.

For **sidecars** these are pod annotations, wired through the **Diagrid Dapr Injector (Option 4)**; the standard sidecar injector (Options 1–3) does not pass them. For **sentry**, use the Helm values from the table above.

| Annotation (Option 4) | Meaning |
|---|---|
| `dapr.io/log-outputs` | Comma-separated destinations: `stdout`, `stderr`, or file paths. Merged with `dapr.io/log-file` |
| `dapr.io/log-timestamp-format` | Timestamp as a Go time layout |
| `dapr.io/log-file-max-size` | Megabytes before rotation |
| `dapr.io/log-file-max-backups` | Rotated files to keep |
| `dapr.io/log-file-max-age` | Days to retain rotated files |
| `dapr.io/log-file-compression` | `none` (default) or `gzip` |

**Console and file at the same time** (file feeds a log shipper, console keeps `kubectl logs` working):

```yaml
annotations:
  dapr.io/log-outputs: "stdout,/var/log/dapr/daprd.log"
  dapr.io/volume-mounts-rw: "log-volume:/var/log/dapr"
```

**Rotation** — a rotating file capped at roughly 20GB of history, gzip-compressed:

```yaml
annotations:
  dapr.io/log-outputs: "stdout,/var/log/dapr/daprd.log"
  dapr.io/log-file-max-size: "1024"
  dapr.io/log-file-max-backups: "20"
  dapr.io/log-file-compression: "gzip"
  dapr.io/volume-mounts-rw: "log-volume:/var/log/dapr"
```

**Mapping from Logback** (for teams porting Java logging configuration):

| Logback | D3E equivalent |
|---|---|
| `max-file-size` (e.g. 1GB) | `log-file-max-size` in megabytes (1GB → `1024`) |
| `max-history` | `log-file-max-age` (days) |
| gzip via `file-name-pattern` ending `.gz` | `log-file-compression: "gzip"` |
| `clean-history-on-start: false` | built-in behavior — cleanup runs at rotation time, not process start |
| `total-size-cap` | approximate with `max-backups × max-file-size` (e.g. 20 × 1GB ≈ 20GB) |
| custom `file-name-pattern` | not supported — rotated archives use a fixed `name-<timestamp>.log[.gz]` naming; log shippers should tail the active file |

**Timestamp format** is a [Go time layout](https://pkg.go.dev/time#pkg-constants). From Java `SimpleDateFormat`: `yyyy/MM/dd HH:mm:ss.SSS` → `2006/01/02 15:04:05.000`.

```yaml
annotations:
  dapr.io/log-timestamp-format: "2006/01/02 15:04:05.000"
```

:::warning
Log files must live on a **per-pod volume** (`emptyDir`, `hostPath`, or a per-replica `subPath`). Rotation renames files, so multiple replicas writing to one shared RWX volume path will lose data.
:::

:::note

- Rotation and compression options apply to every file destination and have no effect without one; a warning is logged if they are set with no file destination configured.
- `stdout` and `stderr` are reserved destination names — a log file cannot be named exactly `stdout` or `stderr`.
- When only `log-file-max-age` or `log-file-compression` is set, a default max size of 100MB applies.
  :::
