---
title: "How to Use KurrentDB for Event Sourcing in C# on Azure"
description: "How to deploy KurrentDB (formerly EventStoreDB) to Azure Container Instances, then append and read events from C#, for hands-on event sourcing in .NET."
url: https://nestenius.se/net/how-to-use-kurrentdb-for-event-sourcing-in-c-on-azure/
date: 2025-05-13
author: "Tore Nestenius"
category: ".NET"
tags: ["azure"]
site: https://nestenius.se/
---

# How to Use KurrentDB for Event Sourcing in C# on Azure

In this blog post, you will learn how to deploy a test instance of [KurrentDB](https://www.kurrent.io/) to Azure and access it from a console application in .NET.

## Why did I write this blog post?

While updating my [DDD, CQRS, and Event Sourcing in .NET](https://tn-data.se/courses/ddd-cqrs-and-event-sourcing-in-net/) training course to the latest version of .NET and C#, I also decided to switch from EventStoreDB to [KurrentDB](https://www.kurrent.io/) for the course’s hands-on exercises.  
Previously, we ran EventStoreDB locally, but during our corporate training sessions, we found that installing software onto student machines often gets restricted. To work around this, I chose to host KurrentDB in Azure, with a separate instance running for each student.  

As usual, I thought to share my learning with you! This blog post walks through how I deployed KurrentDB to Azure and connected to it from C#.

## What is KurrentDB?

**KurrentDB** is the new name for **EventStoreDB**, one of the first purpose-built databases for event sourcing. It was created by Greg Young, who also introduced the [Command and Query Responsibility Segregation (CQRS)](https://cqrs.nu) pattern.

KurrentDB stores every change to your data as an event. Instead of just saving the latest state, it records what happened, when, and why. This approach lets you rebuild the full history of your system, create audit logs, replay behavior, or build read models that are tailored for specific needs.

The KurrentDB database is open-source, written in C#, and available on [GitHub](https://github.com/kurrent-io/KurrentDB). The source code is well written and makes for an interesting codebase to review and explore. I find that reading and exploring real-world projects is a great way to pick up new techniques and ideas.

## Is KurrentDB a new database?

No, it was originally released in 2012 and is one of the few purpose-built event store databases available. It has had many years to mature and evolve. If you're curious about how it works internally, check out this talk by Gregory Young: [How an EventStore actually works](https://www.youtube.com/watch?v=YUjO1wM0PZM).

## What is Event Sourcing?

Event sourcing is a way of storing data by recording each change as an event. Instead of saving the latest state, you store the full history of everything that happened. This makes it easy to rebuild states, trace changes, and support things like auditing and custom views.  

For example, in a shopping cart, you can store each action as it happens:

![An event-sourced shopping-cart example](https://nestenius.se/_astro/kurrentdb-shopping-cart-event-stream-versions.BtU1NIAS_3giLR.webp)

To get to the current state, you would load all events and apply them one by one to the shopping cart. A big benefit of event sourcing, is that it makes it much easier to understand how and why something changed within a system, which is great for troubleshooting.

## Deploying KurrentDB to Azure

There are several ways to host KurrentDB in Azure, but my requirements were:

- Each student should get their own dedicated instance.
- It must be easy to deploy and clean up using a PowerShell script.
- No need to deal with https (TLS) certificates.
- It should be cost-effective.

Given these needs, I decided to use [Azure Container Instances](https://learn.microsoft.com/en-us/azure/container-instances/?WT.mc_id=linkedin&sharingId=MVP_427602). KurrentDB is available as a container image, and you can find the details in their [installation guide](https://docs.kurrent.io/server/v25.0/quick-start/installation).

Azure Container Instances are suitable for testing KurrentDB, but should not be used for production. ACI enforces certain restrictions that limit some features KurrentDB relies on to run properly.

## KurrentDB and the network

Before we deploy KurrentDB, we need to understand its network configuration. By default, KurrentDB listens to two network ports:

### **Port 1113**

KurrentDB uses **port 1113** for internal TCP traffic between cluster nodes, such as replication and gossip.

![Four KurrentDB nodes communicating over port 1113.](https://nestenius.se/_astro/kurrentdb-cluster-nodes-port-1113-replication.DNDxvQdc_Z42KyV.webp)

In our case, since we're running a single-node setup, we don't need to worry about port **1113**. This port is used only for internal node-to-node communication and should never be exposed to the public internet or client applications.

### Port 2113

KurrentDB uses by default port **2113** for client communication over HTTP or HTTPS.

![How a client talks to KurrentDB over port 2113](https://nestenius.se/_astro/client-kurrentdb-port-2113-connection-diagram.D7B8XBNH_Z28yspj.webp)

In this example, we expose port **2113** to the public internet. To simplify setup and avoid dealing with TLS certificates, we run KurrentDB in insecure mode by setting the **KURRENTDB\_INSECURE** environment variable to true. This is fine for testing and development, but it should never be used in production.

## Deploying KurrentDB to Azure Container Instances

For my first attempt at deploying KurrentDB as an Azure Container Instance, I used the following PowerShell script:

```powershell
$containerImage = "docker.kurrent.io/kurrent-latest/kurrentdb:latest"
$ResourceGroupName = "rg-kurrenteventstore"
$containerName = "kurrentdb-attempt1"
$Location = "swedencentral"

# Create resource group if it doesn't exist
az group create --name $ResourceGroupName `
                --location $Location `

# Create Azure Container Instance
az container create `
	--resource-group $ResourceGroupName `
	--name $containerName `
	--image $containerImage `
	--ports 2113 `
	--environment-variables "KURRENTDB_INSECURE=true" `
	--cpu 1 `
	--memory 1 `
	--os-type Linux `
	--location $Location `
	--restart-policy Never `
	--dns-name-label "$containerName-$(Get-Random)" `
	--query "ipAddress.fqdn"
```

After running the script, we had a working KurrentDB instance deployed to the public internet, accessible on port 2113.

When navigating to the URL in your browser, you’ll see KurrentDB’s built-in web interface. This gives you a quick way to check that the instance is up and running, and allows you to explore some basic information and diagnostics like below.

![The KurrentDB built-in admin user-interface.](https://nestenius.se/_astro/kurrentdb-admin-ui-cluster-status-dashboard.Y5d0EFMH_Z1p0WgI.webp)

## Deploying multiple instances of KurrentDB

Being able to deploy an instance to Azure is awesome. However, I need a script to deploy multiple instances to Azure.  

The modified script eventually became this:

```powershell
$containerImage = "docker.kurrent.io/kurrent-latest/kurrentdb:latest"
$ResourceGroupName = "rg-kurrenteventstore2"
$Location = "swedencentral"

$instanceCount = 2

# Create resource group if it doesn't exist
az group create --name $ResourceGroupName `
                --location $Location `

$urls = @()

for ($i = 1; $i -le $InstanceCount; $i++)
{
    $containerName = "kurrentdb-$i"

	# Create Azure Container Instance
	$fqdn = az container create `
		--resource-group $ResourceGroupName `
		--name $containerName `
		--image $containerImage `
		--ports 2113 `
		--environment-variables "KURRENTDB_INSECURE=true" `
		--cpu 1 `
		--memory 1 `
		--os-type Linux `
		--location $Location `
		--restart-policy Never `
		--dns-name-label "$containerName-$(Get-Random)" `
		--query "ipAddress.fqdn" `
		--output tsv

	$urls = $urls + "${fqdn}:2113"

}

Write-Output "`nAll instance URLs:"
$urls
```

## Kurrent Navigator

To manage your KurrentDB instance, you can use [Kurrent Navigator](https://navigator.kurrent.io). This is a standalone desktop app with a clean, modern interface for browsing events, inspecting streams, working with projections, and more. It runs on Windows, macOS, and Linux, and it is useful for both daily administration and troubleshooting.

![KurrentDB Navigator desktop application](https://nestenius.se/_astro/kurrent-navigator-dashboard-azure-container-instance.C4LPGL0h_ryb8x.webp)

## Sending events to KurrentDB from C#

For .NET developers, KurrentDB offers an official [.NET client,](https://github.com/kurrent-io/KurrentDB-Client-Dotnet) which is also available as a **NuGet** package: [KurrentDB.Client](https://www.nuget.org/packages/KurrentDB.Client). It supports both .NET Framework 4.8 and modern .NET versions. Details about how to use this package can be found in the [getting started documentation](https://docs.kurrent.io/clients/dotnet/v1.0/getting-started).

The code below creates four events and sends it to KurrentDB:

![the KurrentDB.Client NuGet package](https://nestenius.se/_astro/kurrentdb-client-nuget-package-prerelease.BSLR_5W6_C6JNm.webp)

```csharp
using KurrentDB.Client;
using KurrentDB.Client.Core.Serialization;

var server = "[ServerAddress]";
var settings = KurrentDBClientSettings.Create($"esdb://{server}?tls=false");
settings.OperationOptions.ThrowOnAppendFailure = false;

await using var client = new KurrentDBClient(settings);

var events = new List<object>
{
    new NewOrderCreated
    {
        Id = Guid.NewGuid(),
        CustomerID = Guid.NewGuid(),
        SalesPersonID = Guid.NewGuid(),
        SalesPersonName = "John Doe"
    },
    new OrderCancelled
    {
        Id = Guid.NewGuid(),
        Reason = "Customer changed their mind"
    },
    new ProductAddedToOrder
    {
        Id = Guid.NewGuid(),
        ProductId = Guid.NewGuid(),
        ProductName = "Laptop",
        Quantity = 1,
        PricePerUnit = 1200.00m
    },
    new ProductRemovedFromOrder
    {
        Id = Guid.NewGuid(),
        ProductId = Guid.NewGuid()
    }
};

var streamName = "order-12345";

await AppendEventsAsync(client, streamName, events);

async Task AppendEventsAsync(KurrentDBClient client,
                              string stream,
                              List<object> events)
{
    // Map the events to KurrentDB message type.
    var messages = events
        .Select(e => Message.From(data: e,
                                  messageId: Uuid.NewUuid())).ToList();

    await client.AppendToStreamAsync(streamName: stream,
                                      messages: messages);
}

// Event definitions
public class NewOrderCreated
{
    public Guid Id { get; set; }
    public Guid CustomerID { get; set; }
    public Guid SalesPersonID { get; set; }
    public string? SalesPersonName { get; set; }
}

public class OrderCancelled
{
    public Guid Id { get; set; }
    public string? Reason { get; set; }
}

public class ProductAddedToOrder
{
    public Guid Id { get; set; }
    public Guid ProductId { get; set; }
    public string? ProductName { get; set; }
    public int Quantity { get; set; }
    public decimal PricePerUnit { get; set; }
}

public class ProductRemovedFromOrder
{
    public Guid Id { get; set; }
    public Guid ProductId { get; set; }
}
```

After sending the events, we can open Kurrent Navigator to view the events stored in the database:

![Viewing the four events in KurrentDB Navigator](https://nestenius.se/_astro/kurrentdb-stream-events-order-list.DgGRDZGa_Z14VxYS.webp)

## Reading events from KurrentDB

Reading from KurrentDB is straightforward. You just need to create a client and specify the stream you want to read from, like this:

```csharp
using KurrentDB.Client;
using System.Text;
using System.Text.Json;

Console.WriteLine("Reading events from KurrentDB...");

var server = "kurrentdb-2-1927695997.swedencentral.azurecontainer.io";
var settings = KurrentDBClientSettings.Create($"esdb://{server}?tls=false");
settings.OperationOptions.ThrowOnAppendFailure = false;

await using var client = new KurrentDBClient(settings);

var streamName = "order-12345";
var events = await client
    .ReadStreamAsync(Direction.Forwards, streamName, StreamPosition.Start)
    .ToListAsync();

foreach (var ev in events)
{
    var json = Encoding.UTF8.GetString(ev.Event.Data.Span);
    Console.WriteLine($"{ev.Event.EventNumber} - {json}");
}
```

As an output, we will get:

```text
Reading events from KurrentDB...
0 - {"id":"0d0545fe-5242-4c06-8ad5-573cd4a89bd1",
     "customerID":"264c704d-1754-4e56-9f2a-0f8b4202a045",
     "salesPersonID":"3fa6e8f1-c114-4eea-81e0-eefa9fe2a4f4",
     "salesPersonName":"John Doe"}

1 - {"id":"b3f37164-94c6-426d-bfe2-b8a6063f37ac",
     "reason":"Customer changed their mind"}

2 - {"id":"4b391bb9-b288-4dc9-9f19-ad65dc577cad",
     "productId":"4bcb44e5-6bc6-42dd-b47c-18496a4d5380",
     "productName":"Laptop","quantity":1,"pricePerUnit":1200.00}

3 - {"id":"6f2fa70f-12ec-46e7-b715-aad3f559be32",
     "productId":"bb5147e0-5fbb-4339-88ce-1249fb002d0b"}
```

## Summary

Writing this blog post was a great way to explore everything new from the team behind KurrentDB. I especially enjoyed trying out the Kurrent Navigator, which was completely new to me. Hosting KurrentDB in Azure using Container Instances turned out to be a smooth experience. Just keep in mind that if you're coming from EventStoreDB, some names and behaviors have changed, which caused a few bumps along the way.

## Curious about DDD, CQRS, and Event Sourcing?

If you're interested in learning more about these concepts, I offer several training classes, including [DDD, CQRS and Event Sourcing in .NET](https://tn-data.se/courses/ddd-cqrs-and-event-sourcing-in-net/) and [Modern Application Architecture](https://tn-data.se/courses/modern-application-architecture/). You can find the full list of workshops on my [training page](https://tn-data.se/courses/). I also offer coaching if you'd like help working through architectural challenges.  

The CQRS and Event Sourcing framework that I use in my training can be found at [https://cqrs.nu](https://cqrs.nu). It's a project I was part of creating. There, you'll also find an example of how to do BDD-style testing of Event Sourced systems.

## Resources

- [Understanding Eventsourcing book, by Martin Diliger](https://eventsourcingbook.com/)
- [Getting started with CQRS](https://cqrs.nu)
- [Greg Young -  A Decade of DDD, CQRS, Event Sourcing](https://www.youtube.com/watch?v=LDW0QWie21s)

## Related posts

- [Introducing the Cloud Debugger for Azure](https://nestenius.se/azure/introducing-the-cloud-debugger-for-azure/)
- [Running Docker in an Azure Windows Virtual Machine – Not so fast!](https://nestenius.se/azure/running-docker-in-an-azure-windows-virtual-machine-not-so-fast/)
- [BearerToken: The new Authentication handler in ASP.NET Core 8](https://nestenius.se/net/bearertoken-the-new-authentication-handler-in-net-8/)
- [How I built my own Sega Mega Drive hardware dev kit from scratch](https://nestenius.se/hardware/how-i-built-my-own-sega-mega-drive-hardware-dev-kit-from-scratch/)
- [IdentityServer in Docker Container](https://nestenius.se/net/identityserver-in-docker-containers-part-1/)
