Skip to content
GitHub

Deployment

Aspire is Fleans’ development orchestrator — not a production runtime. To run Fleans in staging or production, you need different artifacts: a Docker Compose stack, Kubernetes manifests, or a set of services managed by your VM’s process supervisor. This page walks through each path.

Helm on Kubernetes: if you control Kubernetes, the Helm chart published to https://nightbaker.github.io/fleans is the recommended path. See Self-host with Helm for the install walkthrough. This page covers (1) the released Docker Compose bundle, (2) raw Kubernetes manifests extracted from the Helm chart via helm template for GitOps users who want YAML they can commit and edit.

A production Fleans cluster has these components:

The components of a production Fleans cluster and how they connect. Open interactive diagram ↗
ComponentImage / processRequiredPurpose
Core silo (fleans-api)ghcr.io/nightbaker/fleans-api with Fleans__Role=CoreyesAPI endpoints, coordinator grains, workflow state. The same image hosts a Combined silo if Fleans__Role is unset or Combined.
REST API (fleans-api, Fleans__Role=Api)ghcr.io/nightbaker/fleans-api with Fleans__Role=ApioptionalREST only: an Orleans client with no grains, scaled on requests per second apart from the silos. Without it the Core / Combined silo serves REST.
Worker silo (fleans-worker)ghcr.io/nightbaker/fleans-worker (defaults Fleans__Role=Worker)optional[StatelessWorker] script + condition grains and the read-model projector (instance lists, finished-instance views). Skip if a Combined-role API silo handles them; without any worker silo they run on the core silos.
Custom-task worker (fleans-plugins)ghcr.io/nightbaker/fleans-plugins, or your own image from the Hosting Workers templateoptionalCustom tasks (REST Caller, Camunda external tasks, or yours). Connects to the API over gRPC with Fleans__ExternalTaskWorker__EngineUrl; joins no cluster, so plugin failures stay away from the engine.
Web admin UI (fleans-web)ghcr.io/nightbaker/fleans-weboptionalBlazor Server admin UI on port 8080.
MCP server (fleans-mcp)ghcr.io/nightbaker/fleans-mcpoptionalMCP server on port 5200 for AI-agent integration.
Redisredis:7-alpine or managedwhile a provider uses itOrleans clustering, reminders and streams when their provider is Redis (the defaults), and the admin UI’s Data Protection keys. Optional once clustering and reminders are on Postgres and streams on Kafka / Azure Queue — see Running without Redis.
PostgreSQLpostgres:16 or managedrecommended for prodWorkflow event store + read model. SQLite is local-dev only.
Kafkaany Kafka brokeroptionalReplaces in-memory streaming when at-least-once durability is required.

All silo and host processes expose two health endpoints, wired through MapDefaultEndpoints() in Fleans.ServiceDefaults:

  • /alive — liveness only (process up). Use for Kubernetes liveness probes.
  • /health — full check (Postgres reachable, and Redis when a provider uses it). Use for readiness probes so a transient dependency blip does not trigger pod restarts.

Fleans:Role is read once at startup and stamped into the Orleans SiloName as {role}-{machine}-{guid}. Three silo values are accepted (case-insensitive), plus Api on fleans-api:

ValueDefault forHosts
Core—Coordinator grains (workflow instances, definitions, correlations).
Workerfleans-worker[StatelessWorker] script/condition grains and the read-model projector.
Combinedfleans-apiBoth. Single-binary topology for small deployments and dev.
Api—Nothing: fleans-api runs as an Orleans client, not a silo, and serves only the REST API. Needs at least one Core or Combined silo in the cluster.

Invalid values throw at startup. The simplest production topology is one Combined silo on a single PostgreSQL — see Simplest start: one PostgreSQL; split into Api + Core + Worker when you need to scale or isolate them — see Recommended for scale.

The release pipeline attaches docker-compose-v<TAG>.zip to every GitHub Release. Download and unpack:

Terminal window
gh release download v<TAG> --repo nightBaker/fleans -p 'docker-compose-*.zip'
unzip docker-compose-v<TAG>.zip -d fleans-compose
cd fleans-compose

