Skip to content

Self-host with the Helm chart

The Fleans release pipeline publishes a packaged Helm chart (fleans-<VER>.tgz) on every v<SemVer> tag. This guide walks through the install lifecycle against a real Kubernetes cluster: acquire the tarball, install with sane defaults, harden for production, upgrade across releases, and recover from common failures. For chart internals (component matrix, kind smoke test, chart structure), the canonical reference is Self-Hosting on Kubernetes.

  • Kubernetes 1.27+ (the chart is built and tested against the current three minor versions).
  • Helm 3.12+. Older 3.x may work but is unsupported.
  • A StorageClass if you keep the chart-managed Postgres StatefulSet (postgres.persistentVolume.enabled=true, the default). Skip when pointing at an external Postgres.
  • Pull access to ghcr.io/nightbaker/fleans-{api,web,mcp} — public for the Fleans repo’s published images. Provide imagePullSecrets if you mirror to a private registry.
  • A namespace you control (kubectl create ns fleans or use any existing).
Terminal window
gh release download v0.1.0-beta --repo nightBaker/fleans -p 'fleans-*.tgz'

Or via curl:

Terminal window
curl -LO https://github.com/nightBaker/fleans/releases/download/v0.1.0-beta/fleans-0.1.0-beta.tgz

The release pipeline signs both the chart tarball (as a blob) and every container image (by manifest digest). Run two checks before helm install:

1. Verify the helm chart tarball. Download fleans-0.1.0-beta.tgz, fleans-0.1.0-beta.tgz.sig, and fleans-0.1.0-beta.tgz.crt from the same GitHub Release, then:

Terminal window
cosign verify-blob \
--certificate fleans-0.1.0-beta.tgz.crt \
--signature fleans-0.1.0-beta.tgz.sig \
--certificate-identity-regexp "https://github.com/nightBaker/fleans/.github/workflows/release.yml@refs/tags/v.*" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
fleans-0.1.0-beta.tgz

2. Verify each container image. The chart pulls four images (one of which — fleans-api — is reused by the core and worker Deployments per the chart’s values.yaml design; even though the Helm chart pulls only image.api for both core and worker Deployments at runtime, the release pipeline publishes all four as distinct signed images for users who want a Worker silo deployable in non-chart deployments):

Terminal window
for SVC in api web worker mcp; do
cosign verify \
--certificate-identity-regexp "https://github.com/nightBaker/fleans/.github/workflows/release.yml@refs/tags/v.*" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/nightbaker/fleans-$SVC:0.1.0-beta
done

For production Kubernetes installs, the recommended enforcement is the Sigstore Policy Controller (policy.sigstore.dev/v1beta1 ClusterImagePolicy) or Kyverno’s verifyImages/verifyManifests rules.

Terminal window
helm install fleans ./fleans-0.1.0-beta.tgz \
--namespace fleans \
--create-namespace
kubectl wait --for=condition=ready pod \
-l app.kubernetes.io/instance=fleans \
--timeout=180s --namespace fleans
kubectl port-forward -n fleans svc/fleans-web 8080:8080

Open http://localhost:8080 — the admin UI loads. Tear down at any time with helm uninstall fleans -n fleans.

The defaults below are extracted from charts/fleans/values.yaml. Override on the install command line via --set or pass a -f my-values.yaml overlay.

KeyDefaultDescription
image.api.repositoryghcr.io/nightbaker/fleans-apiImage used by both core and worker Deployments.
image.web.repositoryghcr.io/nightbaker/fleans-webBlazor Server admin UI image.
image.mcp.repositoryghcr.io/nightbaker/fleans-mcpMCP server image.
image.tag(empty — falls back to chart appVersion)Pin to a specific tag for reproducible installs.
image.pullPolicyIfNotPresentStandard Kubernetes pull policy.
imagePullSecrets[]Add [{name: my-pull-secret}] when mirroring images.
core.replicas1Number of Core silos.
core.resources.requestscpu: 250m, memory: 512MiRight-sized for ~10 active workflows; bump for higher throughput.
core.resources.limits.memory1GiHard cap.
worker.enabledfalseSet true to dedicate pods to [StatelessWorker] script/condition grains.
worker.replicas1Worker silo count when worker.enabled=true.
worker.nodeSelector / worker.tolerations{} / []Pin worker silos to spot/cheaper nodes.
customWorker.enabledfalseSet true to host user-written custom-task plugins on a dedicated silo built from the fleans-custom-worker-example template.
customWorker.replicas1Custom-worker silo count when customWorker.enabled=true.
web.enabledtrueDisable to ship a headless cluster (no admin UI).
web.replicas1Blazor Server is sticky-session-friendly; HA needs careful affinity.
web.service.port8080Service port for the UI.
mcp.enabledtrueDisable when no MCP-aware AI agents will connect.
mcp.service.port5200MCP server port.
ingress.enabledfalseToggle the chart’s Ingress for the admin UI.
ingress.className""e.g. nginx, traefik. Empty inherits the cluster default.
ingress.hostfleans.example.comPublic hostname.
ingress.tls.enabledfalseSet true + ingress.tls.secretName to terminate TLS.
persistence.providerPostgresPostgres (production) or Sqlite (dev parity, single-replica only — see chart-side caveat).
postgres.enabledtrueSet false to bring your own Postgres (see §4).
postgres.image.tag"16"Stays in sync with the test matrix in Fleans.Persistence.Tests.
postgres.databasefleansDB name created on first boot.
postgres.usernamefleansWorkflow user.
postgres.password""Empty = chart auto-generates and stores in a Secret.
postgres.existingSecret""Reference an existing Secret with key postgres-password.
postgres.persistentVolume.enabledtrueDisable for ephemeral data; persistence requires a StorageClass.
postgres.persistentVolume.size8GiSize the PVC.
redis.enabledtrueRequired — Orleans clustering depends on Redis.
redis.image.tag"7-alpine"Pinned.
redis.persistentVolume.enabledfalseOff by default; Orleans state is rebuilt from Postgres event store.
auth.authority""OIDC issuer URL. Leaving empty disables auth (Fleans.Web fallback).
auth.clientId""Web admin UI OIDC client ID.
auth.clientSecretExistingSecret""Reference a Secret with key client-secret. Use this in production.
auth.clientSecret""Inline OIDC client secret. Not recommended for production.
streaming.providerMemoryMemory (in-process) or Kafka (durable).
streaming.redis.totalQueueCount8Orleans pulling-agent count for Redis streaming.
streaming.kafka.brokers""Comma-separated Kafka brokers, e.g. kafka.kafka.svc:9092.
streaming.kafka.queueCount8Orleans pulling-agent count for Kafka streaming.
extraEnv[]Extra env vars on every Fleans workload (api, web, mcp). Use for ConnectionStrings__fleans overrides.

