Repository navigation
Configuration
How settings are layered, how keys are shaped, and a reference of the ones you'll actually set.
Later wins:
-
appsettings.json— baked into the image -
appsettings.{Environment}.json— baked into the image -
/config/config.json— your mounted file ← recommended - 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 cshellsMount 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:latestvolumes:
- ./config.json:/config/config.json:roInclude only the keys you want to override — everything else stays at its default.
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
The segment is the shell name —
Default— not a number.✅ CShells__Shells__Default__Features__DefaultAdminUser__AdminUsername ❌ CShells__Shells__0__Features__DefaultAdminUser__AdminUsernameUsing
0doesn't error. It quietly defines a second shell called0, 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
| 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.
| 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.
| 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.
| 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.
| Key | Default | Notes |
|---|---|---|
ASPNETCORE_ENVIRONMENT |
Production |
|
ASPNETCORE_URLS |
http://+:8080 |
Rarely worth changing; remap the port instead |
See Persistence and Databases.
{
"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"] }
}
}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
|
|
| 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.
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
Valence Runtime — a product of Valence Works · Elsa Workflows is open source under the MIT License · Found an error in these docs? Open an issue.
Start here
Deploying
Going further
Commercial