Skip to content

Run Chronicle locally

Chronicle runs as a small kernel — a Docker image your application talks to over the network. Before a single line of your host code can append an event, that kernel has to be up and reachable. This is the one page that covers getting it there, so the console, worker service, and ASP.NET Core guides can all point here instead of repeating it.

You only need to do this once per machine. Leave it running in the background and come back to your code.

The latest-development image bundles MongoDB, so there’s nothing else to install or wire up — one command and the kernel is running:

Terminal window
docker run -d -p 27017:27017 -p 8080:8080 -p 35000:35000 cratis/chronicle:latest-development

Three ports, three jobs:

PortWhat it is
35000The kernel endpoint your app connects to (chronicle://localhost:35000).
8080The Chronicle workbench — a web UI for browsing your event store.
27017The bundled MongoDB, where projections write their read models.

That’s everything most local work needs. If you’d rather run against a database you already have — or a different engine entirely — read on; otherwise skip to picking a host.

The latest-development-slim image leaves the database out, so you point Chronicle at one you run yourself. Two environment variables tell it where to go:

  • Cratis__Chronicle__Storage__Type
  • Cratis__Chronicle__Storage__ConnectionDetails

The Compose files below bring up the kernel and a database together. Pick the one for your engine.

Chronicle uses MongoDB transactions and change streams, so MongoDB must run as a replica set (or a sharded cluster) — a standalone mongod won’t do. This file initializes a single-node replica set for local development:

services:
chronicle:
image: cratis/chronicle:latest-development-slim
depends_on:
- mongodb
- mongodb-init
environment:
- Cratis__Chronicle__Storage__Type=MongoDB
- Cratis__Chronicle__Storage__ConnectionDetails=mongodb://mongodb:27017/?directConnection=true
ports:
- 8080:8080
- 35000:35000
mongodb:
image: mongo:8
command: ["mongod", "--replSet", "rs0", "--bind_ip_all"]
ports:
- 27017:27017
mongodb-init:
image: mongo:8
depends_on:
- mongodb
restart: "no"
command:
- /bin/bash
- -lc
- |
until mongosh --host mongodb --quiet --eval "db.adminCommand('ping')" >/dev/null 2>&1; do
sleep 1
done
mongosh --host mongodb --quiet --eval "
try {
rs.status();
} catch (e) {
rs.initiate({
_id: 'rs0',
members: [{ _id: 0, host: 'localhost:27017' }]
});
}"

Why this setup:

  • host: 'localhost:27017' makes the replica set topology usable from host tools (for example mongosh and Compass) when they connect to mongodb://localhost:27017/?replicaSet=rs0.
  • Chronicle still reaches MongoDB over the Docker network (mongodb:27017) and uses directConnection=true to avoid following the advertised host back to localhost inside the Chronicle container.
  • directConnection=true does not disable transactions; transactions still work because MongoDB is running as a replica set.
  • If your existing data volume was initialized with a different replica-set host, run docker compose down -v (or wipe the MongoDB data volume) before starting again so rs.initiate() can apply the new host.
services:
chronicle:
image: cratis/chronicle:latest-development-slim
depends_on:
- postgres
environment:
- Cratis__Chronicle__Storage__Type=PostgreSql
- Cratis__Chronicle__Storage__ConnectionDetails=Host=postgres;Port=5432;Database=chronicle;Username=postgres;Password=postgres
ports:
- 8080:8080
- 35000:35000
postgres:
image: postgres:16
environment:
- POSTGRES_DB=chronicle
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
ports:
- 5432:5432
services:
chronicle:
image: cratis/chronicle:latest-development-slim
depends_on:
- sqlserver
environment:
- Cratis__Chronicle__Storage__Type=MsSql
- Cratis__Chronicle__Storage__ConnectionDetails=Server=sqlserver,1433;Database=Chronicle;User Id=sa;Password=Your_strong_password123!;TrustServerCertificate=True;Encrypt=False
ports:
- 8080:8080
- 35000:35000
sqlserver:
image: mcr.microsoft.com/mssql/server:2025-latest
environment:
- ACCEPT_EULA=Y
- MSSQL_SA_PASSWORD=Your_strong_password123!
ports:
- 1433:1433
services:
chronicle:
image: cratis/chronicle:latest-development-slim
environment:
- Cratis__Chronicle__Storage__Type=Sqlite
- Cratis__Chronicle__Storage__ConnectionDetails=Data Source=/data/chronicle.db
volumes:
- chronicle-data:/data
ports:
- 8080:8080
- 35000:35000
volumes:
chronicle-data:

Bring any of these up the usual way, in the background:

Terminal window
docker compose up -d

If you want local observability while developing — logs, traces, and metrics — run the kernel alongside the Aspire dashboard and set OTEL_EXPORTER_OTLP_ENDPOINT on the Chronicle container. Port 18888 is the dashboard UI in your browser; port 18889 is the OTLP receiver Chronicle exports to inside the Docker network:

services:
chronicle:
image: cratis/chronicle:latest-development
environment:
- OTEL_EXPORTER_OTLP_ENDPOINT=http://aspire-dashboard:18889
ports:
- 27017:27017
- 8080:8080
- 11111:11111
- 30000:30000
- 35000:35000
aspire-dashboard:
image: mcr.microsoft.com/dotnet/aspire-dashboard:latest
environment:
- DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=true
- ALLOW_UNSECURED_TRANSPORT=true
- DOTNET_ENVIRONMENT=Development
ports:
- 18888:18888

Whichever image you ran, it includes the Chronicle workbench — a web UI for poking at your event store. Open http://localhost:8080, pick an event store, and look at Sequences to watch events land in order. It’s the quickest way to confirm the kernel is up and your app is actually appending.

The kernel is running and listening on chronicle://localhost:35000. Now connect your app to it:

  • Just exploring? The Get started quickstart scaffolds a ready-to-run app from a template — the fastest way to see the whole loop.
  • Console — the bare-bones version, no DI container, every connection explicit.
  • Worker service — a background host for the reacting side of an event-sourced system.
  • ASP.NET Core — a web API that appends events straight from its endpoints.