diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index b881ae643..7cd902c05 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -49,6 +49,7 @@ jobs:
Hosting.RavenDB.Tests,
Hosting.Redis.Extensions.Tests,
Hosting.Rust.Tests,
+ Hosting.Solr.Tests,
Hosting.SqlDatabaseProjects.Tests,
Hosting.SqlServer.Extensions.Tests,
Hosting.Sqlite.Tests,
diff --git a/CommunityToolkit.Aspire.slnx b/CommunityToolkit.Aspire.slnx
index a1c768323..a8d2c4b1f 100644
--- a/CommunityToolkit.Aspire.slnx
+++ b/CommunityToolkit.Aspire.slnx
@@ -131,6 +131,9 @@
+
+
+
@@ -179,6 +182,7 @@
+
@@ -228,6 +232,7 @@
+
@@ -250,4 +255,4 @@
-
+
\ No newline at end of file
diff --git a/examples/solr/CommunityToolkit.Aspire.Hosting.Solr.AppHost.csproj b/examples/solr/CommunityToolkit.Aspire.Hosting.Solr.AppHost.csproj
new file mode 100644
index 000000000..11dc8afdc
--- /dev/null
+++ b/examples/solr/CommunityToolkit.Aspire.Hosting.Solr.AppHost.csproj
@@ -0,0 +1,19 @@
+
+
+
+
+ Exe
+ enable
+ enable
+ true
+ bfe6b134-1a06-4449-a146-ba3cdb0d02a6
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/solr/Program.cs b/examples/solr/Program.cs
new file mode 100644
index 000000000..9d4840050
--- /dev/null
+++ b/examples/solr/Program.cs
@@ -0,0 +1,14 @@
+var builder = DistributedApplication.CreateBuilder(args);
+
+// Add Solr resource with default core name "solr"
+var solr = builder.AddSolr("solr");
+
+// Add Solr resource with custom port and core name
+var solrWithCustomPort = builder.AddSolr("solr-custom", port: 8984, coreName: "mycore");
+
+// Reference the Solr resources in a project (example)
+// var exampleProject = builder.AddProject()
+// .WithReference(solr)
+// .WithReference(solrWithCustomPort);
+
+builder.Build().Run();
diff --git a/src/CommunityToolkit.Aspire.Hosting.Solr/CommunityToolkit.Aspire.Hosting.Solr.csproj b/src/CommunityToolkit.Aspire.Hosting.Solr/CommunityToolkit.Aspire.Hosting.Solr.csproj
new file mode 100644
index 000000000..a44287cd9
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.Solr/CommunityToolkit.Aspire.Hosting.Solr.csproj
@@ -0,0 +1,9 @@
+
+
+ A .NET Aspire hosting integration for Apache Solr.
+ solr search hosting
+
+
+
+
+
diff --git a/src/CommunityToolkit.Aspire.Hosting.Solr/README.md b/src/CommunityToolkit.Aspire.Hosting.Solr/README.md
new file mode 100644
index 000000000..e961c4390
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.Solr/README.md
@@ -0,0 +1,42 @@
+# CommunityToolkit.Aspire.Hosting.Solr
+
+This package provides a .NET Aspire hosting integration for [Apache Solr](https://solr.apache.org/), enabling you to add and configure a Solr container as part of your distributed application.
+
+## Getting Started
+
+### Install the package
+
+In your AppHost project, install the package using the following command:
+
+```dotnetcli
+dotnet add package CommunityToolkit.Aspire.Hosting.Solr
+```
+
+## Usage Example
+
+```csharp
+var builder = DistributedApplication.CreateBuilder(args);
+
+// Add Solr resource with default settings (port 8983, core "solr")
+var solr = builder.AddSolr("solr");
+
+// Add Solr with custom port
+var solrWithCustomPort = builder.AddSolr("solr-custom", port: 8984);
+
+// Add Solr with custom core name
+var solrWithCustomCore = builder.AddSolr("solr-core", coreName: "mycore");
+
+// Add Solr with both custom port and core name
+var solrCustom = builder.AddSolr("solr-full", port: 8985, coreName: "documents");
+
+// Reference the Solr resource in a project
+var exampleProject = builder.AddProject()
+ .WithReference(solr);
+
+// Initialize and run the application
+builder.Build().Run();
+```
+
+## Feedback & contributing
+
+https://github.com/dotnet/aspire
diff --git a/src/CommunityToolkit.Aspire.Hosting.Solr/SolrBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.Solr/SolrBuilderExtensions.cs
new file mode 100644
index 000000000..b31e50bff
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.Solr/SolrBuilderExtensions.cs
@@ -0,0 +1,49 @@
+using Aspire.Hosting.ApplicationModel;
+using CommunityToolkit.Aspire.Hosting.Solr;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Aspire.Hosting;
+
+///
+/// Extension methods for adding and configuring a Solr resource.
+///
+public static class SolrBuilderExtensions
+{
+ ///
+ /// Adds an Apache Solr container resource to the distributed application.
+ ///
+ /// The .
+ /// The name of the resource. This name will be used as the connection string name when referenced in a dependency.
+ /// The host port for Solr.
+ /// The name of the core to create.
+ /// A reference to the .
+ public static IResourceBuilder AddSolr(this IDistributedApplicationBuilder builder, [ResourceName] string name, int? port = null, string? coreName = null)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentException.ThrowIfNullOrEmpty(name);
+
+ coreName ??= "solr";
+
+ var resource = new SolrResource(name, coreName);
+
+ var solrBuilder = builder.AddResource(resource)
+ .WithImage(SolrContainerImageTags.Image, SolrContainerImageTags.Tag)
+ .WithImageRegistry(SolrContainerImageTags.Registry)
+ .WithHttpEndpoint(targetPort: 8983, port: port, name: SolrResource.PrimaryEndpointName)
+ .WithArgs("solr-precreate", coreName);
+
+ string healthCheckKey = $"{name}_check";
+ var endpoint = solrBuilder.Resource.GetEndpoint(SolrResource.PrimaryEndpointName);
+
+ builder.Services.AddHealthChecks()
+ .AddUrlGroup(options =>
+ {
+ var uri = new Uri(endpoint.Url);
+ options.AddUri(new Uri(uri, $"solr/{coreName}/admin/ping"), setup => setup.ExpectHttpCode(200));
+ }, healthCheckKey);
+
+ solrBuilder.WithHealthCheck(healthCheckKey);
+
+ return solrBuilder;
+ }
+}
diff --git a/src/CommunityToolkit.Aspire.Hosting.Solr/SolrContainerImageTags.cs b/src/CommunityToolkit.Aspire.Hosting.Solr/SolrContainerImageTags.cs
new file mode 100644
index 000000000..2630e589f
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.Solr/SolrContainerImageTags.cs
@@ -0,0 +1,11 @@
+namespace CommunityToolkit.Aspire.Hosting.Solr;
+
+internal static class SolrContainerImageTags
+{
+ /// docker.io
+ public const string Registry = "docker.io";
+ /// solr
+ public const string Image = "solr";
+ /// 9.7
+ public const string Tag = "9.7";
+}
diff --git a/src/CommunityToolkit.Aspire.Hosting.Solr/SolrResource.cs b/src/CommunityToolkit.Aspire.Hosting.Solr/SolrResource.cs
new file mode 100644
index 000000000..714663a0c
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.Solr/SolrResource.cs
@@ -0,0 +1,32 @@
+using Aspire.Hosting.ApplicationModel;
+
+namespace Aspire.Hosting.ApplicationModel;
+
+///
+/// Represents an Apache Solr container resource.
+///
+/// The name of the resource.
+/// The name of the Solr core.
+public class SolrResource(string name, string coreName) : ContainerResource(name), IResourceWithConnectionString
+{
+ internal const string PrimaryEndpointName = "http";
+
+ private EndpointReference? _primaryEndpoint;
+
+ ///
+ /// The Solr core name.
+ ///
+ public string CoreName { get; set; } = coreName;
+
+ ///
+ /// Gets the primary endpoint for the Solr server.
+ ///
+ public EndpointReference PrimaryEndpoint => _primaryEndpoint ??= new(this, PrimaryEndpointName);
+
+ ///
+ /// Gets the connection string expression for the Solr server.
+ ///
+ public ReferenceExpression ConnectionStringExpression => ReferenceExpression.Create(
+ $"http://{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}/solr/{CoreName}");
+
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Aspire.Hosting.Solr/api/CommunityToolkit.Aspire.Hosting.Solr.cs b/src/CommunityToolkit.Aspire.Hosting.Solr/api/CommunityToolkit.Aspire.Hosting.Solr.cs
new file mode 100644
index 000000000..4ef5b4963
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.Solr/api/CommunityToolkit.Aspire.Hosting.Solr.cs
@@ -0,0 +1,25 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+namespace Aspire.Hosting
+{
+ public static partial class SolrBuilderExtensions
+ {
+ public static ApplicationModel.IResourceBuilder AddSolr(this IDistributedApplicationBuilder builder, string name, int? port = null, string? coreName = null) { throw null; }
+ }
+}
+namespace Aspire.Hosting.ApplicationModel
+{
+ public partial class SolrResource : ContainerResource, IResourceWithConnectionString, IResource, IManifestExpressionProvider, IValueProvider, IValueWithReferences
+ {
+ public SolrResource(string name, string coreName) : base(default!, default) { }
+ public string CoreName { get { throw null; } set { } }
+ public ReferenceExpression ConnectionStringExpression { get { throw null; } }
+ public EndpointReference PrimaryEndpoint { get { throw null; } }
+ }
+}
diff --git a/tests/CommunityToolkit.Aspire.Hosting.Solr.Tests/AppHostTests.cs b/tests/CommunityToolkit.Aspire.Hosting.Solr.Tests/AppHostTests.cs
new file mode 100644
index 000000000..affe52808
--- /dev/null
+++ b/tests/CommunityToolkit.Aspire.Hosting.Solr.Tests/AppHostTests.cs
@@ -0,0 +1,62 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Net;
+using Aspire.Components.Common.Tests;
+using CommunityToolkit.Aspire.Testing;
+
+namespace CommunityToolkit.Aspire.Hosting.Solr.Tests;
+
+[RequiresDocker]
+public class AppHostTests(AspireIntegrationTestFixture fixture) : IClassFixture>
+{
+ [Fact]
+ public async Task SolrResourceStartsAndRespondsOk()
+ {
+ var resourceName = "solr";
+ await fixture.ResourceNotificationService.WaitForResourceHealthyAsync(resourceName).WaitAsync(TimeSpan.FromMinutes(5));
+ var httpClient = fixture.CreateHttpClient(resourceName);
+
+ var response = await httpClient.GetAsync("/solr/");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task SolrResourceWithCustomPortStartsAndRespondsOk()
+ {
+ var resourceName = "solr-custom";
+ await fixture.ResourceNotificationService.WaitForResourceHealthyAsync(resourceName).WaitAsync(TimeSpan.FromMinutes(5));
+ var httpClient = fixture.CreateHttpClient(resourceName);
+
+ var response = await httpClient.GetAsync("/solr/");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task SolrCoreIsHealthy()
+ {
+ var resourceName = "solr";
+ await fixture.ResourceNotificationService.WaitForResourceHealthyAsync(resourceName).WaitAsync(TimeSpan.FromMinutes(5));
+ var httpClient = fixture.CreateHttpClient(resourceName);
+
+ // Test that the specific core admin ping endpoint works
+ var response = await httpClient.GetAsync("/solr/solr/admin/ping");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task SolrCustomCoreIsHealthy()
+ {
+ var resourceName = "solr-custom";
+ await fixture.ResourceNotificationService.WaitForResourceHealthyAsync(resourceName).WaitAsync(TimeSpan.FromMinutes(5));
+ var httpClient = fixture.CreateHttpClient(resourceName);
+
+ // Test that the custom core admin ping endpoint works
+ var response = await httpClient.GetAsync("/solr/mycore/admin/ping");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+}
diff --git a/tests/CommunityToolkit.Aspire.Hosting.Solr.Tests/CommunityToolkit.Aspire.Hosting.Solr.Tests.csproj b/tests/CommunityToolkit.Aspire.Hosting.Solr.Tests/CommunityToolkit.Aspire.Hosting.Solr.Tests.csproj
new file mode 100644
index 000000000..034e6245f
--- /dev/null
+++ b/tests/CommunityToolkit.Aspire.Hosting.Solr.Tests/CommunityToolkit.Aspire.Hosting.Solr.Tests.csproj
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/tests/CommunityToolkit.Aspire.Hosting.Solr.Tests/SolrResourceTests.cs b/tests/CommunityToolkit.Aspire.Hosting.Solr.Tests/SolrResourceTests.cs
new file mode 100644
index 000000000..6e79a0d6b
--- /dev/null
+++ b/tests/CommunityToolkit.Aspire.Hosting.Solr.Tests/SolrResourceTests.cs
@@ -0,0 +1,90 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Aspire.Hosting;
+using Aspire.Hosting.ApplicationModel;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace CommunityToolkit.Aspire.Hosting.Solr.Tests;
+
+public class SolrResourceTests
+{
+ [Fact]
+ public void AddSolr_CreatesCorrectResource()
+ {
+ var builder = DistributedApplication.CreateBuilder();
+ var solr = builder.AddSolr("solr");
+
+ Assert.NotNull(solr.Resource);
+ Assert.Equal("solr", solr.Resource.Name);
+ Assert.IsType(solr.Resource);
+ Assert.Equal("solr", solr.Resource.CoreName);
+ }
+
+ [Fact]
+ public void AddSolr_WithCustomCoreName_ConfiguresCorrectly()
+ {
+ var builder = DistributedApplication.CreateBuilder();
+ var solr = builder.AddSolr("solr", coreName: "mycustomcore");
+
+ Assert.NotNull(solr.Resource);
+ Assert.Equal("solr", solr.Resource.Name);
+ Assert.Equal("mycustomcore", solr.Resource.CoreName);
+ }
+
+ [Fact]
+ public void AddSolr_WithPort_ConfiguresCorrectly()
+ {
+ var builder = DistributedApplication.CreateBuilder();
+ var solr = builder.AddSolr("solr", port: 8984);
+
+ Assert.NotNull(solr.Resource);
+ Assert.Equal("solr", solr.Resource.Name);
+ Assert.Equal("solr", solr.Resource.CoreName);
+ }
+
+ [Fact]
+ public void AddSolr_WithPortAndCoreName_ConfiguresCorrectly()
+ {
+ var builder = DistributedApplication.CreateBuilder();
+ var solr = builder.AddSolr("solr", port: 8984, coreName: "testcore");
+
+ Assert.NotNull(solr.Resource);
+ Assert.Equal("solr", solr.Resource.Name);
+ Assert.Equal("testcore", solr.Resource.CoreName);
+ }
+
+ [Fact]
+ public void SolrResource_HasCorrectConnectionString()
+ {
+ var builder = DistributedApplication.CreateBuilder();
+ var solr = builder.AddSolr("solr", coreName: "mycore");
+
+ Assert.NotNull(solr.Resource.ConnectionStringExpression);
+ // Connection string should contain the core name
+ var connectionString = solr.Resource.ConnectionStringExpression.ValueExpression;
+ Assert.Contains("mycore", connectionString);
+ Assert.Contains("/solr/mycore", connectionString);
+ }
+
+ [Fact]
+ public void SolrResource_HasHealthCheck()
+ {
+ var builder = DistributedApplication.CreateBuilder();
+ var solr = builder.AddSolr("solr", coreName: "testcore");
+
+ using var app = builder.Build();
+ var healthChecks = app.Services.GetRequiredService();
+ Assert.NotNull(healthChecks);
+ }
+
+ [Fact]
+ public void SolrResource_DefaultCoreName_IsSolr()
+ {
+ var builder = DistributedApplication.CreateBuilder();
+ var solr = builder.AddSolr("solr");
+
+ Assert.Equal("solr", solr.Resource.CoreName);
+ }
+}