diff --git a/CommunityToolkit.Aspire.slnx b/CommunityToolkit.Aspire.slnx
index f6c61bf3c..4d9e4a337 100644
--- a/CommunityToolkit.Aspire.slnx
+++ b/CommunityToolkit.Aspire.slnx
@@ -162,6 +162,9 @@
+
+
+
@@ -255,6 +258,7 @@
+
@@ -324,6 +328,7 @@
+
diff --git a/Directory.Build.props b/Directory.Build.props
index 427c978a1..a295ff937 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -52,5 +52,7 @@
+
+
diff --git a/README.md b/README.md
index a875784ea..8f5114a58 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,7 @@ This repository contains the source code for the Aspire Community Toolkit, a col
| - **Learn More**: [`Hosting.Redis.Extensions`][redis-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Redis.Extensions][redis-ext-shields]][redis-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Redis.Extensions][redis-ext-shields-preview]][redis-ext-nuget-preview] | An integration that contains some additional extensions for hosting Redis container. |
| - **Learn More**: [`Hosting.SqlServer.Extensions`][sqlserver-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.SqlServer.Extensions][sqlserver-ext-shields]][sqlserver-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.SqlServer.Extensions][sqlserver-ext-shields-preview]][sqlserver-ext-nuget-preview] | An integration that contains some additional extensions for hosting SqlServer container. |
| - **Learn More**: [`Hosting.LavinMQ`][lavinmq-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.LavinMQ][lavinmq-shields]][lavinmq-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.LavinMQ][lavinmq-shields-preview]][lavinmq-nuget-preview] | An Aspire hosting integration for [LavinMQ](https://www.lavinmq.com). |
+| - **Learn More**: [`Hosting.RedPanda`][redpanda-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.RedPanda][redpanda-shields]][redpanda-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.RedPanda][redpanda-shields-preview]][redpanda-nuget-preview] | An Aspire hosting integration for the [Redpanda](https://www.redpanda.com/) Kafka-compatible streaming platform. |
| - **Learn More**: [`Hosting.MailPit`][mailpit-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.MailPit][mailpit-ext-shields]][mailpit-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.MailPit][mailpit-ext-shields-preview]][mailpit-ext-nuget-preview] | An Aspire integration leveraging the [MailPit](https://mailpit.axllent.org/) container. |
| - **Learn More**: [`Hosting.k6`][k6-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.k6][k6-shields]][k6-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.k6][k6-shields-preview]][k6-nuget-preview] | An Aspire integration leveraging the [Grafana k6](https://k6.io/) container. |
| - **Learn More**: [`Hosting.MySql.Extensions`][mysql-ext-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.MySql.Extensions][mysql-ext-shields]][mysql-ext-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.MySql.Extensions][mysql-ext-shields-preview]][mysql-ext-nuget-preview] | An integration that contains some additional extensions for hosting MySql container. |
@@ -251,6 +252,11 @@ This project is supported by the [.NET Foundation](https://dotnetfoundation.org)
[lavinmq-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.LavinMQ/
[lavinmq-shields-preview]: https://img.shields.io/nuget/vpre/CommunityToolkit.Aspire.Hosting.LavinMQ?label=nuget%20(preview)
[lavinmq-nuget-preview]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.LavinMQ/absoluteLatest
+[redpanda-integration-docs]: https://learn.microsoft.com/dotnet/aspire/community-toolkit/hosting-redpanda
+[redpanda-shields]: https://img.shields.io/nuget/v/CommunityToolkit.Aspire.Hosting.RedPanda
+[redpanda-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.RedPanda/
+[redpanda-shields-preview]: https://img.shields.io/nuget/vpre/CommunityToolkit.Aspire.Hosting.RedPanda?label=nuget%20(preview)
+[redpanda-nuget-preview]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.RedPanda/absoluteLatest
[mailpit-ext-integration-docs]: https://learn.microsoft.com/dotnet/aspire/community-toolkit/hosting-mailpit
[mailpit-ext-shields]: https://img.shields.io/nuget/v/CommunityToolkit.Aspire.Hosting.MailPit
[mailpit-ext-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.MailPit/
diff --git a/examples/redpanda/CommunityToolkit.Aspire.Hosting.RedPanda.AppHost/CommunityToolkit.Aspire.Hosting.RedPanda.AppHost.csproj b/examples/redpanda/CommunityToolkit.Aspire.Hosting.RedPanda.AppHost/CommunityToolkit.Aspire.Hosting.RedPanda.AppHost.csproj
new file mode 100644
index 000000000..c13952dc1
--- /dev/null
+++ b/examples/redpanda/CommunityToolkit.Aspire.Hosting.RedPanda.AppHost/CommunityToolkit.Aspire.Hosting.RedPanda.AppHost.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ enable
+ enable
+ true
+ 3dc32fad-1d56-45b5-bea8-b96fa74a9628
+
+
+
+
+
+
+
diff --git a/examples/redpanda/CommunityToolkit.Aspire.Hosting.RedPanda.AppHost/Program.cs b/examples/redpanda/CommunityToolkit.Aspire.Hosting.RedPanda.AppHost/Program.cs
new file mode 100644
index 000000000..1cfbd9156
--- /dev/null
+++ b/examples/redpanda/CommunityToolkit.Aspire.Hosting.RedPanda.AppHost/Program.cs
@@ -0,0 +1,6 @@
+var builder = DistributedApplication.CreateBuilder(args);
+
+builder.AddRedPanda("redpanda")
+ .WithConsole();
+
+builder.Build().Run();
diff --git a/examples/redpanda/CommunityToolkit.Aspire.Hosting.RedPanda.AppHost/Properties/launchSettings.json b/examples/redpanda/CommunityToolkit.Aspire.Hosting.RedPanda.AppHost/Properties/launchSettings.json
new file mode 100644
index 000000000..5f04271e0
--- /dev/null
+++ b/examples/redpanda/CommunityToolkit.Aspire.Hosting.RedPanda.AppHost/Properties/launchSettings.json
@@ -0,0 +1,29 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:17277;http://localhost:15247",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "DOTNET_ENVIRONMENT": "Development",
+ "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21270",
+ "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22001"
+ }
+ },
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "http://localhost:15247",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "DOTNET_ENVIRONMENT": "Development",
+ "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19100",
+ "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20013"
+ }
+ }
+ }
+}
diff --git a/examples/redpanda/CommunityToolkit.Aspire.Hosting.RedPanda.AppHost/appsettings.json b/examples/redpanda/CommunityToolkit.Aspire.Hosting.RedPanda.AppHost/appsettings.json
new file mode 100644
index 000000000..31c092aa4
--- /dev/null
+++ b/examples/redpanda/CommunityToolkit.Aspire.Hosting.RedPanda.AppHost/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning",
+ "Aspire.Hosting.Dcp": "Warning"
+ }
+ }
+}
diff --git a/src/CommunityToolkit.Aspire.Hosting.RedPanda/CommunityToolkit.Aspire.Hosting.RedPanda.csproj b/src/CommunityToolkit.Aspire.Hosting.RedPanda/CommunityToolkit.Aspire.Hosting.RedPanda.csproj
new file mode 100644
index 000000000..b7dc4ef32
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.RedPanda/CommunityToolkit.Aspire.Hosting.RedPanda.csproj
@@ -0,0 +1,16 @@
+
+
+
+ An Aspire hosting package for the Redpanda Kafka-compatible streaming platform.
+ hosting redpanda kafka streaming messaging
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/CommunityToolkit.Aspire.Hosting.RedPanda/README.md b/src/CommunityToolkit.Aspire.Hosting.RedPanda/README.md
new file mode 100644
index 000000000..2405b7f72
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.RedPanda/README.md
@@ -0,0 +1,82 @@
+# CommunityToolkit.Aspire.Hosting.RedPanda
+
+## Overview
+
+This Aspire hosting integration runs [Redpanda](https://www.redpanda.com/) in a container. Redpanda is a Kafka API compatible streaming platform, so the resource can be referenced by any Kafka client integration (for example the official `Aspire.Confluent.Kafka` client integration). The integration exposes the Kafka API, the Schema Registry, and the Admin API, and can optionally run the Redpanda Console web UI.
+
+## Usage
+
+### Example 1: Add a Redpanda broker
+
+```csharp
+var builder = DistributedApplication.CreateBuilder(args);
+
+var redpanda = builder.AddRedPanda("redpanda");
+
+builder.AddProject("myapp")
+ .WithReference(redpanda)
+ .WaitFor(redpanda);
+
+builder.Build().Run();
+```
+
+The connection string injected into the referencing resource is the Kafka bootstrap server address in the form `host:port`.
+
+### Example 2: Pin the Kafka host port
+
+```csharp
+var redpanda = builder.AddRedPanda("redpanda", port: 9092);
+```
+
+### Example 3: Tune the broker CPU and memory
+
+```csharp
+var redpanda = builder.AddRedPanda("redpanda", options =>
+{
+ options.CpuCount = 2; // Redpanda --smp, defaults to 1
+ options.Memory = "2G"; // Redpanda --memory, defaults to "1G"
+});
+```
+
+### Example 4: Persist data with a volume or bind mount
+
+```csharp
+var redpanda = builder.AddRedPanda("redpanda")
+ .WithDataVolume();
+
+// or
+
+var redpanda = builder.AddRedPanda("redpanda")
+ .WithDataBindMount("./redpanda-data");
+```
+
+### Example 5: Add the Redpanda Console web UI
+
+```csharp
+var redpanda = builder.AddRedPanda("redpanda")
+ .WithConsole(console => console.WithHostPort(8080));
+```
+
+The console is configured automatically to connect to the broker's Kafka API, Schema Registry, and Admin API.
+
+### Example 6: Add the Kafka UI web UI
+
+```csharp
+var redpanda = builder.AddRedPanda("redpanda")
+ .WithKafkaUI(kafkaUi => kafkaUi.WithHostPort(9000));
+```
+
+`WithKafkaUI` runs the same Kafka management UI (the `kafbat/kafka-ui` image) used by the official Aspire Kafka integration. It is wired automatically to the broker's Kafka API and Schema Registry, and can be used as an alternative to the Redpanda Console.
+
+## Endpoints
+
+| Name | Description |
+| ---------------- | -------------------------------------------- |
+| `kafka` | Kafka API (host accessible) |
+| `internal` | Kafka API (container-to-container) |
+| `schemaregistry` | Schema Registry HTTP API |
+| `admin` | Admin API HTTP (used for the health check) |
+
+## Upstream Image
+
+This integration pins the `redpandadata/redpanda` image (from `docker.redpanda.com`) to a specific version tag (`v26.1.10`) rather than a floating tag, and the optional console uses `redpandadata/console` pinned to `v3.7.4`. Redpanda publishes immutable, fully-versioned tags (`vYY.M.P`); update the pinned tags to adopt newer releases. The optional Kafka UI uses `kafbat/kafka-ui` (from `docker.io`) pinned to `v1.5.0`.
diff --git a/src/CommunityToolkit.Aspire.Hosting.RedPanda/RedPandaBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.RedPanda/RedPandaBuilderExtensions.cs
new file mode 100644
index 000000000..ccdb4ad93
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.RedPanda/RedPandaBuilderExtensions.cs
@@ -0,0 +1,309 @@
+using System.Globalization;
+using Aspire.Hosting.ApplicationModel;
+using CommunityToolkit.Aspire.Hosting.RedPanda;
+
+#pragma warning disable ASPIREATS001 // AspireExport is experimental
+
+namespace Aspire.Hosting;
+
+///
+/// Provides extension methods for adding Redpanda resources to an .
+///
+public static class RedPandaBuilderExtensions
+{
+ private const string DataTarget = "/var/lib/redpanda/data";
+
+ ///
+ /// Adds a Redpanda container resource to the application. Redpanda is a Kafka API compatible
+ /// streaming platform, so the resource can be referenced by any Kafka client integration.
+ ///
+ ///
+ /// This version of the package defaults to the tag of the container image.
+ ///
+ /// The to add the resource to.
+ /// The name of the resource. This name is used as the connection string name when referenced in a dependency.
+ /// The host port that the Kafka API is exposed on. If a random port is assigned.
+ /// A reference to the .
+ [AspireExport]
+ public static IResourceBuilder AddRedPanda(
+ this IDistributedApplicationBuilder builder,
+ [ResourceName] string name,
+ int? port = null)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentException.ThrowIfNullOrEmpty(name);
+
+ return builder.AddRedPandaCore(name, new RedPandaServerOptions(), port);
+ }
+
+ ///
+ /// Adds a Redpanda container resource to the application, using a delegate to configure broker options
+ /// such as the CPU and memory limits. Redpanda is a Kafka API compatible streaming platform, so the
+ /// resource can be referenced by any Kafka client integration.
+ ///
+ ///
+ /// This version of the package defaults to the tag of the container image.
+ ///
+ /// The to add the resource to.
+ /// The name of the resource. This name is used as the connection string name when referenced in a dependency.
+ /// A delegate that configures the for the broker.
+ /// The host port that the Kafka API is exposed on. If a random port is assigned.
+ /// A reference to the .
+ [AspireExportIgnore(Reason = "Action is not ATS-compatible. Use the AddRedPanda(builder, name, port) overload instead.")]
+ public static IResourceBuilder AddRedPanda(
+ this IDistributedApplicationBuilder builder,
+ [ResourceName] string name,
+ Action configureOptions,
+ int? port = null)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentException.ThrowIfNullOrEmpty(name);
+ ArgumentNullException.ThrowIfNull(configureOptions);
+
+ RedPandaServerOptions options = new();
+ configureOptions(options);
+
+ return builder.AddRedPandaCore(name, options, port);
+ }
+
+ private static IResourceBuilder AddRedPandaCore(
+ this IDistributedApplicationBuilder builder,
+ string name,
+ RedPandaServerOptions options,
+ int? port)
+ {
+ ArgumentOutOfRangeException.ThrowIfNegativeOrZero(options.CpuCount, $"{nameof(options)}.{nameof(options.CpuCount)}");
+ ArgumentException.ThrowIfNullOrEmpty(options.Memory, $"{nameof(options)}.{nameof(options.Memory)}");
+
+ RedPandaServerResource resource = new(name);
+
+ return builder.AddResource(resource)
+ .WithImage(RedPandaContainerImageTags.Image, RedPandaContainerImageTags.Tag)
+ .WithImageRegistry(RedPandaContainerImageTags.Registry)
+ .WithEndpoint(targetPort: RedPandaServerResource.KafkaBrokerPort, port: port, name: RedPandaServerResource.PrimaryEndpointName)
+ .WithEndpoint(targetPort: RedPandaServerResource.KafkaInternalBrokerPort, name: RedPandaServerResource.InternalEndpointName)
+ .WithHttpEndpoint(targetPort: RedPandaServerResource.SchemaRegistryPort, name: RedPandaServerResource.SchemaRegistryEndpointName)
+ .WithHttpEndpoint(targetPort: RedPandaServerResource.AdminPort, name: RedPandaServerResource.AdminEndpointName)
+ .WithEntrypoint("/usr/bin/rpk")
+ .WithArgs(context => ConfigureRedPandaArgs(context, resource, options))
+ .WithHttpHealthCheck("/v1/status/ready", endpointName: RedPandaServerResource.AdminEndpointName);
+ }
+
+ ///
+ /// Adds a named volume for the data folder to a Redpanda container resource.
+ ///
+ /// The resource builder.
+ /// The name of the volume. Defaults to an auto-generated name based on the application and resource names.
+ /// A flag that indicates if this is a read-only volume.
+ /// A reference to the .
+ [AspireExport]
+ public static IResourceBuilder WithDataVolume(
+ this IResourceBuilder builder,
+ string? name = null,
+ bool isReadOnly = false)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ return builder.WithVolume(name ?? VolumeNameGenerator.Generate(builder, "data"), DataTarget, isReadOnly);
+ }
+
+ ///
+ /// Adds a bind mount for the data folder to a Redpanda container resource.
+ ///
+ /// The resource builder.
+ /// The source directory on the host to mount into the container.
+ /// A flag that indicates if this is a read-only mount.
+ /// A reference to the .
+ [AspireExport]
+ public static IResourceBuilder WithDataBindMount(
+ this IResourceBuilder builder,
+ string source,
+ bool isReadOnly = false)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentException.ThrowIfNullOrEmpty(source);
+
+ return builder.WithBindMount(source, DataTarget, isReadOnly);
+ }
+
+ ///
+ /// Adds a Redpanda Console container to the application, configured to connect to the Redpanda broker.
+ ///
+ ///
+ /// This version of the package defaults to the tag of the container image.
+ ///
+ /// The Redpanda server resource builder.
+ /// An optional callback to configure the Redpanda Console container resource.
+ /// The name of the container (optional).
+ /// A reference to the for the Redpanda server resource.
+ [AspireExport(RunSyncOnBackgroundThread = true)]
+ public static IResourceBuilder WithConsole(
+ this IResourceBuilder builder,
+ Action>? configureContainer = null,
+ string? containerName = null)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ containerName ??= $"{builder.Resource.Name}-console";
+
+ RedPandaConsoleContainerResource console = new(containerName);
+
+ IResourceBuilder consoleBuilder = builder.ApplicationBuilder.AddResource(console)
+ .WithImage(RedPandaContainerImageTags.ConsoleImage, RedPandaContainerImageTags.ConsoleTag)
+ .WithImageRegistry(RedPandaContainerImageTags.Registry)
+ .WithHttpEndpoint(targetPort: RedPandaConsoleContainerResource.HttpPort, name: RedPandaConsoleContainerResource.HttpEndpointName)
+ .WithEnvironment(context => ConfigureConsoleContainer(context, builder.Resource))
+ .WaitFor(builder)
+ .ExcludeFromManifest();
+
+ configureContainer?.Invoke(consoleBuilder);
+
+ return builder;
+ }
+
+ ///
+ /// Configures the host port that the Redpanda Console resource is exposed on instead of using a randomly assigned port.
+ ///
+ /// The resource builder for the Redpanda Console.
+ /// The port to bind on the host. If a random port is assigned.
+ /// A reference to the .
+ [AspireExport]
+ public static IResourceBuilder WithHostPort(
+ this IResourceBuilder builder,
+ int? port)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ return builder.WithEndpoint(RedPandaConsoleContainerResource.HttpEndpointName, endpoint => endpoint.Port = port);
+ }
+
+ ///
+ /// Adds a Kafka UI container to the application, configured to connect to the Redpanda broker. This is the
+ /// same Kafka management UI (the kafbat/kafka-ui image) used by the official Aspire Kafka integration,
+ /// so it works against any Kafka API compatible broker.
+ ///
+ ///
+ /// This version of the package defaults to the tag of the container image.
+ ///
+ /// The Redpanda server resource builder.
+ /// An optional callback to configure the Kafka UI container resource.
+ /// The name of the container (optional).
+ /// A reference to the for the Redpanda server resource.
+ [AspireExport(RunSyncOnBackgroundThread = true)]
+ public static IResourceBuilder WithKafkaUI(
+ this IResourceBuilder builder,
+ Action>? configureContainer = null,
+ string? containerName = null)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ containerName ??= $"{builder.Resource.Name}-kafka-ui";
+
+ RedPandaKafkaUiContainerResource kafkaUi = new(containerName);
+
+ IResourceBuilder kafkaUiBuilder = builder.ApplicationBuilder.AddResource(kafkaUi)
+ .WithImage(RedPandaContainerImageTags.KafkaUiImage, RedPandaContainerImageTags.KafkaUiTag)
+ .WithImageRegistry(RedPandaContainerImageTags.KafkaUiRegistry)
+ .WithHttpEndpoint(targetPort: RedPandaKafkaUiContainerResource.HttpPort, name: RedPandaKafkaUiContainerResource.HttpEndpointName)
+ .WithEnvironment(context => ConfigureKafkaUiContainer(context, builder.Resource))
+ .WaitFor(builder)
+ .ExcludeFromManifest();
+
+ configureContainer?.Invoke(kafkaUiBuilder);
+
+ return builder;
+ }
+
+ ///
+ /// Configures the host port that the Kafka UI resource is exposed on instead of using a randomly assigned port.
+ ///
+ /// The resource builder for the Kafka UI.
+ /// The port to bind on the host. If a random port is assigned.
+ /// A reference to the .
+ [AspireExportIgnore(Reason = "The exported 'withHostPort' capability is already provided by the RedPandaConsoleContainerResource overload; this overload remains available to C# callers.")]
+ public static IResourceBuilder WithHostPort(
+ this IResourceBuilder builder,
+ int? port)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ return builder.WithEndpoint(RedPandaKafkaUiContainerResource.HttpEndpointName, endpoint => endpoint.Port = port);
+ }
+
+ private static void ConfigureRedPandaArgs(CommandLineArgsCallbackContext context, RedPandaServerResource resource, RedPandaServerOptions options)
+ {
+ // Start a single-node Redpanda broker tuned for local development.
+ // See https://docs.redpanda.com/current/reference/rpk/rpk-redpanda/rpk-redpanda-start/
+ context.Args.Add("redpanda");
+ context.Args.Add("start");
+ context.Args.Add("--mode");
+ context.Args.Add("dev-container");
+ context.Args.Add("--smp");
+ context.Args.Add(options.CpuCount.ToString(CultureInfo.InvariantCulture));
+ context.Args.Add("--memory");
+ context.Args.Add(options.Memory);
+
+ // Two Kafka listeners: an "internal" listener for container-to-container traffic over the
+ // Aspire container network, and an "external" listener that is reachable from the host.
+ context.Args.Add("--kafka-addr");
+ context.Args.Add($"internal://0.0.0.0:{RedPandaServerResource.KafkaInternalBrokerPort},external://0.0.0.0:{RedPandaServerResource.KafkaBrokerPort}");
+
+ // Advertised listeners tell clients how to reach the broker after the initial connection.
+ var internalPort = RedPandaServerResource.KafkaInternalBrokerPort.ToString(CultureInfo.InvariantCulture);
+ var advertised = context.ExecutionContext.IsRunMode
+ // In run mode, the internal listener is reached over the default Aspire container network using
+ // the resource name, and the external listener is reached from the host on the mapped port.
+ ? ReferenceExpression.Create(
+ $"internal://{resource.Name}:{internalPort},external://localhost:{resource.PrimaryEndpoint.Property(EndpointProperty.Port)}")
+ : ReferenceExpression.Create(
+ $"internal://{resource.InternalEndpoint.Property(EndpointProperty.HostAndPort)},external://{resource.PrimaryEndpoint.Property(EndpointProperty.HostAndPort)}");
+
+ context.Args.Add("--advertise-kafka-addr");
+ context.Args.Add(advertised);
+
+ context.Args.Add("--schema-registry-addr");
+ context.Args.Add($"0.0.0.0:{RedPandaServerResource.SchemaRegistryPort}");
+ }
+
+ private static void ConfigureConsoleContainer(EnvironmentCallbackContext context, RedPandaServerResource resource)
+ {
+ // The console runs in its own container, so it reaches Redpanda over the default Aspire container
+ // network in run mode (using the resource name + target ports) and over the host otherwise.
+ var brokers = context.ExecutionContext.IsRunMode
+ ? ReferenceExpression.Create($"{resource.Name}:{resource.InternalEndpoint.Property(EndpointProperty.TargetPort)}")
+ : ReferenceExpression.Create($"{resource.InternalEndpoint.Property(EndpointProperty.HostAndPort)}");
+
+ var schemaRegistry = context.ExecutionContext.IsRunMode
+ ? ReferenceExpression.Create($"http://{resource.Name}:{resource.SchemaRegistryEndpoint.Property(EndpointProperty.TargetPort)}")
+ : ReferenceExpression.Create($"{resource.SchemaRegistryEndpoint.Property(EndpointProperty.Scheme)}://{resource.SchemaRegistryEndpoint.Property(EndpointProperty.HostAndPort)}");
+
+ var adminApi = context.ExecutionContext.IsRunMode
+ ? ReferenceExpression.Create($"http://{resource.Name}:{resource.AdminEndpoint.Property(EndpointProperty.TargetPort)}")
+ : ReferenceExpression.Create($"{resource.AdminEndpoint.Property(EndpointProperty.Scheme)}://{resource.AdminEndpoint.Property(EndpointProperty.HostAndPort)}");
+
+ context.EnvironmentVariables["KAFKA_BROKERS"] = brokers;
+ context.EnvironmentVariables["KAFKA_SCHEMAREGISTRY_ENABLED"] = "true";
+ context.EnvironmentVariables["KAFKA_SCHEMAREGISTRY_URLS"] = schemaRegistry;
+ context.EnvironmentVariables["REDPANDA_ADMINAPI_ENABLED"] = "true";
+ context.EnvironmentVariables["REDPANDA_ADMINAPI_URLS"] = adminApi;
+ }
+
+ private static void ConfigureKafkaUiContainer(EnvironmentCallbackContext context, RedPandaServerResource resource)
+ {
+ // Kafka UI runs in its own container, so it reaches Redpanda over the default Aspire container
+ // network in run mode (using the resource name + target ports) and over the host otherwise.
+ var bootstrapServers = context.ExecutionContext.IsRunMode
+ ? ReferenceExpression.Create($"{resource.Name}:{resource.InternalEndpoint.Property(EndpointProperty.TargetPort)}")
+ : ReferenceExpression.Create($"{resource.InternalEndpoint.Property(EndpointProperty.HostAndPort)}");
+
+ var schemaRegistry = context.ExecutionContext.IsRunMode
+ ? ReferenceExpression.Create($"http://{resource.Name}:{resource.SchemaRegistryEndpoint.Property(EndpointProperty.TargetPort)}")
+ : ReferenceExpression.Create($"{resource.SchemaRegistryEndpoint.Property(EndpointProperty.Scheme)}://{resource.SchemaRegistryEndpoint.Property(EndpointProperty.HostAndPort)}");
+
+ context.EnvironmentVariables["KAFKA_CLUSTERS_0_NAME"] = resource.Name;
+ context.EnvironmentVariables["KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS"] = bootstrapServers;
+ context.EnvironmentVariables["KAFKA_CLUSTERS_0_SCHEMAREGISTRY"] = schemaRegistry;
+ }
+}
+
+#pragma warning restore ASPIREATS001
diff --git a/src/CommunityToolkit.Aspire.Hosting.RedPanda/RedPandaConsoleContainerResource.cs b/src/CommunityToolkit.Aspire.Hosting.RedPanda/RedPandaConsoleContainerResource.cs
new file mode 100644
index 000000000..f7f838398
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.RedPanda/RedPandaConsoleContainerResource.cs
@@ -0,0 +1,17 @@
+#pragma warning disable ASPIREATS001 // AspireExport is experimental
+
+namespace Aspire.Hosting.ApplicationModel;
+
+///
+/// A resource that represents a Redpanda Console container, a web UI for managing and inspecting
+/// one or more Redpanda (or Kafka) brokers.
+///
+/// The name of the resource.
+[AspireExport(ExposeProperties = true)]
+public class RedPandaConsoleContainerResource(string name) : ContainerResource(name)
+{
+ internal const string HttpEndpointName = "http";
+ internal const int HttpPort = 8080;
+}
+
+#pragma warning restore ASPIREATS001
diff --git a/src/CommunityToolkit.Aspire.Hosting.RedPanda/RedPandaContainerImageTags.cs b/src/CommunityToolkit.Aspire.Hosting.RedPanda/RedPandaContainerImageTags.cs
new file mode 100644
index 000000000..65c141b74
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.RedPanda/RedPandaContainerImageTags.cs
@@ -0,0 +1,28 @@
+namespace CommunityToolkit.Aspire.Hosting.RedPanda;
+
+internal static class RedPandaContainerImageTags
+{
+ /// docker.redpanda.com
+ internal const string Registry = "docker.redpanda.com";
+
+ /// redpandadata/redpanda
+ internal const string Image = "redpandadata/redpanda";
+
+ /// v26.1.10
+ internal const string Tag = "v26.1.10";
+
+ /// redpandadata/console
+ internal const string ConsoleImage = "redpandadata/console";
+
+ /// v3.7.4
+ internal const string ConsoleTag = "v3.7.4";
+
+ /// docker.io
+ internal const string KafkaUiRegistry = "docker.io";
+
+ /// kafbat/kafka-ui
+ internal const string KafkaUiImage = "kafbat/kafka-ui";
+
+ /// v1.5.0
+ internal const string KafkaUiTag = "v1.5.0";
+}
diff --git a/src/CommunityToolkit.Aspire.Hosting.RedPanda/RedPandaKafkaUiContainerResource.cs b/src/CommunityToolkit.Aspire.Hosting.RedPanda/RedPandaKafkaUiContainerResource.cs
new file mode 100644
index 000000000..ef1759694
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.RedPanda/RedPandaKafkaUiContainerResource.cs
@@ -0,0 +1,17 @@
+#pragma warning disable ASPIREATS001 // AspireExport is experimental
+
+namespace Aspire.Hosting.ApplicationModel;
+
+///
+/// A resource that represents a Kafka UI container (the kafbat/kafka-ui image), a web UI for managing
+/// and inspecting one or more Kafka API compatible brokers such as Redpanda.
+///
+/// The name of the resource.
+[AspireExport(ExposeProperties = true)]
+public class RedPandaKafkaUiContainerResource(string name) : ContainerResource(name)
+{
+ internal const string HttpEndpointName = "http";
+ internal const int HttpPort = 8080;
+}
+
+#pragma warning restore ASPIREATS001
diff --git a/src/CommunityToolkit.Aspire.Hosting.RedPanda/RedPandaServerOptions.cs b/src/CommunityToolkit.Aspire.Hosting.RedPanda/RedPandaServerOptions.cs
new file mode 100644
index 000000000..4daed57ed
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.RedPanda/RedPandaServerOptions.cs
@@ -0,0 +1,21 @@
+using Aspire.Hosting.ApplicationModel;
+
+namespace Aspire.Hosting;
+
+///
+/// Options for configuring the broker of a .
+///
+public class RedPandaServerOptions
+{
+ ///
+ /// Gets or sets the number of logical CPU cores the broker is allowed to use
+ /// (the Redpanda --smp flag). Defaults to 1.
+ ///
+ public int CpuCount { get; set; } = 1;
+
+ ///
+ /// Gets or sets the amount of memory the broker is allowed to use
+ /// (the Redpanda --memory flag), for example "1G". Defaults to "1G".
+ ///
+ public string Memory { get; set; } = "1G";
+}
diff --git a/src/CommunityToolkit.Aspire.Hosting.RedPanda/RedPandaServerResource.cs b/src/CommunityToolkit.Aspire.Hosting.RedPanda/RedPandaServerResource.cs
new file mode 100644
index 000000000..f6362cb55
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.RedPanda/RedPandaServerResource.cs
@@ -0,0 +1,75 @@
+#pragma warning disable ASPIREATS001 // AspireExport is experimental
+
+namespace Aspire.Hosting.ApplicationModel;
+
+///
+/// A resource that represents a Redpanda streaming platform container.
+///
+///
+/// Redpanda is a Kafka API compatible streaming platform. The
+/// returns the bootstrap broker address (host:port) that Kafka clients use to connect.
+///
+/// The name of the resource.
+[AspireExport(ExposeProperties = true)]
+public class RedPandaServerResource(string name) : ContainerResource(name), IResourceWithConnectionString
+{
+ internal const string PrimaryEndpointName = "kafka";
+ internal const string InternalEndpointName = "internal";
+ internal const string SchemaRegistryEndpointName = "schemaregistry";
+ internal const string AdminEndpointName = "admin";
+
+ internal const int KafkaBrokerPort = 9092;
+ internal const int KafkaInternalBrokerPort = 29092;
+ internal const int SchemaRegistryPort = 8081;
+ internal const int AdminPort = 9644;
+
+ private EndpointReference? _primaryEndpoint;
+ private EndpointReference? _internalEndpoint;
+ private EndpointReference? _schemaRegistryEndpoint;
+ private EndpointReference? _adminEndpoint;
+
+ ///
+ /// Gets the primary (Kafka API) endpoint for the Redpanda broker. This endpoint is reachable from the host.
+ ///
+ public EndpointReference PrimaryEndpoint => _primaryEndpoint ??= new(this, PrimaryEndpointName);
+
+ ///
+ /// Gets the internal Kafka API endpoint used for container-to-container communication.
+ ///
+ public EndpointReference InternalEndpoint => _internalEndpoint ??= new(this, InternalEndpointName);
+
+ ///
+ /// Gets the Schema Registry HTTP endpoint for the Redpanda broker.
+ ///
+ public EndpointReference SchemaRegistryEndpoint => _schemaRegistryEndpoint ??= new(this, SchemaRegistryEndpointName);
+
+ ///
+ /// Gets the Admin API HTTP endpoint for the Redpanda broker.
+ ///
+ public EndpointReference AdminEndpoint => _adminEndpoint ??= new(this, AdminEndpointName);
+
+ ///
+ /// Gets the host of the primary Kafka endpoint.
+ ///
+ public EndpointReferenceExpression Host => PrimaryEndpoint.Property(EndpointProperty.Host);
+
+ ///
+ /// Gets the port of the primary Kafka endpoint.
+ ///
+ public EndpointReferenceExpression Port => PrimaryEndpoint.Property(EndpointProperty.Port);
+
+ ///
+ /// Gets the connection string expression for the Redpanda broker in the form of host:port,
+ /// suitable for use as the Kafka bootstrap servers value.
+ ///
+ public ReferenceExpression ConnectionStringExpression =>
+ ReferenceExpression.Create($"{Host}:{Port}");
+
+ IEnumerable> IResourceWithConnectionString.GetConnectionProperties()
+ {
+ yield return new("Host", ReferenceExpression.Create($"{Host}"));
+ yield return new("Port", ReferenceExpression.Create($"{Port}"));
+ }
+}
+
+#pragma warning restore ASPIREATS001
diff --git a/tests/CommunityToolkit.Aspire.Hosting.RedPanda.Tests/AppHostTests.cs b/tests/CommunityToolkit.Aspire.Hosting.RedPanda.Tests/AppHostTests.cs
new file mode 100644
index 000000000..917385d07
--- /dev/null
+++ b/tests/CommunityToolkit.Aspire.Hosting.RedPanda.Tests/AppHostTests.cs
@@ -0,0 +1,35 @@
+using Aspire.Components.Common.Tests;
+using CommunityToolkit.Aspire.Testing;
+
+namespace CommunityToolkit.Aspire.Hosting.RedPanda.Tests;
+
+[RequiresDocker]
+public class AppHostTests(AspireIntegrationTestFixture fixture)
+ : IClassFixture>
+{
+ private const string ResourceName = "redpanda";
+
+ [Fact]
+ public async Task ResourceStartsAndAdminApiReportsReady()
+ {
+ await fixture.ResourceNotificationService.WaitForResourceHealthyAsync(ResourceName).WaitAsync(TimeSpan.FromMinutes(2));
+
+ HttpClient client = fixture.CreateHttpClient(ResourceName, "admin");
+
+ HttpResponseMessage response = await client.GetAsync("/v1/status/ready");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task SchemaRegistryRespondsOk()
+ {
+ await fixture.ResourceNotificationService.WaitForResourceHealthyAsync(ResourceName).WaitAsync(TimeSpan.FromMinutes(2));
+
+ HttpClient client = fixture.CreateHttpClient(ResourceName, "schemaregistry");
+
+ HttpResponseMessage response = await client.GetAsync("/subjects");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+}
diff --git a/tests/CommunityToolkit.Aspire.Hosting.RedPanda.Tests/CommunityToolkit.Aspire.Hosting.RedPanda.Tests.csproj b/tests/CommunityToolkit.Aspire.Hosting.RedPanda.Tests/CommunityToolkit.Aspire.Hosting.RedPanda.Tests.csproj
new file mode 100644
index 000000000..53db91bcc
--- /dev/null
+++ b/tests/CommunityToolkit.Aspire.Hosting.RedPanda.Tests/CommunityToolkit.Aspire.Hosting.RedPanda.Tests.csproj
@@ -0,0 +1,14 @@
+
+
+
+ false
+ true
+
+
+
+
+
+
+
+
+
diff --git a/tests/CommunityToolkit.Aspire.Hosting.RedPanda.Tests/ResourceCreationTests.cs b/tests/CommunityToolkit.Aspire.Hosting.RedPanda.Tests/ResourceCreationTests.cs
new file mode 100644
index 000000000..51b392414
--- /dev/null
+++ b/tests/CommunityToolkit.Aspire.Hosting.RedPanda.Tests/ResourceCreationTests.cs
@@ -0,0 +1,253 @@
+using Aspire.Hosting;
+
+namespace CommunityToolkit.Aspire.Hosting.RedPanda.Tests;
+
+public class ResourceCreationTests
+{
+ [Fact]
+ public void AddRedPandaShouldThrowWhenBuilderIsNull()
+ {
+ IDistributedApplicationBuilder builder = null!;
+ Assert.Throws(() => builder.AddRedPanda("redpanda"));
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ public void AddRedPandaShouldThrowWhenNameIsNullOrEmpty(string? name)
+ {
+ IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder();
+ Assert.ThrowsAny(() => builder.AddRedPanda(name!));
+ }
+
+ [Fact]
+ public void AddRedPandaSetsContainerImageAnnotations()
+ {
+ IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder();
+ builder.AddRedPanda("redpanda");
+
+ using DistributedApplication app = builder.Build();
+ DistributedApplicationModel appModel = app.Services.GetRequiredService();
+
+ RedPandaServerResource resource = Assert.Single(appModel.Resources.OfType());
+ Assert.Equal("redpanda", resource.Name);
+
+ Assert.True(resource.TryGetLastAnnotation(out ContainerImageAnnotation? image));
+ Assert.Equal("redpandadata/redpanda", image.Image);
+ Assert.Equal("v26.1.10", image.Tag);
+ Assert.Equal("docker.redpanda.com", image.Registry);
+ }
+
+ [Fact]
+ public void AddRedPandaCreatesExpectedEndpoints()
+ {
+ IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder();
+ builder.AddRedPanda("redpanda");
+
+ using DistributedApplication app = builder.Build();
+ DistributedApplicationModel appModel = app.Services.GetRequiredService();
+
+ RedPandaServerResource resource = Assert.Single(appModel.Resources.OfType());
+
+ Assert.True(resource.TryGetAnnotationsOfType(out IEnumerable? annotations));
+ Dictionary endpoints = annotations!.ToDictionary(e => e.Name);
+
+ Assert.Equal(4, endpoints.Count);
+ Assert.Equal(RedPandaServerResource.KafkaBrokerPort, endpoints["kafka"].TargetPort);
+ Assert.Equal(RedPandaServerResource.KafkaInternalBrokerPort, endpoints["internal"].TargetPort);
+ Assert.Equal(RedPandaServerResource.SchemaRegistryPort, endpoints["schemaregistry"].TargetPort);
+ Assert.Equal(RedPandaServerResource.AdminPort, endpoints["admin"].TargetPort);
+ Assert.Equal("http", endpoints["schemaregistry"].UriScheme);
+ Assert.Equal("http", endpoints["admin"].UriScheme);
+ }
+
+ [Fact]
+ public void AddRedPandaUsesProvidedHostPortForKafkaEndpoint()
+ {
+ IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder();
+ builder.AddRedPanda("redpanda", port: 9092);
+
+ using DistributedApplication app = builder.Build();
+ DistributedApplicationModel appModel = app.Services.GetRequiredService();
+
+ RedPandaServerResource resource = Assert.Single(appModel.Resources.OfType());
+ EndpointAnnotation kafka = resource.GetEndpoint("kafka").EndpointAnnotation;
+ Assert.Equal(9092, kafka.Port);
+ }
+
+ [Fact]
+ public void ConnectionStringExpressionUsesKafkaEndpoint()
+ {
+ IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder();
+ IResourceBuilder redpanda = builder.AddRedPanda("redpanda");
+
+ Assert.Equal(
+ "{redpanda.bindings.kafka.host}:{redpanda.bindings.kafka.port}",
+ redpanda.Resource.ConnectionStringExpression.ValueExpression);
+ }
+
+ [Fact]
+ public void WithConsoleAddsConsoleResource()
+ {
+ IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder();
+ builder.AddRedPanda("redpanda").WithConsole();
+
+ using DistributedApplication app = builder.Build();
+ DistributedApplicationModel appModel = app.Services.GetRequiredService();
+
+ RedPandaConsoleContainerResource console = Assert.Single(appModel.Resources.OfType());
+ Assert.Equal("redpanda-console", console.Name);
+
+ Assert.True(console.TryGetLastAnnotation(out ContainerImageAnnotation? image));
+ Assert.Equal("redpandadata/console", image.Image);
+ Assert.Equal("v3.7.4", image.Tag);
+ }
+
+ [Fact]
+ public void WithKafkaUIAddsKafkaUiResource()
+ {
+ IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder();
+ builder.AddRedPanda("redpanda").WithKafkaUI();
+
+ using DistributedApplication app = builder.Build();
+ DistributedApplicationModel appModel = app.Services.GetRequiredService();
+
+ RedPandaKafkaUiContainerResource kafkaUi = Assert.Single(appModel.Resources.OfType());
+ Assert.Equal("redpanda-kafka-ui", kafkaUi.Name);
+
+ Assert.True(kafkaUi.TryGetLastAnnotation(out ContainerImageAnnotation? image));
+ Assert.Equal("kafbat/kafka-ui", image.Image);
+ Assert.Equal("v1.5.0", image.Tag);
+ Assert.Equal("docker.io", image.Registry);
+ }
+
+ [Fact]
+ public void WithKafkaUIHostPortSetsEndpointPort()
+ {
+ IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder();
+ builder.AddRedPanda("redpanda").WithKafkaUI(kafkaUi => kafkaUi.WithHostPort(9000));
+
+ using DistributedApplication app = builder.Build();
+ DistributedApplicationModel appModel = app.Services.GetRequiredService();
+
+ RedPandaKafkaUiContainerResource kafkaUi = Assert.Single(appModel.Resources.OfType());
+ EndpointAnnotation http = kafkaUi.GetEndpoint("http").EndpointAnnotation;
+ Assert.Equal(9000, http.Port);
+ }
+
+ [Fact]
+ public void WithConsoleHostPortSetsEndpointPort()
+ {
+ IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder();
+ builder.AddRedPanda("redpanda").WithConsole(console => console.WithHostPort(9090));
+
+ using DistributedApplication app = builder.Build();
+ DistributedApplicationModel appModel = app.Services.GetRequiredService();
+
+ RedPandaConsoleContainerResource console = Assert.Single(appModel.Resources.OfType());
+ EndpointAnnotation http = console.GetEndpoint("http").EndpointAnnotation;
+ Assert.Equal(9090, http.Port);
+ }
+
+ [Fact]
+ public void WithDataVolumeAddsVolumeAnnotation()
+ {
+ IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder();
+ builder.AddRedPanda("redpanda").WithDataVolume("redpanda-data");
+
+ using DistributedApplication app = builder.Build();
+ DistributedApplicationModel appModel = app.Services.GetRequiredService();
+
+ RedPandaServerResource resource = Assert.Single(appModel.Resources.OfType());
+
+ Assert.True(resource.TryGetAnnotationsOfType(out IEnumerable? mounts));
+ ContainerMountAnnotation mount = Assert.Single(mounts!);
+ Assert.Equal("redpanda-data", mount.Source);
+ Assert.Equal("/var/lib/redpanda/data", mount.Target);
+ Assert.Equal(ContainerMountType.Volume, mount.Type);
+ }
+
+ [Fact]
+ public void WithDataBindMountAddsBindMountAnnotation()
+ {
+ IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder();
+ builder.AddRedPanda("redpanda").WithDataBindMount("./redpanda-data");
+
+ using DistributedApplication app = builder.Build();
+ DistributedApplicationModel appModel = app.Services.GetRequiredService();
+
+ RedPandaServerResource resource = Assert.Single(appModel.Resources.OfType());
+
+ Assert.True(resource.TryGetAnnotationsOfType(out IEnumerable? mounts));
+ ContainerMountAnnotation mount = Assert.Single(mounts!);
+ Assert.EndsWith("redpanda-data", mount.Source);
+ Assert.Equal("/var/lib/redpanda/data", mount.Target);
+ Assert.Equal(ContainerMountType.BindMount, mount.Type);
+ Assert.False(mount.IsReadOnly);
+ }
+
+ [Fact]
+ public async Task AddRedPandaUsesDefaultCpuAndMemoryArgs()
+ {
+ IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder();
+ builder.AddRedPanda("redpanda");
+
+ using DistributedApplication app = builder.Build();
+ DistributedApplicationModel appModel = app.Services.GetRequiredService();
+
+ RedPandaServerResource resource = Assert.Single(appModel.Resources.OfType());
+
+ IList