To tune Orleans consumer parallelism (pulling-agent count per cluster) for higher throughput, set Fleans__Streaming__Redis__TotalQueueCount (Redis), Fleans__Streaming__Kafka__QueueCount (Kafka), or supply an explicit Fleans__Streaming__AzureQueue__QueueNames__0..N list — see Tuning throughput for the sizing heuristic, rehash caveat, and Kafka-side NumPartitions tuning.

Cross-link: see the chart component matrix for the per-Deployment image / port table — not duplicated here.

Each item below maps to a values.yaml override. Combine into a single values-prod.yaml overlay and pass with -f.

External Postgres (managed RDS / Cloud SQL / Aiven)

Section titled “External Postgres (managed RDS / Cloud SQL / Aiven)”
postgres:
enabled: false
persistence:
provider: Postgres
extraEnv:
- name: ConnectionStrings__fleans
valueFrom:
secretKeyRef:
name: fleans-pg
key: connection-string

Create the Secret separately (out-of-band or via your secrets operator):

Terminal window
kubectl create secret generic fleans-pg \
-n fleans \
--from-literal=connection-string='Host=my-pg.example.com;Port=5432;Database=fleans;Username=fleans;Password=...;Ssl Mode=Require'

The Postgres user needs CREATE TABLE on first boot (for migrations), then only INSERT/UPDATE/SELECT/DELETE thereafter.

auth:
authority: https://idp.example.com/realms/fleans
clientId: fleans-web
clientSecretExistingSecret: fleans-oidc # Secret with key `client-secret`

The chart wires the Web pods’ env from this Secret. See Authentication for the OIDC scopes the admin UI requests and the API’s JWT validation contract.

ingress:
enabled: true
className: nginx
host: fleans.example.com
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
tls:
enabled: true
secretName: fleans-tls

cert-manager populates fleans-tls via the cert-manager.io/cluster-issuer annotation; the chart references the secret in the Ingress’s tls block.

For compute-isolation of script / condition grains:

worker:
enabled: true
replicas: 2
nodeSelector:
workload: fleans-worker
tolerations:
- key: dedicated
value: fleans-worker
effect: NoSchedule

The chart’s defaults assume a small workload. For production load, raise core.replicas and bump CPU/memory based on observed throughput in the Orleans Dashboard. See Observability for the dashboard URL and metrics that matter.

Image-pull secrets (private registry mirroring)

Section titled “Image-pull secrets (private registry mirroring)”
imagePullSecrets:
- name: my-registry-pull
Terminal window
gh release download v0.2.0 --repo nightBaker/fleans -p 'fleans-*.tgz'
helm upgrade fleans ./fleans-0.2.0.tgz \
--namespace fleans \
-f values-prod.yaml

Helm rolls the new pods forward; Fleans.Api runs pending EF migrations on startup against the existing Postgres state. If the rollout misbehaves:

Terminal window
helm rollback fleans <REVISION> -n fleans

helm history fleans -n fleans lists revision numbers. Rollback is non-destructive to Postgres; the database schema is forward-compatible within the documented per-release migration window.

  • ImagePullBackOff. Either the registry is private and imagePullSecrets is missing, or the tag doesn’t exist. kubectl describe pod <name> shows the underlying error.
  • CrashLoopBackOff on fleans-api with Postgres connection error. When postgres.enabled=false, the extraEnv ConnectionStrings__fleans must resolve before the API pod starts. Check the referenced Secret via kubectl get secret fleans-pg -o jsonpath='{.data.connection-string}' | base64 -d.
  • OIDC redirect_uri_mismatch from the IdP. Add the new ingress host (e.g. https://fleans.example.com/signin-oidc) to the IdP’s allowed redirect URIs.
  • Live-edit drift after kubectl edit. Helm tracks the rendered manifest; in-cluster edits get reverted on the next helm upgrade. Inspect effective values with helm get values fleans -n fleans.
  • HPA fights Orleans placement. Orleans grains stick to silos; an HPA scaling Core or Worker silos in/out can churn placement. Set core.replicas / worker.replicas explicitly for stable workloads.
  • Self-Hosting on Kubernetes — chart component matrix, kind smoke test, chart-internal architecture.
  • Configuration — full configuration matrix (env-var → values.yaml mapping).
  • Persistence — Postgres + Sqlite providers, external-Postgres connection-string contract.
  • Authentication — OIDC scopes, JWT validation, the cookie + bearer flows.
  • Streaming — Kafka provider configuration and production-readiness caveat.
  • Cutting a Release — maintainer runbook for publishing a new chart tarball.