Repository navigation
Persistence and Databases
Where Elsa stores workflow definitions, execution state and identity data — and how to point it at a real database.
Elsa persists two things, configured independently:
| Store | Holds | Feature name pattern |
|---|---|---|
| Workflow persistence | Definitions, instances, execution state, bookmarks | <Provider>WorkflowPersistence |
| Identity persistence | Users, roles, permissions | <Provider>IdentityPersistence |
They can use different providers or different databases, though the same database is normal. Both must be configured — enabling only workflow persistence leaves your users in the default store.
Out of the box, SQLite at /data/elsa.db.
⚠️ Ephemeral unless you mount/data. With nothing mounted there the database file lives in the container's writable layer, sodocker rmdestroys every workflow you have built. Fine for a first look. Never for anything else.
docker run ... -v elsa-data:/data ghcr.io/valence-works/runtime-ce-server:latestChanged in the 2026-08 images. The default used to be
elsa.dbrelative to/app. If you were relying on that default and mounting a volume at/app, either move the mount to/dataor set the connection strings back explicitly. Anyone already using an absolute path, or an external database, is unaffected.
The connection strings, if you want to set them explicitly:
{
"CShells": {
"Shells": {
"Default": {
"Features": {
"SqliteWorkflowPersistence": { "ConnectionString": "Data Source=/data/elsa.db" },
"SqliteIdentityPersistence": { "ConnectionString": "Data Source=/data/elsa.db" }
}
}
}
}
}docker run -d -p 8080:8080 \
-v elsa-data:/data \
-v "$(pwd)/config.json:/config/config.json:ro" \
ghcr.io/valence-works/runtime-ce-combined:latestSQLite is single-writer. It cannot back more than one replica, and it will become a bottleneck under concurrent workflow execution. Use it for development and single-instance low-volume cases only.
The best-supported production option.
1. Load the provider package via Nuplane — the base image doesn't ship it. See Extending with Nuplane:
{
"Nuplane": {
"Setup": {
"Feeds": [
{
"Name": "elsa-feed",
"ServiceIndex": "https://f.feedz.io/elsa-workflows/elsa-3/nuget/index.json",
"IncludePatterns": ["Elsa.Persistence.EFCore.PostgreSql [3.8.0-preview,)"]
}
]
}
}
}2. Enable the features:
{
"CShells": {
"Shells": {
"Default": {
"Features": {
"PostgreSqlWorkflowPersistence": {
"ConnectionString": "Host=postgres;Port=5432;Database=elsa;Username=elsa;Password=..."
},
"PostgreSqlIdentityPersistence": {
"ConnectionString": "Host=postgres;Port=5432;Database=elsa;Username=elsa;Password=..."
}
}
}
}
}
}The host is resolved from inside the container — a Docker Compose service name like postgres
is correct; localhost refers to the container itself.
Schema migrations are applied automatically at startup, so the user needs DDL permission on first run and after upgrades.
Same two steps — load the package, enable the feature. Feature names follow the provider:
| Database | Workflow feature | Identity feature |
|---|---|---|
| PostgreSQL | PostgreSqlWorkflowPersistence |
PostgreSqlIdentityPersistence |
| SQL Server | SqlServerWorkflowPersistence |
SqlServerIdentityPersistence |
| MySQL | MySqlWorkflowPersistence |
MySqlIdentityPersistence |
| Oracle | OracleWorkflowPersistence |
OracleIdentityPersistence |
| SQLite | SqliteWorkflowPersistence |
SqliteIdentityPersistence |
| MongoDB | MongoWorkflowPersistence |
MongoIdentityPersistence |
Availability tracks the Elsa packages published for your version. If a feature doesn't activate, the package almost certainly isn't loaded — check startup logs for Nuplane resolution errors.
| Use when | Avoid when | |
|---|---|---|
| SQLite | Development, demos, single low-volume instance | More than one replica; concurrent execution |
| PostgreSQL | Default production choice | — |
| SQL Server | You're already a SQL Server shop | — |
| MySQL | Existing MySQL infrastructure | — |
| MongoDB | Existing Mongo infrastructure | You want relational querying over instances |
If you have no constraint, use PostgreSQL.
Workflow execution can open many concurrent connections — Elsa is chatty under load in a way typical CRUD applications are not. If you see connection exhaustion, raise the server's limit and the pool size together:
Host=postgres;Database=elsa;Username=elsa;Password=...;Maximum Pool Size=200
The development Compose stack for these images runs PostgreSQL with
-c 'max_connections=2000' for exactly this reason. Size yours to real concurrency rather than
copying that number.
Elsa's data is your workflow definitions and the in-flight state of every running instance. Losing it means losing work mid-execution, not just configuration.
- Back up on your database's normal schedule; nothing Elsa-specific is required.
- Always back up before an image upgrade. Migrations run automatically at startup and are not reversed by rolling the image back.
- Test a restore before you need one.
# PostgreSQL, from the Compose stack
docker compose exec postgres pg_dump -U elsa elsa > elsa-backup.sqlMultiple replicas need more than a shared database. Without distributed coordination, each instance independently fires scheduled workflows and processes triggers — the same workflow runs twice.
You need:
- A shared database (never SQLite)
- A message bus — RabbitMQ or Azure Service Bus
- Distributed runtime features enabled
Next: Extending with Nuplane · Production Checklist · Troubleshooting
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