Skip to content

Choose an application host model

Chronicle can feel like several things at once: a client library, a .NET host integration, and a kernel process your applications connect to. The trick is to separate two decisions that are both yours to make. First, which application host your code runs in — a console app, a worker service, or ASP.NET Core. Second, how you run the Chronicle kernel — locally in Docker, inside an Aspire AppHost, or as infrastructure your platform operates.

Pick the smallest level that answers the question you have today. You can move up the levels without changing your events, projections, reducers, or reactors. And by the end of this page you’ll also have a kernel running locally, ready for whichever host you picked.

The Chronicle kernel — run it your way

Your application host — pick a level

Arc application layer — commands, queries, proxies

Console app — explicit client

Worker service — generic host + DI

ASP.NET Core — web endpoints + DI

Local Docker / Compose

Aspire AppHost

Managed by your platform

The app host decides how much wiring you own

Section titled “The app host decides how much wiring you own”
LevelWhat you writeWhat Chronicle wiresUse it when
Consolenew ChronicleClient(...), GetEventStore(...), append events yourselfNothing hidden. You see the client and event store directly.You are learning, debugging a small reproduction, or writing a script.
Worker ServiceHost.CreateApplicationBuilder, AddCratisChronicle(...), a BackgroundServiceDI registration and artifact discovery for reactors, reducers, and projections.You process events, run background workflows, or keep derived state updated.
ASP.NET CoreWebApplication.CreateBuilder, AddCratisChronicle(...), UseCratisChronicle()DI registration, artifact discovery, and request-pipeline integration.You expose HTTP endpoints, controllers, or minimal APIs that append events.
Arc + ChronicleArc commands and queries; Chronicle-backed command return values and projectionsArc’s command/query pipeline plus Chronicle event appending, identity, tenancy, and read-model integration.You want a typed full-stack CQRS app with generated TypeScript proxies.

The domain artifacts stay the same across these levels. A BookRegistered event, a BookStatus projection, and a NotifyWaitingList reactor can start in a console sample and later run unchanged in a worker, web API, or Arc application.

The kernel has no opinion about where it lives. It’s a container plus the storage you point it at — so you and your team choose how to run it, and you can change that choice later without touching application code:

How you run itWhat it gives youUse it when
Local DockerOne kernel reachable at chronicle://localhost:35000, with the workbench at https://localhost:35000 on the same port.You need the fastest local feedback loop.
Docker ComposeChronicle and storage as named services in a local or CI topology.You want repeatable local infrastructure for a team or pipeline.
Aspire AppHostChronicle as an Aspire resource with endpoints and storage references wired into dependent projects.Your .NET solution already uses Aspire to compose services locally.
Production-managed kernelA Chronicle container, durable storage, TLS, secrets, health checks, and versioned deployment owned by your platform.The application should only know the Chronicle connection string and credentials.

There is no special “managed client” mode in your application. Managed simply means you’ve handed the kernel and its storage to whoever operates your infrastructure — Kubernetes, Docker, cloud services, or an internal platform team — and your app gets a connection string.

Whichever way you go in production, local development starts the same: with a kernel on your machine. Let’s get one running.

Before a single line of your host code can append an event, the kernel has to be up and reachable. 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 35000:35000 cratis/chronicle:latest-development

Two ports, two jobs — Chronicle now serves everything on one port, and it’s TLS:

PortWhat it is
35000The kernel endpoint your app connects to (chronicle://localhost:35000), and the Chronicle workbench — a web UI for browsing your event store — at https://localhost:35000.
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 jump to picking your host guide.

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 tab 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:
- 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.

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

Terminal window
docker compose up -d

Want local observability too — logs, traces, and metrics next to the kernel? The Docker Compose hosting guide adds the Aspire dashboard to the same topology.

Whichever image you ran, it includes the Chronicle workbench — a web UI for poking at your event store. Open https://localhost:35000 and log in with the development credentials (username Admin, password ChangeMeNow!). It’s HTTPS now — in development Chronicle serves the port with a self-signed certificate it generates on the fly, so your browser will warn you the first time; accept it to continue. 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.

If your solution composes services with Aspire, you don’t write Compose files at all — the database choice flows from the Aspire app model. The Cratis.Chronicle.Aspire package adds Chronicle as a resource in your AppHost:

Terminal window
dotnet add package Cratis.Chronicle.Aspire

For development, one line gives you the same batteries-included kernel as the docker run above — AddCratisChronicle() uses the development image with embedded MongoDB:

using Aspire.Hosting;
public static class GetStartedHostingBasicAppHost
{
public static void ConfigureAppHost(string[] args)
{
var builder = DistributedApplication.CreateBuilder(args);
var chronicle = builder.AddCratisChronicle();
// Reference any of your application's projects the same way -
// builder.AddProject<Projects.MyApi>("api") when Projects.MyApi is generated from your solution
builder.AddContainer("api", "my-org/my-api")
.WithReference(chronicle);
builder.Build().Run();
}
}

To choose a database, pass a configure callback. Aspire then switches to the slim image (no embedded MongoDB) and the With* method you pick sets the kernel’s storage type and connection string from the resource you hand it — the same two environment variables you saw in the Compose files, now derived from your app model instead of hand-written:

using Aspire.Hosting;
using Aspire.Hosting.ApplicationModel;
using Cratis.Chronicle.Aspire;
public static class GetStartedHostingMongoDatabase
{
public static IResourceBuilder<ChronicleResource> ConfigureAppHost(IDistributedApplicationBuilder builder)
{
var mongo = builder.AddConnectionString("chronicle-mongo");
return builder.AddCratisChronicle("chronicle", c => c.WithMongoDB(mongo));
}
}

mongo can be any resource with a connection string — a MongoDB Atlas connection string as shown, or a container added directly in the AppHost with builder.AddMongoDB("mongo").

WithReference(chronicle) hands your application projects a chronicle://host:port connection string pointing at the kernel’s gRPC endpoint — no hardcoded ports. The Aspire integration guide covers the rest: exposed endpoints, compliance key storage with HashiCorp Vault or Azure Key Vault, and the full AppHost walkthrough.

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.
If you are…Start withThen move to
Learning Chronicle from scratchGet started, then ConsoleTutorial
Adding events to an existing web APIRun Chronicle locally, then ASP.NET CoreProduction hosting
Building background event processorsWorker ServiceReactors and Reducers
Building a full-stack Cratis appArc + ChronicleBuild a full-stack feature
Preparing a team environmentDocker Compose or AspireProduction hosting and Data Protection Key Encryption

If you are unsure, use this rule: learn in a console app, ship user-facing endpoints in ASP.NET Core or Arc, run background work in a worker, and let your platform own the production kernel.