Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ site.add("public/assets/img/dashboard-2-1024x594.png", "assets/img/dashboard-2-1
site.add("public/assets/img/toexceptionless.png", "assets/img/toexceptionless.png")
site.add("public/assets/img/logs-2.jpg", "assets/img/logs-2.jpg")
site.add("public/assets/img/slider-github.jpg", "assets/img/slider-github.jpg")
site.add("public/assets/img/docs/configuration-precedence.svg", "assets/img/docs/configuration-precedence.svg")
site.add(
"public/assets/img/docs/infrastructure-role-selection.svg",
"assets/img/docs/infrastructure-role-selection.svg",
)
site.add("public/assets/img/docs/redis-connection-ownership.svg", "assets/img/docs/redis-connection-ownership.svg")
site.add("public/assets/img/docs/helm-version-rollout.svg", "assets/img/docs/helm-version-rollout.svg")

site.hooks.markdownIt((markdownIt: any) => {
const defaultHeadingOpen = markdownIt.renderer.rules.heading_open ??
Expand Down
129 changes: 129 additions & 0 deletions docs/docs/self-hosting/configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
title: "Infrastructure Configuration"
---

# Infrastructure Configuration

Exceptionless can infer its cache, message bus, queue, and file storage from technology-named connection strings. Define a technology once and each compatible role will use it automatically.

Configuration sources use the normal application precedence: command-line arguments override `EX_` environment variables, which override ordinary environment variables (including Aspire-injected variables), which override environment-specific and base YAML files. If both `EX_ConnectionStrings__Redis` and `ConnectionStrings__Redis` are present, the `EX_` value wins.

## What changed

This model adds automatic provider selection without changing the public option types. New configurations declare technology connection strings only. Existing Helm, Docker Compose, and self-hosted role selectors remain supported as a compatibility layer. The runtime also keeps Redis connections isolated by their effective connection string, so a legacy role-specific endpoint cannot leak into another role.

![Configuration sources override one another before explicit role selectors or automatic priorities are evaluated.](/assets/img/docs/configuration-precedence.svg)

## Automatic role selection

| Role | Automatic priority |
| --- | --- |
| Cache | `Redis`, then local memory |
| MessageBus | `RabbitMQ`, then `Redis`, then local memory |
| Queue | `AzureQueues`, then `SQS`, then `Redis`, then local memory |
| Storage | `AzureStorage`, then `S3`, then `Aliyun`, then `Folder`, then local memory |

Redis is never selected for file storage. For production file storage, configure durable Azure Blob, S3, Aliyun, or folder storage.

The first configured technology in each role's row wins. Technology connection strings are atomic: extend or replace `ConnectionStrings:Redis` itself rather than defining a second Cache or Queue connection string. Existing `ConnectionStrings:Cache`, `MessageBus`, `Queue`, and `Storage` values still win when present so deployed Helm and Docker configurations remain safe, but they are no longer the recommended configuration model.

![Automatic infrastructure role selection priorities, with Redis intentionally excluded from Storage.](/assets/img/docs/infrastructure-role-selection.svg)

An existing explicit role selector short-circuits this graph. A blank legacy role value is treated as absent.

## Examples

Environment variables use double underscores where YAML or .NET configuration uses colons.

### Redis only

This uses Redis for Cache, MessageBus, and Queue. Storage remains local.

```yaml
EX_ConnectionStrings__Redis: redis:6379,abortConnect=false
```

### Redis with RabbitMQ

RabbitMQ automatically becomes MessageBus while Redis continues to supply Cache and Queue.

```yaml
EX_ConnectionStrings__Redis: redis:6379,abortConnect=false
EX_ConnectionStrings__RabbitMQ: amqps://user:password@rabbitmq:5671/%2F
```

To use Redis for MessageBus instead, omit the `RabbitMQ` technology connection string. Existing deployments may retain `EX_ConnectionStrings__MessageBus=provider=redis` as a compatibility override.

Legacy and inline RabbitMQ forms remain supported:

```yaml
EX_ConnectionStrings__MessageBus: 'provider=rabbitmq;server="amqps://user:password@rabbitmq:5671/%2F"'
# Or: 'provider=rabbitmq;amqps://user:password@rabbitmq:5671/%2F'
```

Percent-encode reserved characters in RabbitMQ usernames, passwords, and virtual hosts.

### Queue and storage technologies

```yaml
# Azure Queue Storage is inferred for Queue.
EX_ConnectionStrings__AzureQueues: DefaultEndpointsProtocol=https;AccountName=example;AccountKey=secret

# Azure Blob Storage is inferred for Storage.
EX_ConnectionStrings__AzureStorage: DefaultEndpointsProtocol=https;AccountName=example;AccountKey=secret

# Folder is a named local storage technology.
EX_ConnectionStrings__Folder: path=/app/storage
```

SQS and S3 use `EX_ConnectionStrings__SQS` and `EX_ConnectionStrings__S3`. Aliyun storage uses `EX_ConnectionStrings__Aliyun`. Do not configure a higher-priority technology for the same role unless that priority is intentional.

Redis and RabbitMQ native connection strings are opaque. Options belong in the complete technology connection string and are never generically concatenated with another role string. Existing `provider=...` selectors and full inline values remain supported for compatibility. New providers must add an allowlisted technology alias, compatible roles, parsing rules, and a fixed priority.

The Redis registration follows the same layering boundary:

![Redis connections are deduplicated only when roles resolve to the same exact connection string; WebSocket mapping always uses the Cache connection.](/assets/img/docs/redis-connection-ownership.svg)

Equal effective strings are deduplicated; different legacy role endpoints remain isolated. Redis telemetry is enabled whenever Redis supplies any role.

### Legacy role controls

```yaml
EX_ConnectionStrings__Cache: local
EX_ConnectionStrings__MessageBus: local
EX_ConnectionStrings__Queue: local
EX_ConnectionStrings__Storage: local
```

These role keys are retained for upgrades and exceptional compatibility needs; new provider-free configurations normally omit them. `local` storage is in memory. Use the named `Folder` technology when local storage must survive restarts.

## Helm, Docker Compose, and Aspire

Existing Helm values and Docker Compose configurations do not need to change. Their explicit `Cache`, `MessageBus`, `Queue`, and `Storage` selectors have the highest role-selection precedence and remain supported. Helm's folder storage setting and persistent-volume behavior are unchanged.

The current Helm chart deliberately operates in legacy-selector mode: it renders explicit values for all four roles. Consequently, adding `EX_ConnectionStrings__RabbitMQ` by itself does **not** switch MessageBus to RabbitMQ because the chart's explicit `MessageBus` value wins. Configure RabbitMQ through the existing Helm value instead:

```yaml
messagebus:
connectionString: 'provider=rabbitmq;server="amqps://user:password@rabbitmq:5671/%2F"'
```

Do not set any distributed Helm role to `local` when multiple app or job replicas may run. In-memory caches, message buses, queues, and storage are process-local and cannot coordinate replicas.

Aspire injects connection strings as `ConnectionStrings__{resource-name}` environment variables. The existing `Redis`, `AzureStorage`, and `AzureQueues` resource names therefore become `ConnectionStrings__Redis`, `ConnectionStrings__AzureStorage`, and `ConnectionStrings__AzureQueues`, which match the automatic selection aliases. Aspire does not need role selectors. An `EX_ConnectionStrings__{name}` value still wins when both forms are present.

Elasticsearch, email, OAuth, LDAP, and other fixed-service connection strings are not part of infrastructure role selection.

## Rolling out the change

A rolling **version-only** Helm upgrade is compatible with mixed old and new Exceptionless instances when every rendered role selector and every effective connection string remains exactly unchanged. Keep the existing `Cache`, `MessageBus`, `Queue`, and `Storage` values in place while upgrading the images. All overlapping instances then use the same providers and endpoints.

This compatibility statement covers the Exceptionless app and job workloads. A production installation also needs durable, highly available infrastructure; the chart's bundled single-replica Redis and Elasticsearch resources are convenience dependencies, not a zero-downtime production topology.

Removing a selector is safe during normal operation only when the role resolves to the same provider **and the same effective connection string** before and after removal. Compare the resolved result, not merely the technology name. For example, removing `MessageBus=provider=redis` is safe only if automatic selection still chooses Redis with the identical Redis connection string.

Changing a role's provider or endpoint is an infrastructure migration, not a zero-downtime configuration cleanup. During a rolling change, old and new replicas would otherwise be split across message buses, queue backlogs, storage locations, distributed caches, locks, and WebSocket mappings. Use a provider-specific bridge or dual-read/write migration where the technology supports it, or quiesce producers, drain outstanding work, switch every replica together during a maintenance window, and verify the new backend before resuming traffic.

Do not combine a binary upgrade with a selector, provider, or endpoint migration. If a configuration migration must be rolled back, restore the old selector and endpoint first and wait for all replicas to converge before rolling back the application version.

![A safe Helm image rollout keeps selectors and effective endpoints unchanged; provider or endpoint changes follow a separate migration path.](/assets/img/docs/helm-version-rollout.svg)
2 changes: 2 additions & 0 deletions docs/docs/self-hosting/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ title: "Docker"

If you would like to test Exceptionless locally, please follow this section.

See [Infrastructure Configuration](/docs/self-hosting/configuration) for technology-named Redis, RabbitMQ, queue, and storage connection strings. Existing Docker Compose selectors remain supported as compatibility settings.

## Requirements

* [Docker](https://www.docker.com)
Expand Down
1 change: 1 addition & 0 deletions docs/docs/self-hosting/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ You can also use Kubernetes while self-hosting Exceptionless. We'll cover both t

* [Docker](/docs/self-hosting/docker)
* [Kubernetes](/docs/self-hosting/kubernetes)
* [Infrastructure configuration](/docs/self-hosting/configuration)
* [Upgrading](/docs/self-hosting/upgrading-self-hosted-instance)

---
Expand Down
27 changes: 17 additions & 10 deletions docs/docs/self-hosting/kubernetes.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Please follow this section to set up Exceptionless in a Kubernetes environment.

## Instructions

Please note that we recommend you use Kubernetes for running in production.
Please note that we recommend you use Kubernetes for running in production. Configure durable, highly available Redis and Elasticsearch services for a production installation. The chart's bundled single-replica dependencies are intended for evaluation and do not provide a zero-downtime or durable production topology.

1. Follow the steps [here](https://github.com/exceptionless/Exceptionless/blob/master/k8s/ex-setup.ps1) for how to create it in AKS
2. View the configuration settings below for more information on configuring Exceptionless.
Expand All @@ -29,22 +29,29 @@ _Please note that if you are specifying configuration via `docker-compose`, then

## ConnectionStrings

See [Infrastructure Configuration](/docs/self-hosting/configuration) for the technology priority table, RabbitMQ examples, compatibility controls, Aspire naming, and rollout guidance. Existing Helm values remain supported without changes.

The current chart renders explicit legacy selectors for Cache, MessageBus, Queue, and Storage. Those selectors intentionally override automatic technology selection. Adding `EX_ConnectionStrings__RabbitMQ` under `config` alone therefore does not switch MessageBus. Use the existing Helm value:

```yaml
# connection string used for any provider specifying Redis.
messagebus:
connectionString: 'provider=rabbitmq;server="amqps://user:password@rabbitmq:5671/%2F"'
```

Keep every selector and effective endpoint unchanged during a rolling image upgrade. Changing a selector, provider, or endpoint is a separate infrastructure migration and requires a bridge or dual-read/write process, or a quiesce-and-drain maintenance window. Do not use `local` for a distributed role when multiple replicas may run; each replica would receive isolated in-memory state.

```yaml
# Redis automatically supplies Cache, MessageBus, and Queue when a higher-priority
# technology for a role is not configured.
EX_ConnectionStrings__Redis: localhost:6379,abortConnect=false

EX_ConnectionStrings__Cache: provider=redis;
EX_ConnectionStrings__Elasticsearch: server=http://10.0.0.4:9200;
EX_ConnectionStrings__Email: smtps://user%40domain.com:password@smtp.domain.com:465
EX_ConnectionStrings__MessageBus: provider=redis;
EX_ConnectionStrings__Metrics: provider=statsd;server=localhost
EX_ConnectionStrings__Queue: provider=redis;
EX_ConnectionStrings__Storage: provider=azurestorage;
EX_ConnectionStrings__AzureQueues: DefaultEndpointsProtocol=https;AccountName=example;AccountKey=secret
EX_ConnectionStrings__AzureStorage: DefaultEndpointsProtocol=https;AccountName=example;AccountKey=secret
```

You can append values to any connection string using a `;`. For example, you can control many shards and replicas each Elasticsearch index should be created with by appending to the `EX_ConnectionStrings__Elasticsearch` connection string. For a Elasticsearch cluster (3 nodes, two masters), you would append `shards=3;replicas=1`.

The `provider` value determines what implementations to use for the various abstractions. We've made it easier to reuse a single connection string by automatically looking up a connection string by the provider name and adding any key value pairs to the current connection string (as shown above with redis).
Structured connection strings support provider-specific key-value options. For example, you can control how many shards and replicas each Elasticsearch index should be created with by appending `shards=3;replicas=1` to `EX_ConnectionStrings__Elasticsearch`. Redis and RabbitMQ use their native complete connection-string formats.

## General Configuration

Expand Down
8 changes: 8 additions & 0 deletions docs/docs/self-hosting/upgrading-self-hosted-instance.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ title: "Upgrading"

**Please ensure that you have created backups before upgrading!**

## Layered infrastructure connection strings

Existing Helm values, Docker Compose settings, and explicit `provider=...` role connection strings require no changes. They continue to override automatic provider selection. New installations may use the simpler technology-named connection strings described in [Infrastructure Configuration](/docs/self-hosting/configuration).

For a rolling Helm image upgrade, leave every rendered role selector and effective connection string exactly unchanged while old and new replicas overlap. The current chart remains in legacy-selector mode, so a technology-named RabbitMQ connection string alone does not override its explicit MessageBus value. Configure RabbitMQ through `messagebus.connectionString` as documented in [Infrastructure Configuration](/docs/self-hosting/configuration#helm-docker-compose-and-aspire).

Treat selector removal and provider or endpoint changes as separate infrastructure migrations. Selector removal is safe only when the resolved provider and exact effective connection string are identical before and after the change. A real backend change requires a provider-specific bridge or dual-read/write process, or a quiesce-and-drain maintenance window; it is not a zero-downtime rolling configuration change.

**If you are upgrading from v1 or [v2](https://github.com/exceptionless/Exceptionless/releases/tag/v2.0.0) you will need to upgrade to [v3.0](https://github.com/exceptionless/Exceptionless/releases/tag/v3.0.0) before upgrading to the latest release.**

## Upgrading from v7.1 to v8
Expand Down
Loading
Loading