---
title: SQL schema operations
editUrl: https://github.com/Cratis/Chronicle/edit/main/Documentation/hosting/configuration/sql-schema-operations.md
description: Provision SQL schemas before starting Chronicle and verify versioned schema readiness without joining the cluster.
lastUpdated: 2026-10-22T00:00:00.000Z
---

Run migrations as a deployment step when kernel replicas must start without applying versioned DDL. Use the same Chronicle Server build and storage configuration for the migration job and the replicas. These operations support PostgreSQL, SQL Server, and SQLite.

## Provision before starting replicas

Run the server executable once with `--migrate-only`:

```sh
./Cratis.Chronicle.Server --migrate-only \
  --event-store orders --namespace production
```

This process does not start Orleans, cluster membership, or HTTP listeners. It uses the configured **Schema** connection identity, creates missing databases and schemas only when `Storage.CreateIfMissing` permits it, and applies migrations under Chronicle's existing per-database schema locks.

The operation visits:

- The cluster database.
- The System event store and its Default namespace.
- Every registered event store and namespace, plus Default for each store.
- Additional stores supplied with repeatable `--event-store` flags. Repeatable `--namespace` flags apply to each explicitly supplied store.
- Namespace and jobs migrations, the event-log, system, outbox and shared named-tag tables, and existing sequence tables, including inboxes.
- Read-model databases, without creating arbitrary read-model tables.

Explicit stores and namespaces are registered so subsequent kernel boots can discover them. Re-running the operation is supported. Applied migration identifiers unknown to the current build are reported but do not cause verification to fail.

For containers, set `Cratis__Chronicle__Storage__SchemaAction=MigrateAndExit` instead of passing `--migrate-only`. The command-line action takes precedence over the configured action.

## Verify readiness

Run a read-only schema probe:

```sh
./Cratis.Chronicle.Server --verify-schema \
  --event-store orders --namespace production
```

Or set `Cratis__Chronicle__Storage__SchemaAction=VerifyAndExit`. Verification reads history and table metadata; it does not register stores, acquire schema migration locks, create databases or schemas, or apply migrations. It checks versioned EF schemas and the presence of well-known sequence tables, not future dynamically shaped read-model or constraint tables.

After a successful migration job, configure normal replicas with:

```sh
export Cratis__Chronicle__Storage__SchemaMode=Verify
./Cratis.Chronicle.Server
```

`SchemaMode` defaults to `Migrate`, preserving migration-on-access behavior. `Verify` disables database and schema creation regardless of `CreateIfMissing`. Before accepting traffic, startup checks the cluster, System event-store, Default namespace and jobs schemas. Later versioned-schema accesses also fail if migrations are pending, including jobs initialization. Dynamically shaped tables still use their existing per-table path; this is not a blanket DDL prohibition.

Do not set `SchemaAction` on normal replicas: any non-`None` action exits instead of running the server. MongoDB and InMemory schema actions are successful no-ops; normal startup warns when `SchemaMode=Verify` is configured for them.

## Exit codes

| Code | Meaning |
| --- | --- |
| `0` | Migration completed, verification is current, or storage has no versioned schema. |
| `2` | Verification found a missing database, schema, required table or pending migration. |
| `3` | A database schema lock could not be acquired before its timeout. |
| `4` | Configuration is invalid, or migration needs a database that creation policy does not permit Chronicle to create. |
| `1` | An unexpected error occurred. |

Structured logs identify the logical scope, database, pending migrations, elapsed time and final exit code. Correct the reported configuration or run the migration job before retrying; do not treat a timeout as success.

## Kubernetes deployment

Use a Job ahead of the replica rollout. The referenced secrets must contain the appropriate `Cratis__Chronicle__Storage__*` environment variables. Use the same pinned server image for both resources; the image below illustrates the configuration.

```yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: chronicle-migrate
spec:
  backoffLimit: 0
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: cratis/chronicle:latest
          args: ["--migrate-only", "--event-store", "orders", "--namespace", "production"]
          envFrom:
            - secretRef:
                name: chronicle-schema-storage
```

Wait for this Job to complete successfully before rolling out replicas. A failed or timed-out Job must block the rollout. Each server container then uses the runtime storage configuration and Verify policy:

```yaml
envFrom:
  - secretRef:
      name: chronicle-runtime-storage
env:
  - name: Cratis__Chronicle__Storage__SchemaMode
    value: Verify
  - name: Cratis__Chronicle__Storage__SchemaAction
    value: None
```

The runtime identity still needs permissions for Chronicle's dynamic per-table operations. In Verify mode, these tables are still created when first used:

- Event-sequence tables for sequences that did not exist when `--migrate-only` ran.
- Unique-constraint tables.
- Read-model tables, including the per-generation tables created when a projection is rebuilt.

Every other schema object comes from versioned migrations, which `--migrate-only` applies and Verify checks.

Replicas from the previous release keep running against the migrated schema during a rollout. They ignore migrations they do not know, so a rollout stays safe as long as each release only adds tables, columns and indexes. Keep schema connection resolution consistent with the [storage connection resolution configuration](/chronicle/hosting/configuration/storage-connection-resolution/).
