Dotnet-skills akka-net-aspire-configuration
Configure Akka.NET with .NET Aspire for local development and production deployments. Covers actor system setup, clustering, persistence, Akka.Management integration, and Aspire orchestration patterns.
install
source · Clone the upstream repo
git clone https://github.com/Aaronontheweb/dotnet-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Aaronontheweb/dotnet-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/akka-aspire-configuration" ~/.claude/skills/aaronontheweb-dotnet-skills-akka-net-aspire-configuration && rm -rf "$T"
manifest:
skills/akka-aspire-configuration/SKILL.mdsource content
Configuring Akka.NET with .NET Aspire
When to Use This Skill
Use this skill when:
- Setting up a new Akka.NET project with .NET Aspire orchestration
- Configuring Akka.Cluster with cluster bootstrapping and discovery
- Integrating Akka.Persistence with SQL Server
- Setting up Akka.Management for cluster management
- Configuring multi-replica actor systems in local development
- Deploying Akka.NET applications to Kubernetes with Aspire
Related Skills
- Deep dive into Akka.Management, Cluster Bootstrap, and discovery providers (Kubernetes, Azure, Config)akka-net-management
- IValidateOptions patterns for configuration validationmicrosoft-extensions-configuration
- Cluster/local mode abstractions for testable actor systemsakka-net-best-practices
- Testing Aspire applications with real infrastructureaspire-integration-testing
Core Principles
- Configuration via Microsoft.Extensions.Configuration - Use strongly-typed settings classes bound from appsettings.json (see
skill)microsoft-extensions-configuration - Akka.Hosting for DI Integration - Use the Akka.Hosting library for seamless ASP.NET Core integration
- Aspire for Orchestration - Let Aspire manage service dependencies, networking, and environment configuration
- Health Checks - Always configure health checks for clustering, persistence, and readiness
- Separate Concerns - Keep actor definitions, configuration, and Aspire orchestration in separate layers
- Validate Configuration at Startup - Use
andIValidateOptions<T>
to fail fast on misconfiguration.ValidateOnStart()
Project Structure
YourSolution/ ├── src/ │ ├── YourApp.Actors/ # Actor definitions and business logic │ │ ├── YourActor.cs │ │ └── YourApp.Actors.csproj │ ├── YourApp/ # ASP.NET Core web application │ │ ├── Config/ │ │ │ ├── AkkaConfiguration.cs # Akka setup extension methods │ │ │ └── AkkaSettings.cs # Configuration model │ │ ├── Program.cs │ │ ├── appsettings.json │ │ └── YourApp.csproj │ └── YourApp.AppHost/ # Aspire orchestration │ ├── Program.cs │ ├── AkkaManagementExtensions.cs │ └── YourApp.AppHost.csproj
Required NuGet Packages
For Actor Project (YourApp.Actors.csproj)
<ItemGroup> <PackageReference Include="Akka.Cluster.Hosting" /> <PackageReference Include="Akka.Streams" /> </ItemGroup>
For Web Application (YourApp.csproj)
<ItemGroup> <PackageReference Include="Akka.Hosting" /> <PackageReference Include="Akka.Cluster.Hosting" /> <PackageReference Include="Akka.Persistence.Sql.Hosting" /> <PackageReference Include="Akka.Management" /> <PackageReference Include="Akka.Management.Cluster.Bootstrap" /> <PackageReference Include="Akka.Discovery.KubernetesApi" /> <PackageReference Include="Akka.Discovery.Azure" /> <PackageReference Include="Akka.Discovery.Config.Hosting" /> <PackageReference Include="Petabridge.Cmd.Host" /> <PackageReference Include="Petabridge.Cmd.Cluster" /> </ItemGroup>
For AppHost (YourApp.AppHost.csproj)
<Sdk Name="Aspire.AppHost.Sdk" Version="$(AspireVersion)" /> <ItemGroup> <PackageReference Include="Aspire.Hosting.AppHost" /> <PackageReference Include="Aspire.Hosting.Azure.Storage" /> <PackageReference Include="Aspire.Hosting.SqlServer" /> </ItemGroup>
Configuration Model (AkkaSettings.cs)
Create a strongly-typed configuration class:
using System.Net; using System.Security.Cryptography.X509Certificates; using Akka.Cluster.Hosting; using Akka.Remote.Hosting; using Petabridge.Cmd.Host; namespace YourApp.Config; public class AkkaSettings { public string ActorSystemName { get; set; } = "YourSystem"; public bool LogConfigOnStart { get; set; } = false; public RemoteOptions RemoteOptions { get; set; } = new() { PublicHostName = Dns.GetHostName(), HostName = "0.0.0.0", Port = 8081 }; public ClusterOptions ClusterOptions { get; set; } = new() { SeedNodes = [$"akka.tcp://YourSystem@{Dns.GetHostName()}:8081"], Roles = ["your-role"] }; public ShardOptions ShardOptions { get; set; } = new(); public AkkaManagementOptions? AkkaManagementOptions { get; set; } public PetabridgeCmdOptions PbmOptions { get; set; } = new() { Host = "0.0.0.0", Port = 9110 }; public TlsSettings? TlsSettings { get; set; } } public class TlsSettings { public bool Enabled { get; set; } = false; public string? CertificatePath { get; set; } public string? CertificatePassword { get; set; } public bool ValidateCertificates { get; set; } = true; public X509Certificate2? LoadCertificate() { if (string.IsNullOrWhiteSpace(CertificatePath)) return null; if (!File.Exists(CertificatePath)) throw new FileNotFoundException($"Certificate file not found at: {CertificatePath}"); return !string.IsNullOrWhiteSpace(CertificatePassword) ? X509CertificateLoader.LoadPkcs12FromFile(CertificatePath, CertificatePassword) : X509CertificateLoader.LoadCertificateFromFile(CertificatePath); } } public class AkkaManagementOptions { public bool Enabled { get; set; } public string? Hostname { get; set; } public int Port { get; set; } = 8558; public string ServiceName { get; set; } = "your-service"; public string PortName { get; set; } = "management"; public int RequiredContactPointsNr { get; set; } = 1; public bool FilterOnFallbackPort { get; set; } = true; public DiscoveryMethod DiscoveryMethod { get; set; } = DiscoveryMethod.Config; } public enum DiscoveryMethod { Config, Kubernetes, AzureTableStorage, AwsEcsTagBased, AwsEc2TagBased }
Akka Configuration Extension Methods (AkkaConfiguration.cs)
using Akka.Cluster.Hosting; using Akka.Discovery.Azure; using Akka.Discovery.Config.Hosting; using Akka.Discovery.KubernetesApi; using Akka.Hosting; using Akka.Management; using Akka.Management.Cluster.Bootstrap; using Akka.Persistence.Sql.Config; using Akka.Persistence.Sql.Hosting; using Akka.Remote.Hosting; using LinqToDB; namespace YourApp.Config; public static class AkkaConfiguration { public static IServiceCollection ConfigureAkka( this IServiceCollection services, IConfiguration configuration, Action<AkkaConfigurationBuilder, IServiceProvider> additionalConfig) { var akkaSettings = BindAkkaSettings(services, configuration); var connectionString = configuration.GetConnectionString("DefaultConnection"); if (connectionString is null) throw new Exception("DefaultConnection ConnectionString is missing"); const string roleName = "your-role"; services.AddAkka(akkaSettings.ActorSystemName, (builder, provider) => { builder.ConfigureNetwork(provider) .WithAkkaClusterReadinessCheck() .WithActorSystemLivenessCheck() .WithSqlPersistence( connectionString: connectionString, providerName: ProviderName.SqlServer2022, databaseMapping: DatabaseMapping.SqlServer, tagStorageMode: TagMode.TagTable, deleteCompatibilityMode: true, useWriterUuidColumn: true, autoInitialize: true, journalBuilder: journalBuilder => { journalBuilder.WithHealthCheck(name: "Akka.Persistence.Sql.Journal[default]"); }, snapshotBuilder: snapshotBuilder => { snapshotBuilder.WithHealthCheck(name: "Akka.Persistence.Sql.SnapshotStore[default]"); }); // Add your actors here // Example: builder.WithActors((system, registry) => { ... }); additionalConfig(builder, provider); }); return services; } public static AkkaSettings BindAkkaSettings(IServiceCollection services, IConfiguration configuration) { var akkaSettings = new AkkaSettings(); configuration.GetSection(nameof(AkkaSettings)).Bind(akkaSettings); services.AddSingleton(akkaSettings); return akkaSettings; } public static AkkaConfigurationBuilder ConfigureNetwork( this AkkaConfigurationBuilder builder, IServiceProvider serviceProvider) { var settings = serviceProvider.GetRequiredService<AkkaSettings>(); var configuration = serviceProvider.GetRequiredService<IConfiguration>(); // Apply TLS configuration if enabled if (settings.TlsSettings is { Enabled: true }) { ConfigureRemoteOptionsWithTls(settings); } builder.WithRemoting(settings.RemoteOptions); if (settings.AkkaManagementOptions is { Enabled: true }) { // Clear seed nodes when using Akka.Management var clusterOptions = settings.ClusterOptions; clusterOptions.SeedNodes = []; builder .WithClustering(clusterOptions) .WithAkkaManagement(setup => { setup.Http.HostName = settings.AkkaManagementOptions.Hostname?.ToLower(); setup.Http.Port = settings.AkkaManagementOptions.Port; setup.Http.BindHostName = "0.0.0.0"; setup.Http.BindPort = settings.AkkaManagementOptions.Port; }) .WithClusterBootstrap(options => { options.ContactPointDiscovery.ServiceName = settings.AkkaManagementOptions.ServiceName; options.ContactPointDiscovery.PortName = settings.AkkaManagementOptions.PortName; options.ContactPointDiscovery.RequiredContactPointsNr = settings.AkkaManagementOptions.RequiredContactPointsNr; options.ContactPointDiscovery.ContactWithAllContactPoints = true; options.ContactPointDiscovery.StableMargin = TimeSpan.FromSeconds(5); options.ContactPoint.FilterOnFallbackPort = settings.AkkaManagementOptions.FilterOnFallbackPort; }, autoStart: true); ConfigureDiscovery(builder, settings, configuration); } else { builder.WithClustering(settings.ClusterOptions); } return builder; } private static void ConfigureDiscovery( AkkaConfigurationBuilder builder, AkkaSettings settings, IConfiguration configuration) { switch (settings.AkkaManagementOptions!.DiscoveryMethod) { case DiscoveryMethod.Kubernetes: builder.WithKubernetesDiscovery(); break; case DiscoveryMethod.AzureTableStorage: var connectionString = configuration.GetConnectionString("AkkaManagementAzure"); if (connectionString is null) throw new Exception("AkkaManagement table storage connection string [AkkaManagementAzure] is missing"); builder .WithAzureDiscovery(options => { options.ServiceName = settings.AkkaManagementOptions.ServiceName; options.ConnectionString = connectionString; options.HostName = settings.RemoteOptions.PublicHostName?.ToLower() ?? "localhost"; options.Port = settings.AkkaManagementOptions.Port; }) .AddHocon(AzureDiscovery.DefaultConfiguration(), HoconAddMode.Append); break; case DiscoveryMethod.Config: builder.WithConfigDiscovery(options => { options.Services.Add(new Service { Name = settings.AkkaManagementOptions.ServiceName, Endpoints = [ $"{settings.AkkaManagementOptions.Hostname}:{settings.AkkaManagementOptions.Port}" ] }); }); break; default: throw new ArgumentOutOfRangeException(); } } private static void ConfigureRemoteOptionsWithTls(AkkaSettings settings) { var tlsSettings = settings.TlsSettings!; var remoteOptions = settings.RemoteOptions; var certificate = tlsSettings.LoadCertificate(); if (certificate is null) throw new InvalidOperationException("TLS is enabled but no certificate could be loaded"); remoteOptions.EnableSsl = true; remoteOptions.Ssl = new SslOptions { X509Certificate = certificate, SuppressValidation = !tlsSettings.ValidateCertificates }; // Update seed nodes to use akka.ssl.tcp:// protocol if (settings.ClusterOptions.SeedNodes?.Length > 0) { settings.ClusterOptions.SeedNodes = settings.ClusterOptions.SeedNodes .Select(node => node.Replace("akka.tcp://", "akka.ssl.tcp://")) .ToArray(); } } }
Program.cs Integration
using YourApp.Config; using Petabridge.Cmd.Host; using Petabridge.Cmd.Cluster; var builder = WebApplication.CreateBuilder(args); // Add services builder.Services.AddRazorPages(); // or whatever your app needs // Configure Akka.NET builder.Services.ConfigureAkka(builder.Configuration, (configurationBuilder, provider) => { var options = provider.GetRequiredService<AkkaSettings>(); // Add Petabridge.Cmd for cluster management configurationBuilder.AddPetabridgeCmd( options: options.PbmOptions, hostConfiguration: cmd => { cmd.RegisterCommandPalette(ClusterCommands.Instance); }); }); var app = builder.Build(); // Configure middleware app.MapRazorPages(); app.Run();
Aspire AppHost Configuration (Program.cs)
using System.Net.Sockets; var builder = DistributedApplication.CreateBuilder(args); var config = builder.Configuration.GetSection("YourApp") .Get<YourAppConfiguration>() ?? new YourAppConfiguration(); var saPassword = builder.AddParameter( "sql-sa-password", () => "YourStrong!Passw0rd", secret: true); var sqlServer = builder.AddSqlServer("sql", saPassword); if (config.UseVolumes) { sqlServer.WithDataVolume(); } var db = sqlServer.AddDatabase("YourDb"); var app = builder.AddProject<Projects.YourApp>("yourapp") .WithReplicas(config.Replicas) .WithReference(db, "DefaultConnection") .ConfigureAkkaManagementForApp(config); builder.Build().Run(); public class YourAppConfiguration { public int Replicas { get; set; } = 1; public bool UseVolumes { get; set; } = false; public bool UseAkkaManagement { get; set; } = false; }
Aspire Akka.Management Extensions (AkkaManagementExtensions.cs)
using System.Net.Sockets; using Aspire.Hosting.Azure; namespace YourApp.AppHost; public static class AkkaManagementExtensions { public static IResourceBuilder<ProjectResource> ConfigureAkkaManagementForApp( this IResourceBuilder<ProjectResource> appBuilder, YourAppConfiguration config) { if (!config.UseAkkaManagement) return appBuilder; var builder = appBuilder.ApplicationBuilder; // Setup Azure Table Storage for discovery var azureStorage = builder.AddAzureStorage("storage") .RunAsEmulator(); var tableStorage = azureStorage.AddTables("akka-discovery"); appBuilder.WaitFor(tableStorage) .WithReference(tableStorage, "AkkaManagementAzure"); // Setup network endpoint ports appBuilder .WithEndpoint(name: "remote", protocol: ProtocolType.Tcp, env: "AkkaSettings__RemoteOptions__Port") .WithEndpoint(name: "management", protocol: ProtocolType.Tcp, env: "AkkaSettings__AkkaManagementOptions__Port") .WithEndpoint(name: "pbm", protocol: ProtocolType.Tcp, env: "AkkaSettings__PbmOptions__Port"); // Configure Akka.Management settings via environment variables appBuilder .WithEnvironment("AkkaSettings__RemoteOptions__PublicHostName", "localhost") .WithEnvironment("AkkaSettings__AkkaManagementOptions__Enabled", "true") .WithEnvironment("AkkaSettings__AkkaManagementOptions__Hostname", "localhost") .WithEnvironment("AkkaSettings__AkkaManagementOptions__DiscoveryMethod", "AzureTableStorage") .WithEnvironment("AkkaSettings__AkkaManagementOptions__RequiredContactPointsNr", config.Replicas.ToString()) .WithEnvironment("AkkaSettings__AkkaManagementOptions__FilterOnFallbackPort", "false"); return appBuilder; } }
appsettings.json Configuration
{ "ConnectionStrings": { "DefaultConnection": "Server=localhost;Database=YourDb;User Id=sa;Password=YourStrong!Passw0rd;" }, "AkkaSettings": { "ActorSystemName": "YourSystem", "LogConfigOnStart": false, "RemoteOptions": { "PublicHostName": null, "HostName": "0.0.0.0", "Port": 8081 }, "ClusterOptions": { "Roles": ["your-role"], "SeedNodes": [] }, "PbmOptions": { "Host": "0.0.0.0", "Port": 9110 } } }
Common Patterns
Pattern 1: Actor Registration with Dependency Injection
// In your actor project public static class ActorRegistration { public static AkkaConfigurationBuilder AddYourActor( this AkkaConfigurationBuilder builder, string roleName) { builder.WithActors((system, registry, resolver) => { var props = resolver.Props<YourActor>(); var actor = system.ActorOf(props, "your-actor"); registry.Register<YourActor>(actor); }); return builder; } } // In AkkaConfiguration.cs builder .ConfigureNetwork(provider) .WithSqlPersistence(...) .AddYourActor(roleName); // Register your actor
Pattern 2: Cluster Sharding Setup
builder.WithShardRegion<YourEntityActor>( typeName: "your-entity", entityPropsFactory: (_, _, resolver) => resolver.Props<YourEntityActor>(), extractEntityId: ExtractEntityId, extractShardId: ExtractShardId, shardOptions: new ShardOptions { Role = "your-role", StateStoreMode = StateStoreMode.Persistence }); private static string ExtractEntityId(object message) { return message switch { IEntityMessage msg => msg.EntityId, _ => null }; } private static string ExtractShardId(object message) { return message switch { IEntityMessage msg => (msg.EntityId.GetHashCode() % 10).ToString(), _ => null }; }
Pattern 3: Health Checks
Always configure health checks in Program.cs:
builder.Services.AddHealthChecks() .AddCheck("self", () => HealthCheckResult.Healthy("Application is running"), tags: new[] { "liveness" }); // Akka health checks are added automatically by: // - .WithAkkaClusterReadinessCheck() // - .WithActorSystemLivenessCheck() // - journalBuilder.WithHealthCheck() // - snapshotBuilder.WithHealthCheck()
Common Issues and Solutions
Issue 1: Cluster Nodes Can't Discover Each Other
Symptoms: Nodes stay as "Unreachable" in cluster status
Solution:
- Verify
matches the number of replicasRequiredContactPointsNr - Check that all nodes use the same
in AkkaManagementOptionsServiceName - Ensure Azure Table Storage connection string is correct
- Verify firewall/network allows TCP on remote and management ports
Issue 2: Persistence Initialization Fails
Symptoms: Application fails to start with SQL connection errors
Solution:
- Ensure SQL Server is running (check Aspire dashboard)
- Verify connection string is correctly configured
- Set
in WithSqlPersistenceautoInitialize: true - Check that database exists and is accessible
Issue 3: Split Brain in Development
Symptoms: Multiple separate clusters form instead of one unified cluster
Solution:
- Use
in local developmentFilterOnFallbackPort = false - Ensure all replicas use the same discovery configuration
- Set
ContactWithAllContactPoints = true - Increase
for slower dev machinesStableMargin
Testing Akka.NET Actors
For comprehensive Akka.NET testing patterns using Akka.Hosting.TestKit, see the
akka-net-testing-patterns skill.
That skill covers:
- Modern testing with Akka.Hosting.TestKit and dependency injection
- TestProbe patterns for verifying actor interactions
- Testing persistent actors and event sourcing
- Local cluster sharding tests with
AkkaExecutionMode.LocalTest - Scenario-based integration tests
- Best practices and anti-patterns
Quick Example: Testing Akka + Aspire Integration
When testing Aspire applications with Akka.NET actors, combine
aspire-integration-testing patterns with akka-net-testing-patterns:
// Use Aspire's DistributedApplicationTestingBuilder for infrastructure // Use Akka.Hosting.TestKit for actor testing public class AkkaAspireIntegrationTests : IAsyncLifetime { private DistributedApplication? _app; public async Task InitializeAsync() { var appHost = await DistributedApplicationTestingBuilder .CreateAsync<Projects.YourApp_AppHost>(); _app = await appHost.BuildAsync(); await _app.StartAsync(); } [Fact] public async Task ActorSystem_WithRealDatabase_ShouldPersistEvents() { // Get SQL connection string from Aspire var dbResource = _app!.GetResource("yourdb"); var connectionString = await dbResource.GetConnectionStringAsync(); // Create HttpClient to test actor endpoints var httpClient = _app.CreateHttpClient("yourapp"); // Test actor behavior through HTTP API var response = await httpClient.PostAsJsonAsync("/orders", new { OrderId = "ORDER-001", Amount = 100.00m }); response.Should().BeSuccessStatusCode(); // Verify data was persisted to real database await using var connection = new SqlConnection(connectionString); await connection.OpenAsync(); var events = await connection.QueryAsync<string>( "SELECT EventType FROM EventJournal WHERE PersistenceId = 'order-ORDER-001'"); events.Should().Contain("OrderCreated"); } public async Task DisposeAsync() { if (_app is not null) await _app.DisposeAsync(); } }
For unit testing individual actors, use
akka-net-testing-patterns with in-memory persistence (no Aspire needed).
Best Practices Summary
- Always use health checks - Configure readiness and liveness checks for all components
- Bind settings from configuration - Never hard-code hostnames, ports, or connection strings
- Use Akka.Management for multi-node - Don't use static seed nodes for clusters with >1 replica
- Configure TLS for production - Always use TLS in production environments
- Separate actor logic from configuration - Keep actors pure and configuration in extension methods
- Use Petabridge.Cmd - Essential for debugging and managing clusters
- Test with multiple replicas - Always test with
to catch clustering issuesReplicas > 1 - Monitor persistence health - Configure health checks for journal and snapshot stores