Quickstart Worker Service
Pre-requisites
- .NET 8 or higher
- Docker Desktop or compatible
- MongoDB client (e.g. MongoDB Compass)
Objective
In this quickstart, you will set up Chronicle in a .NET worker service (generic host) application — without ASP.NET Core. Worker services are ideal for background processing: reacting to events, running scheduled jobs, or maintaining derived data in message-processing pipelines.
Docker
Chronicle is available as a Docker image.
Image Tags for Development
Use one of these development tags:
| Tag | Includes MongoDB | Typical Use |
|---|---|---|
cratis/chronicle:latest-development |
Yes | Fast local setup with no external database container |
cratis/chronicle:latest-development-slim |
No | Local setup with your own database container (MongoDB, PostgreSQL, SQL Server, or SQLite) |
Quick Start: Embedded MongoDB (Development Image)
The development image bundles MongoDB, so no separate database setup is required.
docker run -d -p 27017:27017 -p 8080:8080 -p 35000:35000 cratis/chronicle:latest-development
Quick Start: External Database (Development Slim Image)
Use latest-development-slim and configure Chronicle storage through Chronicle options environment variables:
Cratis__Chronicle__Storage__TypeCratis__Chronicle__Storage__ConnectionDetails
MongoDB with Docker Compose
Chronicle uses MongoDB transactions (and change streams), so MongoDB must run as a replica set or a sharded cluster. The following example 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 examplemongoshand Compass) when they connect tomongodb://localhost:27017/?replicaSet=rs0.- Chronicle still reaches MongoDB over the Docker network (
mongodb:27017) and usesdirectConnection=trueto avoid following the advertised host back tolocalhostinside the Chronicle container. directConnection=truedoes 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 sors.initiate()can apply the new host.
PostgreSQL with Docker Compose
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
SQL Server with Docker Compose
Warning
The SQL Server credentials in this example are for local development only. For production, use secure credentials and manage secrets through Docker secrets, environment files, or an external secret manager.
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:2022-latest
environment:
- ACCEPT_EULA=Y
- MSSQL_SA_PASSWORD=Your_strong_password123!
ports:
- 1433:1433
SQLite with Docker Compose
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:
Optional: Add Aspire Dashboard for Logs, Traces, and Metrics
If you want local observability while developing, run Chronicle with the Aspire Dashboard and set OTEL_EXPORTER_OTLP_ENDPOINT on the Chronicle container. Use port 18888 for the dashboard UI in your browser, and port 18889 as the OTLP receiver that Chronicle exports telemetry 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
Setup project
Start by creating a folder for your project and then create a .NET worker service project inside this folder:
dotnet new worker
Add a reference to the Chronicle client package:
dotnet add package Cratis.Chronicle
Note: For worker services you only need the base
Cratis.Chroniclepackage — theCratis.Chronicle.AspNetCorepackage is for web applications only.
Host setup
Open your Program.cs and configure Chronicle using AddCratisChronicle on the IHostApplicationBuilder:
var builder = Host.CreateApplicationBuilder(args);
builder.AddCratisChronicle(options =>
{
options.EventStore = "MyWorkerApp";
});
builder.Services.AddHostedService<Worker>();
var host = builder.Build();
await host.RunAsync();
The AddCratisChronicle call:
- Registers
IChronicleClient,IEventStore, and all the event store components (IEventLog,IReactors,IReducers,IProjections,IReadModels) in the DI container. - Automatically discovers and registers all artifacts (Reactors, Reducers, Projections) from the loaded assemblies.
- Reads configuration from the
Cratis:Chroniclesection ofappsettings.json(connection string, timeouts, etc.).
Configuration
Chronicle reads its connection settings from appsettings.json. Add the following to yours:
{
"Cratis": {
"Chronicle": {
"ConnectionString": "chronicle://localhost:35000",
"EventStore": "MyWorkerApp"
}
}
}
You can also configure the event store name inline (as shown above) and keep the connection string in configuration.
Worker implementation
Inject IEventStore or any of the event store sub-services into your hosted service:
public class Worker(IEventStore eventStore) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Connect to Chronicle and start processing
await eventStore.Connection.Connect();
// Keep running until the host shuts down
await Task.Delay(Timeout.Infinite, stoppingToken);
}
}
Structural dependencies
For custom identity providers, correlation ID accessors, or namespace resolvers, use the configure callback:
builder.AddCratisChronicle(
configureOptions: options => options.EventStore = "MyWorkerApp",
configure: b => b
.WithIdentityProvider(new MyServiceIdentityProvider())
.WithNamespaceResolver(new MyTenantResolver()));
See Structural Dependencies for a full list of configurable dependencies.
Namespace resolution
By default the worker uses the default namespace for all operations. To support multi-tenant scenarios, provide a custom IEventStoreNamespaceResolver via ChronicleClientOptions:
builder.AddCratisChronicle(options =>
{
options.EventStore = "MyWorkerApp";
options.EventStoreNamespaceResolverType = typeof(MyTenantNamespaceResolver);
});
Or pass a resolver instance directly through the builder:
builder.AddCratisChronicle(
configureOptions: options => options.EventStore = "MyWorkerApp",
configure: b => b.WithNamespaceResolver(new MyTenantNamespaceResolver(config)));
See Namespace resolution for details on built-in resolvers.
Services
Chronicle uses the DI container to create instances of Reactors, Reducers, and Projections it discovers. Register them as services in Program.cs:
builder.Services.AddTransient<MyReactor>();
builder.Services.AddTransient<MyReducer>();
For larger solutions, the Cratis Fundamentals convention-based registration helpers keep this manageable:
builder.Services
.AddBindingsByConvention()
.AddSelfBindings();