diff --git a/Testcontainers.dic b/Testcontainers.dic
index fd74cff87..121faa3d8 100644
--- a/Testcontainers.dic
+++ b/Testcontainers.dic
@@ -19,6 +19,7 @@ lipsum
ltsc
memopt
mongosh
+mongot
mosquitto
mycounter
mydatabase
diff --git a/Testcontainers.sln.DotSettings b/Testcontainers.sln.DotSettings
index f2463e9cf..b216507ab 100644
--- a/Testcontainers.sln.DotSettings
+++ b/Testcontainers.sln.DotSettings
@@ -27,6 +27,7 @@
True
True
True
+ True
True
True
True
diff --git a/Testcontainers.slnx b/Testcontainers.slnx
index 77ffa7c4e..5ee90858d 100644
--- a/Testcontainers.slnx
+++ b/Testcontainers.slnx
@@ -52,6 +52,7 @@
+
@@ -121,6 +122,7 @@
+
diff --git a/docs/modules/index.md b/docs/modules/index.md
index 9b7f801be..9c2b4ffe4 100644
--- a/docs/modules/index.md
+++ b/docs/modules/index.md
@@ -59,6 +59,7 @@ await moduleNameContainer.StartAsync();
| Milvus | `milvusdb/milvus:v2.3.10` | [NuGet](https://www.nuget.org/packages/Testcontainers.Milvus) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Milvus) |
| MinIO | `minio/minio:RELEASE.2023-01-31T02-24-19Z` | [NuGet](https://www.nuget.org/packages/Testcontainers.Minio) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Minio) |
| MongoDB | `mongo:6.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.MongoDb) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.MongoDb) |
+| MongoDB Atlas Local | `mongodb/mongodb-atlas-local:8.0.32` | [NuGet](https://www.nuget.org/packages/Testcontainers.MongoDbAtlasLocal) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.MongoDbAtlasLocal) |
| Mosquitto | `eclipse-mosquitto:2.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.Mosquitto) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Mosquitto) |
| MySQL | `mysql:8.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.MySql) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.MySql) |
| NATS | `nats:2.9` | [NuGet](https://www.nuget.org/packages/Testcontainers.Nats) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Nats) |
diff --git a/docs/modules/mongodb-atlas-local.md b/docs/modules/mongodb-atlas-local.md
new file mode 100644
index 000000000..39ea81589
--- /dev/null
+++ b/docs/modules/mongodb-atlas-local.md
@@ -0,0 +1,61 @@
+# MongoDB Atlas Local
+
+[MongoDB Atlas Local](https://www.mongodb.com/docs/atlas/cli/current/atlas-cli-deploy-docker/) runs a local Atlas deployment in a single container. Next to MongoDB, it includes the Atlas Search process, so features like [Atlas Search](https://www.mongodb.com/docs/atlas/atlas-search/) (`$search`) and [Atlas Vector Search](https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-overview/) (`$vectorSearch`) can be tested without an Atlas cluster.
+
+Add the following dependency to your project file:
+
+```shell title="NuGet"
+dotnet add package Testcontainers.MongoDbAtlasLocal
+```
+
+You can start a MongoDB Atlas Local container instance from any .NET application. Here, we create different container instances and pass them to the base test class. This allows us to test different configurations. Authentication is disabled by default. Set a username and a password to enable it.
+
+=== "Create Container Instance"
+ ```csharp
+ --8<-- "tests/Testcontainers.MongoDbAtlasLocal.Tests/MongoDbAtlasLocalContainerTest.cs:CreateMongoDbAtlasLocalContainer"
+ ```
+
+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.MongoDbAtlasLocal.Tests/MongoDbAtlasLocalContainerTest.cs:UseMongoDbAtlasLocalContainer"
+ ```
+
+=== "Atlas Search Helper"
+ ```csharp
+ --8<-- "tests/Testcontainers.MongoDbAtlasLocal.Tests/AtlasSearch.cs"
+ ```
+
+The test example uses the following NuGet dependencies:
+
+=== "Package References"
+ ```xml
+ --8<-- "tests/Testcontainers.MongoDbAtlasLocal.Tests/Testcontainers.MongoDbAtlasLocal.Tests.csproj:PackageReferences"
+ ```
+
+To execute the tests, use the command `dotnet test` from a terminal.
+
+--8<-- "docs/modules/_call_out_test_projects.txt"
+
+!!! note
+
+ Atlas Search builds indexes asynchronously. A search index becomes queryable shortly after it has been created, and newly written documents become searchable shortly after they have been written. Wait until the index reports `queryable: true` before running search queries, and retry a query until it returns the documents you expect, as the Atlas Search helper above does.
+
+## Seeding the deployment
+
+Init scripts seed the deployment on its first start. `WithInitScript(string)` copies a script file from the test host, and `WithInitScriptContent(string, string)` creates one from a string. JavaScript (`.js`) scripts run in `mongosh` against the database set with `WithInitDatabase(string)` (default `test`), and shell (`.sh`) scripts run in `bash`. The container silently skips files with any other extension, so the builder rejects them. Scripts run in alphabetical order of their file names and finish before the container is reported ready. They can also create Atlas Search indexes. A restarted or reused container keeps its data and does not run the scripts again.
+
+=== "Seed Configuration"
+ ```csharp
+ --8<-- "tests/Testcontainers.MongoDbAtlasLocal.Tests/MongoDbAtlasLocalSeedTest.cs:SeedMongoDbAtlasLocalContainer"
+ ```
+
+=== "Seed Script"
+ ```javascript
+ --8<-- "tests/Testcontainers.MongoDbAtlasLocal.Tests/Seed/01-movies.js"
+ ```
+
+## Telemetry
+
+The MongoDB Atlas Local image sends telemetry to MongoDB by default. Call `WithNoTelemetry()` to disable it, as shown in the seed configuration above.
diff --git a/mkdocs.yml b/mkdocs.yml
index a127856f6..e0b618b23 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -68,6 +68,7 @@ nav:
- modules/garnet.md
- modules/grafana.md
- modules/mongodb.md
+ - modules/mongodb-atlas-local.md
- modules/mssql.md
- modules/neo4j.md
- modules/opensearch.md
diff --git a/src/Testcontainers.MongoDbAtlasLocal/.editorconfig b/src/Testcontainers.MongoDbAtlasLocal/.editorconfig
new file mode 100644
index 000000000..6f066619d
--- /dev/null
+++ b/src/Testcontainers.MongoDbAtlasLocal/.editorconfig
@@ -0,0 +1 @@
+root = true
\ No newline at end of file
diff --git a/src/Testcontainers.MongoDbAtlasLocal/MongoDbAtlasLocalBuilder.cs b/src/Testcontainers.MongoDbAtlasLocal/MongoDbAtlasLocalBuilder.cs
new file mode 100644
index 000000000..f0e3e0a78
--- /dev/null
+++ b/src/Testcontainers.MongoDbAtlasLocal/MongoDbAtlasLocalBuilder.cs
@@ -0,0 +1,225 @@
+namespace Testcontainers.MongoDbAtlasLocal;
+
+///
+[PublicAPI]
+public sealed class MongoDbAtlasLocalBuilder : ContainerBuilder
+{
+ public const ushort MongoDbAtlasLocalPort = 27017;
+
+ public const string InitScriptsDirectoryPath = "/docker-entrypoint-initdb.d/";
+
+ private static readonly string[] InitScriptFileExtensions = { ".js", ".sh" };
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The full Docker image name, including the image repository and tag
+ /// (e.g., mongodb/mongodb-atlas-local:8.0.32).
+ ///
+ ///
+ /// Docker image tags available at .
+ ///
+ public MongoDbAtlasLocalBuilder(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 MongoDbAtlasLocalBuilder(IImage image)
+ : this(new MongoDbAtlasLocalConfiguration())
+ {
+ DockerResourceConfiguration = Init().WithImage(image).DockerResourceConfiguration;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ private MongoDbAtlasLocalBuilder(MongoDbAtlasLocalConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ DockerResourceConfiguration = resourceConfiguration;
+ }
+
+ ///
+ protected override MongoDbAtlasLocalConfiguration DockerResourceConfiguration { get; }
+
+ ///
+ /// Sets the MongoDb Atlas Local username.
+ ///
+ ///
+ /// Authentication is disabled by default. Set both the username and the password
+ /// to create a root user and enable authentication.
+ ///
+ /// The MongoDb Atlas Local username.
+ /// A configured instance of .
+ public MongoDbAtlasLocalBuilder WithUsername(string username)
+ {
+ var initDbRootUsername = username ?? string.Empty;
+
+ return Merge(DockerResourceConfiguration, new MongoDbAtlasLocalConfiguration(username: initDbRootUsername))
+ .WithEnvironment("MONGODB_INITDB_ROOT_USERNAME", initDbRootUsername);
+ }
+
+ ///
+ /// Sets the MongoDb Atlas Local password.
+ ///
+ ///
+ /// Authentication is disabled by default. Set both the username and the password
+ /// to create a root user and enable authentication.
+ ///
+ /// The MongoDb Atlas Local password.
+ /// A configured instance of .
+ public MongoDbAtlasLocalBuilder WithPassword(string password)
+ {
+ var initDbRootPassword = password ?? string.Empty;
+
+ return Merge(DockerResourceConfiguration, new MongoDbAtlasLocalConfiguration(password: initDbRootPassword))
+ .WithEnvironment("MONGODB_INITDB_ROOT_PASSWORD", initDbRootPassword);
+ }
+
+ ///
+ /// Sets the database the JavaScript init scripts run against.
+ ///
+ ///
+ /// Defaults to test. The database is not added to the connection string, because MongoDb
+ /// would use it as the authentication database, which does not contain the root user.
+ ///
+ /// The init database.
+ /// A configured instance of .
+ public MongoDbAtlasLocalBuilder WithInitDatabase(string database)
+ {
+ return WithEnvironment("MONGODB_INITDB_DATABASE", database);
+ }
+
+ ///
+ /// Copies an init script to the container to seed the deployment on its first start.
+ ///
+ ///
+ /// JavaScript (.js) scripts run in mongosh against the init database, shell (.sh)
+ /// scripts run in bash. Scripts run in alphabetical order of their file names, before the
+ /// container is reported ready, and only once. A restarted or reused container keeps its data and
+ /// does not run them again. Init scripts can create Atlas Search indexes.
+ ///
+ /// The host path of the init script.
+ /// A configured instance of .
+ /// Thrown when the file name does not end with .js or .sh.
+ public MongoDbAtlasLocalBuilder WithInitScript(string scriptFilePath)
+ {
+ _ = Guard.Argument(scriptFilePath, nameof(scriptFilePath))
+ .NotNull()
+ .NotEmpty();
+
+ ValidateInitScriptFileName(Path.GetFileName(scriptFilePath), nameof(scriptFilePath));
+
+ return WithResourceMapping(FilePath.Of(scriptFilePath), DirectoryPath.Of(InitScriptsDirectoryPath));
+ }
+
+ ///
+ /// Copies an init script to the container to seed the deployment on its first start.
+ ///
+ ///
+ /// See for how init scripts run. The file name's extension
+ /// selects the interpreter, either .js or .sh.
+ ///
+ /// The file name of the init script, e.g. 01-seed.js.
+ /// The content of the init script.
+ /// A configured instance of .
+ /// Thrown when the file name contains a path separator or does not end with .js or .sh.
+ public MongoDbAtlasLocalBuilder WithInitScriptContent(string fileName, string scriptContent)
+ {
+ _ = Guard.Argument(fileName, nameof(fileName))
+ .NotNull()
+ .NotEmpty()
+ .ThrowIf(argument => argument.Value.IndexOfAny(new[] { '/', '\\' }) >= 0, argument => new ArgumentException("The init script file name must not contain a path separator.", argument.Name));
+
+ _ = Guard.Argument(scriptContent, nameof(scriptContent))
+ .NotNull();
+
+ ValidateInitScriptFileName(fileName, nameof(fileName));
+
+ return WithResourceMapping(Encoding.UTF8.GetBytes(scriptContent), FilePath.Of(InitScriptsDirectoryPath + fileName));
+ }
+
+ ///
+ /// Disables the telemetry the MongoDb Atlas Local container sends to MongoDb.
+ ///
+ /// A configured instance of .
+ public MongoDbAtlasLocalBuilder WithNoTelemetry()
+ {
+ return WithEnvironment("DO_NOT_TRACK", "1");
+ }
+
+ ///
+ public override MongoDbAtlasLocalContainer Build()
+ {
+ Validate();
+ return new MongoDbAtlasLocalContainer(DockerResourceConfiguration);
+ }
+
+ ///
+ protected override MongoDbAtlasLocalBuilder Init()
+ {
+ // The runner's healthcheck checks mongod, the replica set, mongot (the Atlas Search
+ // process) and that the init scripts have finished. We run it directly instead of
+ // waiting for the image's HEALTHCHECK, whose first probe runs 30 seconds after start.
+ return base.Init()
+ .WithPortBinding(MongoDbAtlasLocalPort, true)
+ .WithConnectionStringProvider(new MongoDbAtlasLocalConnectionStringProvider())
+ .WithWaitStrategy(Wait.ForUnixContainer().UntilCommandIsCompleted("runner", "healthcheck"));
+ }
+
+ ///
+ protected override void Validate()
+ {
+ const string message = "Missing username or password. Both must be specified for a user to be created.";
+
+ base.Validate();
+
+ _ = Guard.Argument(DockerResourceConfiguration, "Credentials")
+ .ThrowIf(argument => 1.Equals(new[] { argument.Value.Username, argument.Value.Password }.Count(string.IsNullOrWhiteSpace)), argument => new ArgumentException(message, argument.Name));
+ }
+
+ ///
+ /// Validates that the runner executes the init script.
+ ///
+ ///
+ /// The runner only executes files ending with .js or .sh (case-sensitive)
+ /// and silently skips any other file, e.g. seed.json or seed.JS.
+ ///
+ /// The file name of the init script.
+ /// The name of the validated parameter.
+ private static void ValidateInitScriptFileName(string fileName, string parameterName)
+ {
+ _ = Guard.Argument(fileName, parameterName)
+ .ThrowIf(argument => !InitScriptFileExtensions.Any(fileExtension => argument.Value.EndsWith(fileExtension, StringComparison.Ordinal)), argument => new ArgumentException("The init script file name must end with .js or .sh, the container skips any other file.", argument.Name));
+ }
+
+ ///
+ protected override MongoDbAtlasLocalBuilder Clone(IResourceConfiguration resourceConfiguration)
+ {
+ return Merge(DockerResourceConfiguration, new MongoDbAtlasLocalConfiguration(resourceConfiguration));
+ }
+
+ ///
+ protected override MongoDbAtlasLocalBuilder Clone(IContainerConfiguration resourceConfiguration)
+ {
+ return Merge(DockerResourceConfiguration, new MongoDbAtlasLocalConfiguration(resourceConfiguration));
+ }
+
+ ///
+ protected override MongoDbAtlasLocalBuilder Merge(MongoDbAtlasLocalConfiguration oldValue, MongoDbAtlasLocalConfiguration newValue)
+ {
+ return new MongoDbAtlasLocalBuilder(new MongoDbAtlasLocalConfiguration(oldValue, newValue));
+ }
+}
\ No newline at end of file
diff --git a/src/Testcontainers.MongoDbAtlasLocal/MongoDbAtlasLocalConfiguration.cs b/src/Testcontainers.MongoDbAtlasLocal/MongoDbAtlasLocalConfiguration.cs
new file mode 100644
index 000000000..6edd7f65a
--- /dev/null
+++ b/src/Testcontainers.MongoDbAtlasLocal/MongoDbAtlasLocalConfiguration.cs
@@ -0,0 +1,77 @@
+namespace Testcontainers.MongoDbAtlasLocal;
+
+///
+[PublicAPI]
+public sealed class MongoDbAtlasLocalConfiguration : ContainerConfiguration
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The MongoDb Atlas Local username.
+ /// The MongoDb Atlas Local password.
+ public MongoDbAtlasLocalConfiguration(
+ string username = null,
+ string password = null)
+ {
+ Username = username;
+ Password = password;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public MongoDbAtlasLocalConfiguration(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 MongoDbAtlasLocalConfiguration(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 MongoDbAtlasLocalConfiguration(MongoDbAtlasLocalConfiguration resourceConfiguration)
+ : this(new MongoDbAtlasLocalConfiguration(), 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 MongoDbAtlasLocalConfiguration(MongoDbAtlasLocalConfiguration oldValue, MongoDbAtlasLocalConfiguration newValue)
+ : base(oldValue, newValue)
+ {
+ Username = BuildConfiguration.Combine(oldValue.Username, newValue.Username);
+ Password = BuildConfiguration.Combine(oldValue.Password, newValue.Password);
+ }
+
+ ///
+ /// Gets the MongoDb Atlas Local username.
+ ///
+ ///
+ /// If null or empty, the deployment does not enable authentication.
+ ///
+ public string Username { get; }
+
+ ///
+ /// Gets the MongoDb Atlas Local password.
+ ///
+ ///
+ /// If null or empty, the deployment does not enable authentication.
+ ///
+ public string Password { get; }
+}
\ No newline at end of file
diff --git a/src/Testcontainers.MongoDbAtlasLocal/MongoDbAtlasLocalConnectionStringProvider.cs b/src/Testcontainers.MongoDbAtlasLocal/MongoDbAtlasLocalConnectionStringProvider.cs
new file mode 100644
index 000000000..d750f5b6f
--- /dev/null
+++ b/src/Testcontainers.MongoDbAtlasLocal/MongoDbAtlasLocalConnectionStringProvider.cs
@@ -0,0 +1,13 @@
+namespace Testcontainers.MongoDbAtlasLocal;
+
+///
+/// Provides the MongoDb Atlas Local connection string.
+///
+internal sealed class MongoDbAtlasLocalConnectionStringProvider : ContainerConnectionStringProvider
+{
+ ///
+ protected override string GetHostConnectionString()
+ {
+ return Container.GetConnectionString();
+ }
+}
\ No newline at end of file
diff --git a/src/Testcontainers.MongoDbAtlasLocal/MongoDbAtlasLocalContainer.cs b/src/Testcontainers.MongoDbAtlasLocal/MongoDbAtlasLocalContainer.cs
new file mode 100644
index 000000000..c6cc71026
--- /dev/null
+++ b/src/Testcontainers.MongoDbAtlasLocal/MongoDbAtlasLocalContainer.cs
@@ -0,0 +1,71 @@
+namespace Testcontainers.MongoDbAtlasLocal;
+
+///
+[PublicAPI]
+public sealed class MongoDbAtlasLocalContainer : DockerContainer
+{
+ private const string MongoDbShellFilePath = "mongosh";
+
+ private readonly MongoDbAtlasLocalConfiguration _configuration;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The container configuration.
+ public MongoDbAtlasLocalContainer(MongoDbAtlasLocalConfiguration configuration)
+ : base(configuration)
+ {
+ _configuration = configuration;
+ }
+
+ ///
+ /// Gets the MongoDb Atlas Local connection string.
+ ///
+ ///
+ /// The deployment runs as a single-node replica set that advertises the container's
+ /// hostname, which the test host cannot resolve. The connection string therefore
+ /// sets directConnection=true.
+ ///
+ /// The MongoDb Atlas Local connection string.
+ public string GetConnectionString()
+ {
+ // The MongoDb documentation recommends to use percent-encoding for username and password: https://www.mongodb.com/docs/manual/reference/connection-string/.
+ var endpoint = new UriBuilder("mongodb", Hostname, GetMappedPublicPort(MongoDbAtlasLocalBuilder.MongoDbAtlasLocalPort));
+ endpoint.UserName = Uri.EscapeDataString(_configuration.Username ?? string.Empty);
+ endpoint.Password = Uri.EscapeDataString(_configuration.Password ?? string.Empty);
+ endpoint.Query = "directConnection=true";
+ return endpoint.ToString();
+ }
+
+ ///
+ /// Executes the JavaScript script in the MongoDb Atlas Local container.
+ ///
+ /// The content of the JavaScript script to execute.
+ /// Cancellation token.
+ /// Task that completes when the JavaScript script has been executed.
+ public async Task ExecScriptAsync(string scriptContent, CancellationToken ct = default)
+ {
+ var scriptFilePath = string.Join("/", string.Empty, "tmp", Guid.NewGuid().ToString("D"), Path.GetRandomFileName());
+
+ await CopyAsync(Encoding.UTF8.GetBytes(scriptContent), scriptFilePath, fileMode: Unix.FileMode644, ct: ct)
+ .ConfigureAwait(false);
+
+ var command = new List();
+ command.Add(MongoDbShellFilePath);
+
+ if (!string.IsNullOrEmpty(_configuration.Username) && !string.IsNullOrEmpty(_configuration.Password))
+ {
+ command.Add("--username");
+ command.Add(_configuration.Username);
+ command.Add("--password");
+ command.Add(_configuration.Password);
+ }
+
+ command.Add("--quiet");
+ command.Add("--eval");
+ command.Add($"load('{scriptFilePath}')");
+
+ return await ExecAsync(command, ct)
+ .ConfigureAwait(false);
+ }
+}
\ No newline at end of file
diff --git a/src/Testcontainers.MongoDbAtlasLocal/Testcontainers.MongoDbAtlasLocal.csproj b/src/Testcontainers.MongoDbAtlasLocal/Testcontainers.MongoDbAtlasLocal.csproj
new file mode 100644
index 000000000..32fd35b92
--- /dev/null
+++ b/src/Testcontainers.MongoDbAtlasLocal/Testcontainers.MongoDbAtlasLocal.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.MongoDbAtlasLocal/Usings.cs b/src/Testcontainers.MongoDbAtlasLocal/Usings.cs
new file mode 100644
index 000000000..6e4dc12d6
--- /dev/null
+++ b/src/Testcontainers.MongoDbAtlasLocal/Usings.cs
@@ -0,0 +1,14 @@
+global using System;
+global using System.Collections.Generic;
+global using System.IO;
+global using System.Linq;
+global using System.Text;
+global using System.Threading;
+global using System.Threading.Tasks;
+global using Docker.DotNet.Models;
+global using DotNet.Testcontainers;
+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.MongoDbAtlasLocal.Tests/.editorconfig b/tests/Testcontainers.MongoDbAtlasLocal.Tests/.editorconfig
new file mode 100644
index 000000000..6f066619d
--- /dev/null
+++ b/tests/Testcontainers.MongoDbAtlasLocal.Tests/.editorconfig
@@ -0,0 +1 @@
+root = true
\ No newline at end of file
diff --git a/tests/Testcontainers.MongoDbAtlasLocal.Tests/.runs-on b/tests/Testcontainers.MongoDbAtlasLocal.Tests/.runs-on
new file mode 100644
index 000000000..d0395e498
--- /dev/null
+++ b/tests/Testcontainers.MongoDbAtlasLocal.Tests/.runs-on
@@ -0,0 +1 @@
+ubuntu-24.04
\ No newline at end of file
diff --git a/tests/Testcontainers.MongoDbAtlasLocal.Tests/AtlasSearch.cs b/tests/Testcontainers.MongoDbAtlasLocal.Tests/AtlasSearch.cs
new file mode 100644
index 000000000..c979f6d2e
--- /dev/null
+++ b/tests/Testcontainers.MongoDbAtlasLocal.Tests/AtlasSearch.cs
@@ -0,0 +1,55 @@
+namespace Testcontainers.MongoDbAtlasLocal;
+
+internal static class AtlasSearch
+{
+ private static readonly TimeSpan PollInterval = TimeSpan.FromMilliseconds(250);
+
+ private static readonly TimeSpan PollTimeout = TimeSpan.FromMinutes(1);
+
+ public static async Task WaitUntilSearchIndexIsQueryableAsync(IMongoCollection collection, string indexName, CancellationToken ct)
+ {
+ // Atlas Search builds the index asynchronously, it becomes queryable shortly after it has been created.
+ using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
+ timeoutCts.CancelAfter(PollTimeout);
+
+ while (true)
+ {
+ using var cursor = await collection.SearchIndexes.ListAsync(indexName, cancellationToken: timeoutCts.Token)
+ .ConfigureAwait(false);
+
+ var searchIndexes = await cursor.ToListAsync(timeoutCts.Token)
+ .ConfigureAwait(false);
+
+ if (searchIndexes.Any(searchIndex => searchIndex.TryGetValue("queryable", out var queryable) && queryable.ToBoolean()))
+ {
+ return;
+ }
+
+ await Task.Delay(PollInterval, timeoutCts.Token)
+ .ConfigureAwait(false);
+ }
+ }
+
+ public static async Task WaitUntilSearchReturnsAsync(IMongoCollection collection, string indexName, SearchDefinition searchDefinition, int expectedCount, CancellationToken ct)
+ {
+ // mongot replicates documents from mongod asynchronously, a queryable index may not contain all documents yet.
+ using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
+ timeoutCts.CancelAfter(PollTimeout);
+
+ while (true)
+ {
+ var documents = await collection.Aggregate()
+ .Search(searchDefinition, indexName: indexName)
+ .ToListAsync(timeoutCts.Token)
+ .ConfigureAwait(false);
+
+ if (documents.Count >= expectedCount)
+ {
+ return documents.Select(document => document["title"].AsString).ToArray();
+ }
+
+ await Task.Delay(PollInterval, timeoutCts.Token)
+ .ConfigureAwait(false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/Testcontainers.MongoDbAtlasLocal.Tests/Dockerfile b/tests/Testcontainers.MongoDbAtlasLocal.Tests/Dockerfile
new file mode 100644
index 000000000..b4c151b37
--- /dev/null
+++ b/tests/Testcontainers.MongoDbAtlasLocal.Tests/Dockerfile
@@ -0,0 +1 @@
+FROM mongodb/mongodb-atlas-local:8.0.32@sha256:1985314b0ded756ba965e0f400d17406b7a89bec659d4763a490f42f006e4fbd
\ No newline at end of file
diff --git a/tests/Testcontainers.MongoDbAtlasLocal.Tests/MongoDbAtlasLocalBuilderTest.cs b/tests/Testcontainers.MongoDbAtlasLocal.Tests/MongoDbAtlasLocalBuilderTest.cs
new file mode 100644
index 000000000..6cbd0ba38
--- /dev/null
+++ b/tests/Testcontainers.MongoDbAtlasLocal.Tests/MongoDbAtlasLocalBuilderTest.cs
@@ -0,0 +1,66 @@
+namespace Testcontainers.MongoDbAtlasLocal;
+
+public sealed class MongoDbAtlasLocalBuilderTest
+{
+ [Theory]
+ [InlineData("mongo", "")]
+ [InlineData("", "mongo")]
+ public void BuildThrowsArgumentExceptionWhenOnlyOneCredentialIsSet(string username, string password)
+ {
+ // Given
+ var builder = new MongoDbAtlasLocalBuilder(TestSession.GetImageFromDockerfile()).WithUsername(username).WithPassword(password);
+
+ // When
+ var exception = Assert.Throws(() => builder.Build());
+
+ // Then
+ Assert.Equal("Credentials", exception.ParamName);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("seed")]
+ [InlineData("seed.json")]
+ [InlineData("seed.JS")]
+ [InlineData("seed/01-seed.js")]
+ [InlineData("seed\\01-seed.js")]
+ public void WithInitScriptContentThrowsArgumentExceptionWhenFileNameIsInvalid(string fileName)
+ {
+ // Given
+ var builder = new MongoDbAtlasLocalBuilder(TestSession.GetImageFromDockerfile());
+
+ // When
+ var exception = Assert.ThrowsAny(() => builder.WithInitScriptContent(fileName, string.Empty));
+
+ // Then
+ Assert.Equal("fileName", exception.ParamName);
+ }
+
+ [Theory]
+ [InlineData("01-seed.js")]
+ [InlineData("02-seed.sh")]
+ public void WithInitScriptContentAcceptsJavaScriptAndShellScripts(string fileName)
+ {
+ // Given
+ var builder = new MongoDbAtlasLocalBuilder(TestSession.GetImageFromDockerfile());
+
+ // When
+ var exception = Record.Exception(() => builder.WithInitScriptContent(fileName, string.Empty).Build());
+
+ // Then
+ Assert.Null(exception);
+ }
+
+ [Fact]
+ public void WithInitScriptThrowsArgumentExceptionWhenFileExtensionIsNotSupported()
+ {
+ // Given
+ var builder = new MongoDbAtlasLocalBuilder(TestSession.GetImageFromDockerfile());
+
+ // When
+ var exception = Assert.Throws(() => builder.WithInitScript("Seed/movies.json"));
+
+ // Then
+ Assert.Equal("scriptFilePath", exception.ParamName);
+ }
+}
\ No newline at end of file
diff --git a/tests/Testcontainers.MongoDbAtlasLocal.Tests/MongoDbAtlasLocalContainerTest.cs b/tests/Testcontainers.MongoDbAtlasLocal.Tests/MongoDbAtlasLocalContainerTest.cs
new file mode 100644
index 000000000..36de00af5
--- /dev/null
+++ b/tests/Testcontainers.MongoDbAtlasLocal.Tests/MongoDbAtlasLocalContainerTest.cs
@@ -0,0 +1,111 @@
+namespace Testcontainers.MongoDbAtlasLocal;
+
+public abstract class MongoDbAtlasLocalContainerTest : IAsyncLifetime
+{
+ private readonly MongoDbAtlasLocalContainer _mongoDbAtlasLocalContainer;
+
+ private MongoDbAtlasLocalContainerTest(MongoDbAtlasLocalContainer mongoDbAtlasLocalContainer)
+ {
+ _mongoDbAtlasLocalContainer = mongoDbAtlasLocalContainer;
+ }
+
+ // # --8<-- [start:UseMongoDbAtlasLocalContainer]
+ public async ValueTask InitializeAsync()
+ {
+ await _mongoDbAtlasLocalContainer.StartAsync()
+ .ConfigureAwait(false);
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ await DisposeAsyncCore()
+ .ConfigureAwait(false);
+
+ GC.SuppressFinalize(this);
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public void ConnectionStateReturnsOpen()
+ {
+ // Given
+ var client = new MongoClient(_mongoDbAtlasLocalContainer.GetConnectionString());
+
+ // When
+ using var databases = client.ListDatabases(TestContext.Current.CancellationToken);
+
+ // Then
+ Assert.Contains(databases.ToEnumerable(TestContext.Current.CancellationToken), database => database.TryGetValue("name", out var name) && "admin".Equals(name.AsString));
+ Assert.Equal(_mongoDbAtlasLocalContainer.GetConnectionString(), _mongoDbAtlasLocalContainer.GetConnectionString(ConnectionMode.Host));
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task AtlasSearchReturnsMatchingDocuments()
+ {
+ // Given
+ const string indexName = "default";
+
+ var client = new MongoClient(_mongoDbAtlasLocalContainer.GetConnectionString());
+
+ var collection = client.GetDatabase("test").GetCollection("movies");
+
+ await collection.InsertManyAsync(new[] { new BsonDocument("title", "The Matrix"), new BsonDocument("title", "Back to the Future"), new BsonDocument("title", "The Matrix Reloaded") }, cancellationToken: TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ await collection.SearchIndexes.CreateOneAsync(new CreateSearchIndexModel(indexName, new BsonDocument("mappings", new BsonDocument("dynamic", true))), TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ // When
+ await AtlasSearch.WaitUntilSearchIndexIsQueryableAsync(collection, indexName, TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ var titles = await AtlasSearch.WaitUntilSearchReturnsAsync(collection, indexName, Builders.Search.Text("title", "matrix"), 2, TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ // Then
+ Assert.Equal(new[] { "The Matrix", "The Matrix Reloaded" }, titles.OrderBy(title => title));
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task ExecScriptReturnsSuccessful()
+ {
+ // Given
+ const string scriptContent = "printjson(db.adminCommand({listDatabases:1,nameOnly:true,filter:{\"name\":/^admin/}}));";
+
+ // When
+ var execResult = await _mongoDbAtlasLocalContainer.ExecScriptAsync(scriptContent, TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ // Then
+ Assert.True(0L.Equals(execResult.ExitCode), execResult.Stderr);
+ Assert.Empty(execResult.Stderr);
+ }
+ // # --8<-- [end:UseMongoDbAtlasLocalContainer]
+
+ protected virtual ValueTask DisposeAsyncCore()
+ {
+ return _mongoDbAtlasLocalContainer.DisposeAsync();
+ }
+
+ // # --8<-- [start:CreateMongoDbAtlasLocalContainer]
+ [UsedImplicitly]
+ public sealed class MongoDbAtlasLocalDefaultConfiguration : MongoDbAtlasLocalContainerTest
+ {
+ public MongoDbAtlasLocalDefaultConfiguration()
+ : base(new MongoDbAtlasLocalBuilder(TestSession.GetImageFromDockerfile()).Build())
+ {
+ }
+ }
+
+ [UsedImplicitly]
+ public sealed class MongoDbAtlasLocalAuthConfiguration : MongoDbAtlasLocalContainerTest
+ {
+ public MongoDbAtlasLocalAuthConfiguration()
+ : base(new MongoDbAtlasLocalBuilder(TestSession.GetImageFromDockerfile()).WithUsername("mongo").WithPassword("mongo").Build())
+ {
+ }
+ }
+ // # --8<-- [end:CreateMongoDbAtlasLocalContainer]
+}
\ No newline at end of file
diff --git a/tests/Testcontainers.MongoDbAtlasLocal.Tests/MongoDbAtlasLocalSeedTest.cs b/tests/Testcontainers.MongoDbAtlasLocal.Tests/MongoDbAtlasLocalSeedTest.cs
new file mode 100644
index 000000000..7576c024d
--- /dev/null
+++ b/tests/Testcontainers.MongoDbAtlasLocal.Tests/MongoDbAtlasLocalSeedTest.cs
@@ -0,0 +1,102 @@
+namespace Testcontainers.MongoDbAtlasLocal;
+
+public sealed class MongoDbAtlasLocalSeedTest : IAsyncLifetime
+{
+ private const string Database = "sample";
+
+ private const string SeedMarkerFilePath = "/tmp/seeded";
+
+ // # --8<-- [start:SeedMongoDbAtlasLocalContainer]
+ private readonly MongoDbAtlasLocalContainer _mongoDbAtlasLocalContainer = new MongoDbAtlasLocalBuilder(TestSession.GetImageFromDockerfile())
+ .WithUsername("mongo")
+ .WithPassword("mongo")
+ .WithInitDatabase(Database)
+ .WithInitScript("Seed/01-movies.js")
+ .WithInitScriptContent("02-marker.sh", "touch " + SeedMarkerFilePath)
+ .WithNoTelemetry()
+ .Build();
+ // # --8<-- [end:SeedMongoDbAtlasLocalContainer]
+
+ public async ValueTask InitializeAsync()
+ {
+ await _mongoDbAtlasLocalContainer.StartAsync()
+ .ConfigureAwait(false);
+ }
+
+ public ValueTask DisposeAsync()
+ {
+ return _mongoDbAtlasLocalContainer.DisposeAsync();
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task SeededSearchIndexReturnsMatchingDocuments()
+ {
+ // Given
+ const string indexName = "default";
+
+ var client = new MongoClient(_mongoDbAtlasLocalContainer.GetConnectionString());
+
+ var collection = client.GetDatabase(Database).GetCollection("movies");
+
+ // When
+ var count = await collection.CountDocumentsAsync(FilterDefinition.Empty, cancellationToken: TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ await AtlasSearch.WaitUntilSearchIndexIsQueryableAsync(collection, indexName, TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ var titles = await AtlasSearch.WaitUntilSearchReturnsAsync(collection, indexName, Builders.Search.Text("title", "matrix"), 2, TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ // Then
+ Assert.Equal(3, count);
+ Assert.Equal(new[] { "The Matrix", "The Matrix Reloaded" }, titles.OrderBy(title => title));
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public void UnauthenticatedClientIsRejected()
+ {
+ // Given
+ var connectionString = new MongoUrlBuilder(_mongoDbAtlasLocalContainer.GetConnectionString()) { Username = null, Password = null }.ToString();
+
+ var client = new MongoClient(connectionString);
+
+ // When
+ var exception = Assert.Throws(() => client.ListDatabaseNames(TestContext.Current.CancellationToken));
+
+ // Then
+ Assert.Equal("Unauthorized", exception.CodeName);
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task ShellInitScriptRan()
+ {
+ // Given
+ var command = new[] { "test", "-f", SeedMarkerFilePath };
+
+ // When
+ var execResult = await _mongoDbAtlasLocalContainer.ExecAsync(command, TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ // Then
+ Assert.Equal(0L, execResult.ExitCode);
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task TelemetryIsDisabled()
+ {
+ // Given
+ var command = new[] { "printenv", "DO_NOT_TRACK" };
+
+ // When
+ var execResult = await _mongoDbAtlasLocalContainer.ExecAsync(command, TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ // Then
+ Assert.Equal("1", execResult.Stdout.Trim());
+ }
+}
\ No newline at end of file
diff --git a/tests/Testcontainers.MongoDbAtlasLocal.Tests/Seed/01-movies.js b/tests/Testcontainers.MongoDbAtlasLocal.Tests/Seed/01-movies.js
new file mode 100644
index 000000000..41b0ea413
--- /dev/null
+++ b/tests/Testcontainers.MongoDbAtlasLocal.Tests/Seed/01-movies.js
@@ -0,0 +1,7 @@
+db.movies.insertMany([
+ { title: "The Matrix" },
+ { title: "Back to the Future" },
+ { title: "The Matrix Reloaded" },
+]);
+
+db.movies.createSearchIndex("default", { mappings: { dynamic: true } });
diff --git a/tests/Testcontainers.MongoDbAtlasLocal.Tests/Testcontainers.MongoDbAtlasLocal.Tests.csproj b/tests/Testcontainers.MongoDbAtlasLocal.Tests/Testcontainers.MongoDbAtlasLocal.Tests.csproj
new file mode 100644
index 000000000..94848eda6
--- /dev/null
+++ b/tests/Testcontainers.MongoDbAtlasLocal.Tests/Testcontainers.MongoDbAtlasLocal.Tests.csproj
@@ -0,0 +1,29 @@
+
+
+ net10.0
+ false
+ false
+ Exe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
\ No newline at end of file
diff --git a/tests/Testcontainers.MongoDbAtlasLocal.Tests/Usings.cs b/tests/Testcontainers.MongoDbAtlasLocal.Tests/Usings.cs
new file mode 100644
index 000000000..7ac2d4595
--- /dev/null
+++ b/tests/Testcontainers.MongoDbAtlasLocal.Tests/Usings.cs
@@ -0,0 +1,11 @@
+global using System;
+global using System.Linq;
+global using System.Threading;
+global using System.Threading.Tasks;
+global using DotNet.Testcontainers.Commons;
+global using DotNet.Testcontainers.Configurations;
+global using JetBrains.Annotations;
+global using MongoDB.Bson;
+global using MongoDB.Driver;
+global using MongoDB.Driver.Search;
+global using Xunit;
\ No newline at end of file