Skip to content

Configuration

Sipke Schoorstra edited this page Aug 12, 2026 · 3 revisions

Configuration

How settings are layered, how keys are shaped, and a reference of the ones you'll actually set.


Precedence

Later wins:

  1. appsettings.json — baked into the image
  2. appsettings.{Environment}.json — baked into the image
  3. /config/config.json — your mounted file ← recommended
  4. Environment variables — highest precedence

So an environment variable always overrides the mounted file. Useful for a one-off override; a frequent source of confusion when a stray -e silently beats the file you're editing. When a setting seems to be ignored, check the environment first — from the host, since the image is chiselled and has no shell or printenv to exec into:

docker inspect elsa-pro --format '{{json .Config.Env}}' | tr ',' '\n' | grep -i cshells

The mounted config file

Mount JSON at /config/config.json. This is the recommended approach: it keeps secrets out of docker inspect and shell history, and it's far more readable than twelve -e flags.

docker run ... -v "$(pwd)/config.json:/config/config.json:ro" ghcr.io/valence-works/runtime-ce-combined:latest
volumes:
  - ./config.json:/config/config.json:ro

Include only the keys you want to override — everything else stays at its default.


Key shape

Configuration is nested JSON. Most of it lives under CShells, because each shell is an isolated Elsa engine instance with its own features:

CShells → Shells → <shell name> → Features → <feature name> → <setting>

For a single-instance deployment there is one shell, named Default.

{
  "CShells": {
    "Shells": {
      "Default": {
        "Features": {
          "DefaultAdminUser": { "AdminUsername": "admin" }
        }
      }
    }
  }
}

As an environment variable, flatten with __ (double underscore):

CShells__Shells__Default__Features__DefaultAdminUser__AdminUsername=admin

⚠️ Shells is keyed by name, not index

The segment is the shell name — Default — not a number.

✅ CShells__Shells__Default__Features__DefaultAdminUser__AdminUsername
❌ CShells__Shells__0__Features__DefaultAdminUser__AdminUsername

Using 0 doesn't error. It quietly defines a second shell called 0, and your settings never reach the shell you're actually running. If your admin user "isn't created", check this first.

Arrays are indexed numerically:

Elsa__Cors__AllowedOrigins__0=https://studio.example.com
Elsa__Cors__AllowedOrigins__1=https://admin.example.com

Settings you'll actually set

Identity and admin

Key Default Notes
CShells:Shells:Default:Features:DefaultAdminUser:AdminUsername admin
CShells:Shells:Default:Features:DefaultAdminUser:AdminPassword password Always override
CShells:Shells:Default:Features:DefaultAdminUser:AdminRoleName admin
CShells:Shells:Default:Features:DefaultAdminUser:AdminRolePermissions ["*"] Narrow this if you can
CShells:Shells:Default:Features:Identity:SigningKey placeholder Required in production. 256-bit minimum

Generate a signing key:

openssl rand -base64 32

🔐 The signing key signs authentication tokens. Anyone who knows it can mint valid tokens for your instance. Treat it exactly like a private key: never commit it, never reuse it across environments, and rotate it if it leaks. Rotating invalidates existing sessions.

Studio

Key Applies to Notes
Studio:HostingModel Both WebAssembly (default) or BlazorServer
Studio:Client:Backend:Url WebAssembly Browser-reachable API URL
Backend:Url Blazor Server Container-reachable API URL

Only the Studio:Client section is served to the browser. Never put a secret under it.

CORS

Key Notes
Elsa:Cors:AllowedOrigins Array. Needed when Studio and API are on different origins

Required for WebAssembly Studio on a separate origin; unnecessary for Blazor Server or the combined image. Use * in development only.

HTTP activities

Key Notes
CShells:Shells:Default:Features:Http:HttpActivityOptions:BaseUrl Public base URL used to build callback URLs

If workflows expose HTTP endpoints or use callbacks, this must be the externally reachable URL — what a caller outside your network would use — not localhost.

Hosting

Key Default Notes
ASPNETCORE_ENVIRONMENT Production
ASPNETCORE_URLS http://+:8080 Rarely worth changing; remap the port instead

Persistence

See Persistence and Databases.


A worked example

{
  "CShells": {
    "Shells": {
      "Default": {
        "Features": {
          "DefaultAdminUser": {
            "AdminUsername": "admin",
            "AdminPassword": "a-strong-password",
            "AdminRoleName": "admin",
            "AdminRolePermissions": ["*"]
          },
          "Identity": {
            "SigningKey": "REPLACE_WITH_openssl_rand_base64_32"
          },
          "PostgreSqlWorkflowPersistence": {
            "ConnectionString": "Host=postgres;Port=5432;Database=elsa;Username=elsa;Password=..."
          },
          "PostgreSqlIdentityPersistence": {
            "ConnectionString": "Host=postgres;Port=5432;Database=elsa;Username=elsa;Password=..."
          },
          "Http": {
            "HttpActivityOptions": { "BaseUrl": "https://elsa.example.com" }
          }
        }
      }
    }
  },
  "Elsa": {
    "Cors": { "AllowedOrigins": ["https://studio.example.com"] }
  }
}

Handling secrets

The config file contains a signing key and database passwords. Options, roughly worst to best:

Approach Verdict
-e flags on the command line ❌ Lands in shell history and docker inspect
Committed config.json ❌ Never
Mounted file, gitignored, chmod 600 ⚠️ Acceptable for small deployments
Docker secrets / Kubernetes Secrets mounted as files ✅
External secret manager (Vault, AWS/Azure) injected at deploy ✅ Best

A workable hybrid: keep non-secret settings in the mounted config.json, and inject only the secrets as environment variables from your secret store — environment variables have the higher precedence, so they override cleanly.


Multiple shells

CShells can run several isolated Elsa instances in one host, each with its own database, identity and features — the basis for multi-tenancy.

{
  "CShells": {
    "Shells": {
      "Default": { "Features": { "…": {} } },
      "TenantA":  { "Features": { "…": {} } }
    }
  }
}

Each shell is configured independently and can use entirely different features. If you're only running one instance, ignore this and use Default.


Next: Persistence and Databases · Extending with Nuplane · Production Checklist

Clone this wiki locally