diff --git a/Directory.Packages.props b/Directory.Packages.props
index 95abd06f2..32f9266d8 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -47,6 +47,7 @@
+
diff --git a/Testcontainers.dic b/Testcontainers.dic
index fd74cff87..ff106d7dd 100644
--- a/Testcontainers.dic
+++ b/Testcontainers.dic
@@ -3,6 +3,7 @@ azurecr
azurite
buildx
capi
+chromadb
creds
dind
dockerignore
diff --git a/Testcontainers.sln.DotSettings b/Testcontainers.sln.DotSettings
index f2463e9cf..a56b93c47 100644
--- a/Testcontainers.sln.DotSettings
+++ b/Testcontainers.sln.DotSettings
@@ -11,6 +11,7 @@
True
True
True
+ True
True
True
True
diff --git a/Testcontainers.slnx b/Testcontainers.slnx
index 77ffa7c4e..0f4c1947f 100644
--- a/Testcontainers.slnx
+++ b/Testcontainers.slnx
@@ -23,6 +23,7 @@
+
@@ -91,6 +92,7 @@
+
diff --git a/docs/modules/chroma.md b/docs/modules/chroma.md
new file mode 100644
index 000000000..c515ab735
--- /dev/null
+++ b/docs/modules/chroma.md
@@ -0,0 +1,31 @@
+# Chroma
+
+[Chroma](https://www.trychroma.com/) is an open-source vector database for AI applications. It stores embeddings with their documents and metadata, and finds the nearest ones to a query, through a REST API.
+
+Add the following dependency to your project file:
+
+```shell title="NuGet"
+dotnet add package Testcontainers.Chroma
+```
+
+You can start a Chroma container instance from any .NET application. This example uses xUnit.net's `IAsyncLifetime` interface to manage the lifecycle of the container. The container is started in the `InitializeAsync` method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the `DisposeAsync` method.
+
+=== "Usage Example"
+ ```csharp
+ --8<-- "tests/Testcontainers.Chroma.Tests/ChromaDefaultContainerTest.cs:UseChromaContainer"
+ ```
+
+The test example uses the following NuGet dependencies:
+
+=== "Package References"
+ ```xml
+ --8<-- "tests/Testcontainers.Chroma.Tests/Testcontainers.Chroma.Tests.csproj:PackageReferences"
+ ```
+
+To execute the tests, use the command `dotnet test` from a terminal.
+
+--8<-- "docs/modules/_call_out_test_projects.txt"
+
+## Chroma versions
+
+The container is ready when the heartbeat of the Chroma API answers. Chroma 0.5.16 and later answer the heartbeat of the v2 API, and the earlier releases have only the v1 API, so the module asks both: it works with any Chroma image, whatever its tag.
diff --git a/docs/modules/index.md b/docs/modules/index.md
index 9b7f801be..06e1ec25e 100644
--- a/docs/modules/index.md
+++ b/docs/modules/index.md
@@ -31,6 +31,7 @@ await moduleNameContainer.StartAsync();
| BigQuery | `ghcr.io/goccy/bigquery-emulator:0.4` | [NuGet](https://www.nuget.org/packages/Testcontainers.BigQuery) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.BigQuery) |
| Bigtable | `gcr.io/google.com/cloudsdktool/google-cloud-cli:446.0.1-emulators` | [NuGet](https://www.nuget.org/packages/Testcontainers.Bigtable) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Bigtable) |
| Cassandra | `library/cassandra:5.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.Cassandra) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Cassandra) |
+| Chroma | `chromadb/chroma:1.5.9` | [NuGet](https://www.nuget.org/packages/Testcontainers.Chroma) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Chroma) |
| ClickHouse | `clickhouse/clickhouse-server:23.6-alpine` | [NuGet](https://www.nuget.org/packages/Testcontainers.ClickHouse) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.ClickHouse) |
| CockroachDB | `cockroachdb:23.1.13` | [NuGet](https://www.nuget.org/packages/Testcontainers.CockroachDb) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.CockroachDb) |
| Consul | `consul:1.15` | [NuGet](https://www.nuget.org/packages/Testcontainers.Consul) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Consul) |
diff --git a/mkdocs.yml b/mkdocs.yml
index 615b4d4bf..5501d9c85 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -61,6 +61,7 @@ nav:
- modules/aspire-dashboard.md
- modules/eventhubs.md # Azure
- modules/servicebus.md # Azure
+ - modules/chroma.md
- modules/clickhouse.md
- modules/db2.md
- modules/elasticsearch.md
diff --git a/src/Testcontainers.Chroma/.editorconfig b/src/Testcontainers.Chroma/.editorconfig
new file mode 100644
index 000000000..6f066619d
--- /dev/null
+++ b/src/Testcontainers.Chroma/.editorconfig
@@ -0,0 +1 @@
+root = true
\ No newline at end of file
diff --git a/src/Testcontainers.Chroma/ChromaBuilder.cs b/src/Testcontainers.Chroma/ChromaBuilder.cs
new file mode 100644
index 000000000..ed9ff11ac
--- /dev/null
+++ b/src/Testcontainers.Chroma/ChromaBuilder.cs
@@ -0,0 +1,124 @@
+namespace Testcontainers.Chroma;
+
+///
+[PublicAPI]
+public sealed class ChromaBuilder : ContainerBuilder
+{
+ [Obsolete("This constant is obsolete and will be removed in the future. Use the constructor with the image parameter instead: https://github.com/testcontainers/testcontainers-dotnet/discussions/1470#discussioncomment-15185721.")]
+ public const string ChromaImage = "chromadb/chroma:1.5.9";
+
+ public const ushort ChromaHttpPort = 8000;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ [Obsolete("This parameterless constructor is obsolete and will be removed. Use the constructor with the image parameter instead: https://github.com/testcontainers/testcontainers-dotnet/discussions/1470#discussioncomment-15185721.")]
+ [ExcludeFromCodeCoverage]
+ public ChromaBuilder()
+ : this(ChromaImage)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The full Docker image name, including the image repository and tag
+ /// (e.g., chromadb/chroma:1.5.9).
+ ///
+ ///
+ /// Docker image tags available at .
+ ///
+ public ChromaBuilder(string image)
+ : this(new DockerImage(image))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// An instance that specifies the Docker image to be used
+ /// for the container builder configuration.
+ ///
+ ///
+ /// Docker image tags available at .
+ ///
+ public ChromaBuilder(IImage image)
+ : this(new ChromaConfiguration())
+ {
+ DockerResourceConfiguration = Init().WithImage(image).DockerResourceConfiguration;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ private ChromaBuilder(ChromaConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ DockerResourceConfiguration = resourceConfiguration;
+ }
+
+ ///
+ protected override ChromaConfiguration DockerResourceConfiguration { get; }
+
+ ///
+ public override ChromaContainer Build()
+ {
+ Validate();
+ return new ChromaContainer(DockerResourceConfiguration);
+ }
+
+ ///
+ protected override ChromaBuilder Init()
+ {
+ return base.Init()
+ .WithPortBinding(ChromaHttpPort, true)
+ .WithConnectionStringProvider(new ChromaConnectionStringProvider())
+ .WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil()));
+ }
+
+ ///
+ protected override ChromaBuilder Clone(IResourceConfiguration resourceConfiguration)
+ {
+ return Merge(DockerResourceConfiguration, new ChromaConfiguration(resourceConfiguration));
+ }
+
+ ///
+ protected override ChromaBuilder Clone(IContainerConfiguration resourceConfiguration)
+ {
+ return Merge(DockerResourceConfiguration, new ChromaConfiguration(resourceConfiguration));
+ }
+
+ ///
+ protected override ChromaBuilder Merge(ChromaConfiguration oldValue, ChromaConfiguration newValue)
+ {
+ return new ChromaBuilder(new ChromaConfiguration(oldValue, newValue));
+ }
+
+ ///
+ ///
+ /// Chroma is ready when its heartbeat answers. Chroma 0.5.16 and later answer the heartbeat of the v2 API,
+ /// the earlier releases have only the v1 API, and Chroma 1.x answers the v1 API with 410 Gone.
+ /// Asking both works on every release, without reading the version from the image tag.
+ ///
+ private sealed class WaitUntil : IWaitUntil
+ {
+ private static readonly IWaitUntil V2Heartbeat = new HttpWaitStrategy()
+ .ForPort(ChromaHttpPort)
+ .ForPath("/api/v2/heartbeat");
+
+ private static readonly IWaitUntil V1Heartbeat = new HttpWaitStrategy()
+ .ForPort(ChromaHttpPort)
+ .ForPath("/api/v1/heartbeat");
+
+ ///
+ public async Task UntilAsync(IContainer container)
+ {
+ return await V2Heartbeat.UntilAsync(container)
+ .ConfigureAwait(false) || await V1Heartbeat.UntilAsync(container)
+ .ConfigureAwait(false);
+ }
+ }
+}
diff --git a/src/Testcontainers.Chroma/ChromaConfiguration.cs b/src/Testcontainers.Chroma/ChromaConfiguration.cs
new file mode 100644
index 000000000..1029b47a2
--- /dev/null
+++ b/src/Testcontainers.Chroma/ChromaConfiguration.cs
@@ -0,0 +1,53 @@
+namespace Testcontainers.Chroma;
+
+///
+[PublicAPI]
+public sealed class ChromaConfiguration : ContainerConfiguration
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ChromaConfiguration()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public ChromaConfiguration(IResourceConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ // Passes the configuration upwards to the base implementations to create an updated immutable copy.
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public ChromaConfiguration(IContainerConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ // Passes the configuration upwards to the base implementations to create an updated immutable copy.
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public ChromaConfiguration(ChromaConfiguration resourceConfiguration)
+ : this(new ChromaConfiguration(), resourceConfiguration)
+ {
+ // Passes the configuration upwards to the base implementations to create an updated immutable copy.
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The old Docker resource configuration.
+ /// The new Docker resource configuration.
+ public ChromaConfiguration(ChromaConfiguration oldValue, ChromaConfiguration newValue)
+ : base(oldValue, newValue)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Testcontainers.Chroma/ChromaConnectionStringProvider.cs b/src/Testcontainers.Chroma/ChromaConnectionStringProvider.cs
new file mode 100644
index 000000000..b13a2eca6
--- /dev/null
+++ b/src/Testcontainers.Chroma/ChromaConnectionStringProvider.cs
@@ -0,0 +1,13 @@
+namespace Testcontainers.Chroma;
+
+///
+/// Provides the Chroma connection string.
+///
+internal sealed class ChromaConnectionStringProvider : ContainerConnectionStringProvider
+{
+ ///
+ protected override string GetHostConnectionString()
+ {
+ return Container.GetBaseAddress();
+ }
+}
diff --git a/src/Testcontainers.Chroma/ChromaContainer.cs b/src/Testcontainers.Chroma/ChromaContainer.cs
new file mode 100644
index 000000000..c765723dc
--- /dev/null
+++ b/src/Testcontainers.Chroma/ChromaContainer.cs
@@ -0,0 +1,24 @@
+namespace Testcontainers.Chroma;
+
+///
+[PublicAPI]
+public sealed class ChromaContainer : DockerContainer
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The container configuration.
+ public ChromaContainer(ChromaConfiguration configuration)
+ : base(configuration)
+ {
+ }
+
+ ///
+ /// Gets the base address of the Chroma HTTP API.
+ ///
+ /// The base address of the Chroma HTTP API, like http://localhost:32768/.
+ public string GetBaseAddress()
+ {
+ return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(ChromaBuilder.ChromaHttpPort)).ToString();
+ }
+}
diff --git a/src/Testcontainers.Chroma/Testcontainers.Chroma.csproj b/src/Testcontainers.Chroma/Testcontainers.Chroma.csproj
new file mode 100644
index 000000000..32fd35b92
--- /dev/null
+++ b/src/Testcontainers.Chroma/Testcontainers.Chroma.csproj
@@ -0,0 +1,12 @@
+
+
+ net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1
+ latest
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Testcontainers.Chroma/Usings.cs b/src/Testcontainers.Chroma/Usings.cs
new file mode 100644
index 000000000..c5e730d6b
--- /dev/null
+++ b/src/Testcontainers.Chroma/Usings.cs
@@ -0,0 +1,9 @@
+global using System;
+global using System.Diagnostics.CodeAnalysis;
+global using System.Threading.Tasks;
+global using Docker.DotNet.Models;
+global using DotNet.Testcontainers.Builders;
+global using DotNet.Testcontainers.Configurations;
+global using DotNet.Testcontainers.Containers;
+global using DotNet.Testcontainers.Images;
+global using JetBrains.Annotations;
\ No newline at end of file
diff --git a/tests/Testcontainers.Chroma.Tests/.editorconfig b/tests/Testcontainers.Chroma.Tests/.editorconfig
new file mode 100644
index 000000000..6f066619d
--- /dev/null
+++ b/tests/Testcontainers.Chroma.Tests/.editorconfig
@@ -0,0 +1 @@
+root = true
\ No newline at end of file
diff --git a/tests/Testcontainers.Chroma.Tests/.runs-on b/tests/Testcontainers.Chroma.Tests/.runs-on
new file mode 100644
index 000000000..d0395e498
--- /dev/null
+++ b/tests/Testcontainers.Chroma.Tests/.runs-on
@@ -0,0 +1 @@
+ubuntu-24.04
\ No newline at end of file
diff --git a/tests/Testcontainers.Chroma.Tests/ChromaDefaultContainerTest.cs b/tests/Testcontainers.Chroma.Tests/ChromaDefaultContainerTest.cs
new file mode 100644
index 000000000..ef6daaea3
--- /dev/null
+++ b/tests/Testcontainers.Chroma.Tests/ChromaDefaultContainerTest.cs
@@ -0,0 +1,59 @@
+namespace Testcontainers.Chroma;
+
+public sealed class ChromaDefaultContainerTest : IAsyncLifetime
+{
+ // # --8<-- [start:UseChromaContainer]
+ private readonly ChromaContainer _chromaContainer = new ChromaBuilder(TestSession.GetImageFromDockerfile()).Build();
+
+ public async ValueTask InitializeAsync()
+ {
+ await _chromaContainer.StartAsync()
+ .ConfigureAwait(false);
+ }
+
+ public ValueTask DisposeAsync()
+ {
+ return _chromaContainer.DisposeAsync();
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task QueryReturnsNearestRecord()
+ {
+ // Given
+ using var client = new ChromaClient(_chromaContainer.GetConnectionString());
+
+ var collection = await client.CreateCollectionAsync("documents", cancellationToken: TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ var collectionClient = client.GetCollectionClient(collection);
+
+ await collectionClient.AddAsync(["a", "b"], [new[] { 1f, 0f }, new[] { 0f, 1f }], cancellationToken: TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ // When
+ var results = await collectionClient.QueryAsync(new[] { 0.9f, 0.1f }, nResults: 1, cancellationToken: TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ // Then
+ Assert.Equal("a", Assert.Single(results).Id);
+ }
+ // # --8<-- [end:UseChromaContainer]
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task GetHeartbeatReturnsHttpStatusCodeOk()
+ {
+ // Given
+ using var httpClient = new HttpClient();
+ httpClient.BaseAddress = new Uri(_chromaContainer.GetBaseAddress());
+
+ // When
+ using var httpResponse = await httpClient.GetAsync("/api/v2/heartbeat", TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ // Then
+ Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
+ Assert.Equal(_chromaContainer.GetBaseAddress(), _chromaContainer.GetConnectionString());
+ }
+}
diff --git a/tests/Testcontainers.Chroma.Tests/ChromaV1ContainerTest.cs b/tests/Testcontainers.Chroma.Tests/ChromaV1ContainerTest.cs
new file mode 100644
index 000000000..6f582a3db
--- /dev/null
+++ b/tests/Testcontainers.Chroma.Tests/ChromaV1ContainerTest.cs
@@ -0,0 +1,35 @@
+namespace Testcontainers.Chroma;
+
+// Chroma 0.5.15 is the last release with only the v1 API: the container must be ready
+// although the heartbeat of the v2 API does not answer.
+public sealed class ChromaV1ContainerTest : IAsyncLifetime
+{
+ private readonly ChromaContainer _chromaContainer = new ChromaBuilder(TestSession.GetImageFromDockerfile(stage: "v1")).Build();
+
+ public async ValueTask InitializeAsync()
+ {
+ await _chromaContainer.StartAsync()
+ .ConfigureAwait(false);
+ }
+
+ public ValueTask DisposeAsync()
+ {
+ return _chromaContainer.DisposeAsync();
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task GetV1HeartbeatReturnsHttpStatusCodeOk()
+ {
+ // Given
+ using var httpClient = new HttpClient();
+ httpClient.BaseAddress = new Uri(_chromaContainer.GetBaseAddress());
+
+ // When
+ using var httpResponse = await httpClient.GetAsync("/api/v1/heartbeat", TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ // Then
+ Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
+ }
+}
diff --git a/tests/Testcontainers.Chroma.Tests/Dockerfile b/tests/Testcontainers.Chroma.Tests/Dockerfile
new file mode 100644
index 000000000..0d561f780
--- /dev/null
+++ b/tests/Testcontainers.Chroma.Tests/Dockerfile
@@ -0,0 +1,2 @@
+FROM chromadb/chroma:1.5.9@sha256:1e0b73a187a28757c572acba508c46f48c9e8b0acaf5c20e6d95cdedce1acdf6
+FROM chromadb/chroma:0.5.15@sha256:7c44cf61eb3f210fa8641c6c943d5621b9626ce863d89247183ffde37717e576 AS v1
\ No newline at end of file
diff --git a/tests/Testcontainers.Chroma.Tests/Testcontainers.Chroma.Tests.csproj b/tests/Testcontainers.Chroma.Tests/Testcontainers.Chroma.Tests.csproj
new file mode 100644
index 000000000..ced5f78de
--- /dev/null
+++ b/tests/Testcontainers.Chroma.Tests/Testcontainers.Chroma.Tests.csproj
@@ -0,0 +1,26 @@
+
+
+ net10.0
+ false
+ false
+ Exe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
\ No newline at end of file
diff --git a/tests/Testcontainers.Chroma.Tests/Usings.cs b/tests/Testcontainers.Chroma.Tests/Usings.cs
new file mode 100644
index 000000000..7ae3d3e25
--- /dev/null
+++ b/tests/Testcontainers.Chroma.Tests/Usings.cs
@@ -0,0 +1,7 @@
+global using System;
+global using System.Net;
+global using System.Net.Http;
+global using System.Threading.Tasks;
+global using ChromaDB.Client;
+global using DotNet.Testcontainers.Commons;
+global using Xunit;
\ No newline at end of file