The bundle ships a self-contained compose.yaml, an .env file with generated secrets, and a docker-compose.override.yaml (empty by default — use it for managed-Postgres / managed-Redis overrides per § Override managed Postgres / Redis below).

Bring the stack up:

Terminal window
docker compose up -d
docker compose ps

Service URLs (default host ports from the post-processed bundle — confirm in .env):

For the full lifecycle walkthrough (.env tuning, persistence backups, upgrade path, troubleshooting) see Self-host with Docker Compose. The sub-sections below cover the topology overrides this reference page owns.

The bundled compose.yaml includes in-cluster postgres and redis services by default. To use managed services (RDS, ElastiCache, Cloud SQL, Memorystore, …), edit docker-compose.override.yaml:

  1. Override postgres and redis (or orleans-redis) with deploy: { replicas: 0 } so they don’t start (or drop them from compose.yaml if you prefer the bundle to be source-of-truth — leaving the override file as your only diff is the recommended pattern).
  2. Drop the depends_on entries that reference them on fleans-core / fleans-management / fleans-mcp.
  3. Replace the auto-generated connection strings with the managed endpoints:
docker-compose.override.yaml
services:
fleans-core:
environment:
- ConnectionStrings__fleans=Host=db.example.internal;Database=fleans;Username=fleans;Password=${PG_PASSWORD}
- ConnectionStrings__fleans-query=Host=db-replica.example.internal;Database=fleans;Username=fleans_ro;Password=${PG_RO_PASSWORD}
- ConnectionStrings__orleans-redis=redis.example.internal:6379,password=${REDIS_PASSWORD}
- Persistence__Provider=Postgres
# repeat for fleans-management, fleans-mcp

The connection-string keys are the canonical names the silos expect: fleans (write), fleans-query (optional read replica — falls back to fleans when absent), fleans-journal (optional database for the workflow journal — falls back to fleans; see Separate journal database), and orleans-redis (clustering + grain storage). ConnectionStrings__ (double underscore) is the standard .NET environment-variable form of ConnectionStrings:fleans.

Terminal window
docker compose up -d --scale fleans-core=3

Stateless worker grains are placed across all available Worker/Combined silos automatically — Orleans handles routing through the Redis cluster table.

Terminal window
docker compose exec fleans-core curl -fsS http://localhost:8080/alive
docker compose exec fleans-core curl -fsS http://localhost:8080/health
Terminal window
docker compose logs -f fleans-core
docker compose down # keep volumes
docker compose down -v # also drop data

Path 2 — Kubernetes (raw manifests via helm template)

Section titled “Path 2 — Kubernetes (raw manifests via helm template)”

For GitOps repos or environments where Helm isn’t on the cluster-side install path, extract raw manifests from the released chart using helm template. The output is plain YAML you can commit, diff, and edit by hand — but produced from a released artifact, so you stay on a maintained chart version.

Terminal window
helm repo add nightbaker https://nightbaker.github.io/fleans
helm repo update
helm template fleans nightbaker/fleans -n fleans \
--set persistence.provider=Postgres \
> out/k8s/manifests.yaml

The output contains a Deployment + Service for each Fleans workload (fleans-core, fleans-management, fleans-mcp), a Secret for the Postgres connection string, and StatefulSet resources for the in-cluster Postgres + Redis when the chart’s postgres.enabled / redis.enabled defaults aren’t overridden. Re-run helm template whenever you bump the chart version.

Terminal window
kubectl create namespace fleans
kubectl apply -n fleans -f out/k8s/manifests.yaml
kubectl get -n fleans pods -w

Wait until every pod reaches Running with both containers 1/1.

For any production cluster, point the silos at managed databases instead of in-cluster StatefulSets. Pass the override at helm template time:

Terminal window
helm template fleans nightbaker/fleans -n fleans \
--set persistence.provider=Postgres \
--set postgres.enabled=true \
--set 'extraEnv[0].name=ConnectionStrings__fleans' \
--set 'extraEnv[0].value=Host=db.example.internal;Database=fleans;Username=fleans;Password=...' \
--set 'extraEnv[1].name=ConnectionStrings__orleans-redis' \
--set 'extraEnv[1].value=redis.example.internal:6379,password=...' \
> out/k8s/manifests.yaml

