Skip to content

Persistence and Databases

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

Persistence and Databases

Where Elsa stores workflow definitions, execution state and identity data — and how to point it at a real database.


Two separate stores

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.


Default: SQLite

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, so docker rm destroys 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:latest

Changed in the 2026-08 images. The default used to be elsa.db relative to /app. If you were relying on that default and mounting a volume at /app, either move the mount to /data or 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:latest

SQLite 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.


PostgreSQL

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.


Other providers

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.


Choosing

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.


Connection pooling

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.


Backups

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.sql

Scaling out

Multiple 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

See Extending with Nuplane.


Next: Extending with Nuplane · Production Checklist · Troubleshooting

Clone this wiki locally