The chart emits the canonical Fleans probes on every silo + host. Confirm in the generated manifests:

livenessProbe:
httpGet: { path: /alive, port: 8080 }
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
httpGet: { path: /health, port: 8080 }
initialDelaySeconds: 5
periodSeconds: 5

Why the split: /alive is process-only — a Redis blip won’t kill pods. /health is full-fabric — it pulls a degraded pod out of the Service until dependencies recover.

Terminal window
kubectl -n fleans scale deployment fleans-core --replicas=3

For external access to the admin UI, add an Ingress (or a LoadBalancer Service) targeting fleans-management on port 8080. The chart ships an Ingress template you can re-extract via helm template ... --set ingress.enabled=true.

helm install is the lower-friction path when Helm IS on the cluster-side install path — helm upgrade gracefully handles config changes; helm rollback exists. Use helm template only when (a) you commit YAML to a GitOps repo, (b) you need to hand-edit manifests Helm doesn’t expose as values, or (c) Helm isn’t available cluster-side. See Self-host with Helm for the install walkthrough.

Terminate TLS at nginx (or Caddy, HAProxy, Traefik) and forward to the admin UI / API:

server {
listen 443 ssl http2;
server_name fleans.example.com;
ssl_certificate /etc/letsencrypt/live/fleans.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/fleans.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080; # Fleans.Web admin UI
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; # Blazor Server uses WebSockets
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /api/ {
proxy_pass http://127.0.0.1:8081/; # Fleans.Api
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

When Fleans.Web runs behind a TLS-terminating proxy, set the KnownProxies / KnownNetworks allowlist (see Authentication) so OIDC redirect URIs are built from the public host.

The engine ships only OCI container images and the Helm chart. To run on a bare VM without a container runtime, the supported path is to run the released images under podman or rootless docker driven by systemd — once the container is launched, the rest of this page applies. The engine does not publish standalone .tar.gz binaries.

The full canonical list of every configuration key — what it does, where it’s read in source, and which environment-variable equivalent operators set on a container or systemd unit — is on the dedicated Configuration page. The summary tables below cover the deployment-relevant subset; cross-link to that page for the per-host applicability matrix and source pins.

: in config keys becomes __ (double underscore) in environment variables — see Configuration / The naming rule.

KeyAllowed valuesDefaultRead byNotes
Fleans:RoleCore, Worker, Combined; also Api on Fleans.Api (case-insensitive)Combined (Api), Worker (WorkerHost)Fleans.Api, Fleans.WorkerHostStamped into SiloName. Api = REST-only Orleans client. Invalid values throw at startup.
KeyRequiredRead byNotes
ConnectionStrings:fleansyes (Postgres mode)All silosWorkflow command-side database (event store + write model).
ConnectionStrings:fleans-querynoAll silos, Web, MCPRead replica for every read-model query (admin UI, MCP, REST listings). Falls back to fleans when unset. See Persistence → Read replica.
ConnectionStrings:fleans-journalnoAll silosWorkflow journal (event batches + snapshots). Falls back to fleans when unset. Pair with Persistence:Journal:Provider=Citus to shard it.
ConnectionStrings:orleans-rediswhen a provider is RedisAll hostsOrleans clustering, reminders and streams when their provider is Redis (the defaults); hosts with no Redis provider never connect. Note the name — it’s orleans-redis, not bare redis.
KeyAllowed valuesDefaultNotes
Persistence:ProviderSqlite, Postgres (case-insensitive)SqlitePostgres runs MigrateAsync() at startup; Sqlite runs EnsureCreated(). SQLite is dev-only — see Persistence.
KeyAllowed valuesDefaultNotes
Fleans:Streaming:ProviderMemory, Kafka (case-insensitive)MemorySee Streaming for at-least-once semantics.
Fleans:Streaming:Kafka:Brokerscomma-separated host:port list—Required when Provider=Kafka.
Fleans:Streaming:Kafka:ConsumerGroupstringfleans
Fleans:Streaming:Kafka:TopicPrefixstringfleans-

The API, the MCP server and the admin UI ship with opt-in authentication — when nothing is configured, all run unauthenticated, identical to dev mode. See Authentication for the full key reference, role plans, and reverse-proxy guidance.

KeyRequiredDefaultNotes
Authentication:Authorityto enable— (auth disabled)OIDC issuer. Setting this enables JWT enforcement on every API and MCP endpoint and OIDC sign-in on the admin UI.
Authentication:AudienceAPI, MCPfleans-api / fleans-mcpJWT aud claim the host requires.
Authentication:RequireHttpsMetadatanotrueSet false only for local dev against an HTTP IdP.
Authentication:ClientIdWeb—OIDC client ID for the Blazor Server admin UI.
Authentication:ClientSecretWeb—Client secret. Source from a Secret/Key Vault, not appsettings, in production.
  • /alive and /health are exposed on every silo + host (MapDefaultEndpoints).
  • The Orleans Dashboard is available on each silo at the configured port — port-forward or expose via your reverse proxy / Ingress for ad-hoc cluster inspection.
  • Fleans.ServiceDefaults wires OpenTelemetry traces + metrics by default; point OTEL_EXPORTER_OTLP_ENDPOINT at your collector.
  • A dedicated observability page (Prometheus scrape config, Grafana dashboards) is tracked in the project backlog.

Fleans silos form a single Orleans cluster — rolling restarts are the standard upgrade path, but a few Orleans-specific invariants apply.

Orleans groups silos into a cluster by ClusterId. The default ClusterId is dev — fine for a single deployment, but you should set it explicitly per environment (production, staging-eu, …) so two unrelated deployments hitting the same Redis can’t see each other’s silos.

Terminal window
Orleans__ClusterId=fleans-production
Orleans__ServiceId=fleans

Critical for rolling upgrades: keep ClusterId (and the membership store: the Redis or Postgres clustering connection) identical across new and old pods/processes during a rollout. The new silos use the membership table to discover and gossip with the old silos so grain handoff is graceful. If ClusterId changes, the new silos form a separate logical cluster, the old grains never re-balance, and in-flight workflow events can be lost.

What a restart does to running workflows, how long failover takes, and what clients must retry: High availability.

  • Kubernetes: kubectl rollout restart deployment/fleans-api -n fleans — set strategy.type=RollingUpdate, maxUnavailable=1, maxSurge=1 so at least one Core silo always serves the cluster table.
  • Compose: docker compose -f out/compose/compose.yaml up -d --no-deps --scale fleans-api=2 fleans-api (start one extra), then drain old containers one at a time.
  • systemd: systemctl restart fleans-api on each VM, one at a time, watching journalctl -u fleans-api -f until the new instance reports Active silo count: N.

For blue / green: stand up a second cluster with a different ClusterId (e.g. fleans-production-green) and its own Redis + Postgres, then flip the load balancer. This avoids cross-cluster grain confusion.

For canary on a single cluster: deploy the new image to a small subset of replicas (maxSurge=1, maxUnavailable=0), watch error rate / latency, and either continue the rollout or kubectl rollout undo.

PostgreSQL: migrations apply automatically on Fleans.Api startup via MigrateAsync(). Roll out a new image only after confirming the new migration is backwards-compatible (additive columns, non-breaking changes) — Fleans does not gate writes during migrations.

  • Self-host with Docker Compose — full Compose install walkthrough (download, run, .env, persistence, upgrade, troubleshooting).
  • Self-host with Helm — Helm install walkthrough (verify chart + images, values.yaml reference, production checklist).
  • Self-Hosting on Kubernetes — Helm-specific quick start and chart values reference.
  • Persistence — SQLite vs PostgreSQL provider config and migration model.
  • Streaming — Memory vs Kafka provider config and at-least-once semantics.
  • Authentication — OIDC + JWT setup for API and admin UI, reverse-proxy notes.