diff --git a/docs/api/create_docker_network.md b/docs/api/create_docker_network.md
index 0b3d85faf..c8619c3df 100644
--- a/docs/api/create_docker_network.md
+++ b/docs/api/create_docker_network.md
@@ -56,6 +56,37 @@ var execResult = await ultimateQuestionContainer.ExecAsync(new[] { "nc", MagicNu
Assert.Equal(MagicNumber, execResult.Stdout.Trim());
```
+## Connecting a running container to an existing network
+
+If a container is already running, use `IContainer.ConnectAsync(...)` to attach it to an existing network.
+
+The network must already exist. You can reference it either by network name or by an `INetwork` instance:
+
+```csharp
+var network = new NetworkBuilder()
+ .WithName(Guid.NewGuid().ToString("D"))
+ .Build();
+
+var container = new ContainerBuilder("alpine:3.20.0")
+ .WithEntrypoint("top")
+ .Build();
+
+await network.CreateAsync()
+ .ConfigureAwait(false);
+
+await container.StartAsync()
+ .ConfigureAwait(false);
+
+await container.ConnectAsync(network)
+ .ConfigureAwait(false);
+
+// Equivalent when only the network name is available:
+await container.ConnectAsync(network.Name)
+ .ConfigureAwait(false);
+```
+
+Prefer `WithNetwork(...)` during container configuration whenever possible. Use `ConnectAsync(...)` when you explicitly need to attach a running container to an already existing network.
+
## Exposing container ports to the host
It is common to connect to a container from your test process running on your test host. To bind and expose a container port, use the `WithPortBinding(ushort, true)` container builder member. To retrieve the actual port at runtime, use the container `GetMappedPublicPort(ushort)` member. Further information on network configurations is included in our [best practices](https://dotnet.testcontainers.org/api/best_practices/).
diff --git a/src/Testcontainers/Containers/DockerContainer.cs b/src/Testcontainers/Containers/DockerContainer.cs
index 510e66a5e..093f706f6 100644
--- a/src/Testcontainers/Containers/DockerContainer.cs
+++ b/src/Testcontainers/Containers/DockerContainer.cs
@@ -12,6 +12,7 @@ namespace DotNet.Testcontainers.Containers
using DotNet.Testcontainers.Clients;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Images;
+ using DotNet.Testcontainers.Networks;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
@@ -373,6 +374,26 @@ await UnsafeUnpauseAsync(ct)
.ConfigureAwait(false);
}
+ ///
+ public async Task ConnectAsync(string network, CancellationToken ct = default)
+ {
+ using var disposable = await AcquireLockAsync(ct)
+ .ConfigureAwait(false);
+
+ await UnsafeConnectAsync(network, ct)
+ .ConfigureAwait(false);
+ }
+
+ ///
+ public async Task ConnectAsync(INetwork network, CancellationToken ct = default)
+ {
+ using var disposable = await AcquireLockAsync(ct)
+ .ConfigureAwait(false);
+
+ await UnsafeConnectAsync(network.Name, ct)
+ .ConfigureAwait(false);
+ }
+
///
public Task CopyAsync(byte[] fileContent, string filePath, uint uid = 0, uint gid = 0, UnixFileModes fileMode = Unix.FileMode644, CancellationToken ct = default)
{
@@ -688,6 +709,28 @@ await _client.UnpauseAsync(_container.ID, ct)
Unpaused?.Invoke(this, EventArgs.Empty);
}
+ ///
+ /// Connects the container to an existing network.
+ ///
+ ///
+ /// Only the public members and are thread-safe for now.
+ ///
+ /// The network name.
+ /// Cancellation token.
+ /// Task that completes when the container has been connected to the network.
+ protected virtual async Task UnsafeConnectAsync(string network, CancellationToken ct = default)
+ {
+ ThrowIfLockNotAcquired();
+
+ ThrowIfResourceNotFound();
+
+ await _client.Network.ConnectAsync(network, _container.ID, ct)
+ .ConfigureAwait(false);
+
+ _container = await _client.Container.ByIdAsync(_container.ID, ct)
+ .ConfigureAwait(false);
+ }
+
///
protected override bool Exists()
{
diff --git a/src/Testcontainers/Containers/IContainer.cs b/src/Testcontainers/Containers/IContainer.cs
index 04eb6a5a7..99cb797e6 100644
--- a/src/Testcontainers/Containers/IContainer.cs
+++ b/src/Testcontainers/Containers/IContainer.cs
@@ -7,6 +7,7 @@ namespace DotNet.Testcontainers.Containers
using System.Threading.Tasks;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Images;
+ using DotNet.Testcontainers.Networks;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
@@ -188,7 +189,7 @@ public interface IContainer : IConnectionStringProvider, IAsyncDisposable
///
/// The container port.
/// Returns the public assigned host port.
- /// Container has not been created.
+ /// Container has not been created, or no mapped port was found.
ushort GetMappedPublicPort(string containerPort);
///
@@ -252,6 +253,28 @@ public interface IContainer : IConnectionStringProvider, IAsyncDisposable
/// Thrown when a Testcontainers task gets canceled.
Task UnpauseAsync(CancellationToken ct = default);
+ ///
+ /// Connects the running container to an existing network.
+ ///
+ /// The existing network to connect to.
+ /// Cancellation token.
+ /// Task that completes when the container has been connected to the network.
+ /// Container has not been created.
+ /// Thrown when a Docker API call gets canceled.
+ /// Thrown when a Testcontainers task gets canceled.
+ Task ConnectAsync(string network, CancellationToken ct = default);
+
+ ///
+ /// Connects the running container to an existing network.
+ ///
+ /// The existing network to connect to.
+ /// Cancellation token.
+ /// Task that completes when the container has been connected to the network.
+ /// Container has not been created.
+ /// Thrown when a Docker API call gets canceled.
+ /// Thrown when a Testcontainers task gets canceled.
+ Task ConnectAsync(INetwork network, CancellationToken ct = default);
+
///
/// Copies a test host file to the container.
///
@@ -261,7 +284,7 @@ public interface IContainer : IConnectionStringProvider, IAsyncDisposable
/// The group ID to set for the copied file or directory. Defaults to 0 (root).
/// The POSIX file mode permission.
/// Cancellation token.
- ///
+ /// A task that completes when the byte array content has been copied.
Task CopyAsync(byte[] fileContent, string filePath, uint uid = 0, uint gid = 0, UnixFileModes fileMode = Unix.FileMode644, CancellationToken ct = default);
///
diff --git a/src/Testcontainers/Images/MatchImage.cs b/src/Testcontainers/Images/MatchImage.cs
index 681fe4330..688e12b03 100644
--- a/src/Testcontainers/Images/MatchImage.cs
+++ b/src/Testcontainers/Images/MatchImage.cs
@@ -44,7 +44,7 @@ static ReferenceRegex()
}
private ReferenceRegex()
- : base(Pattern, RegexOptions.Compiled, TimeSpan.FromSeconds(1))
+ : base(Pattern, RegexOptions.Compiled, TimeSpan.FromSeconds(5))
{
}
diff --git a/tests/Testcontainers.Platform.Linux.Tests/NetworkConnectTest.cs b/tests/Testcontainers.Platform.Linux.Tests/NetworkConnectTest.cs
new file mode 100644
index 000000000..2c10f0a47
--- /dev/null
+++ b/tests/Testcontainers.Platform.Linux.Tests/NetworkConnectTest.cs
@@ -0,0 +1,45 @@
+namespace Testcontainers.Tests;
+
+public sealed class NetworkConnectTest
+{
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task ConnectsRunningContainerToExistingNetworks()
+ {
+ // Given
+ await using var networkByName = new NetworkBuilder()
+ .Build();
+
+ await using var networkByReference = new NetworkBuilder()
+ .Build();
+
+ await using var container = new ContainerBuilder(CommonImages.Alpine)
+ .WithCommand(CommonCommands.SleepInfinity)
+ .Build();
+
+ await networkByName.CreateAsync(TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ await networkByReference.CreateAsync(TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ await container.StartAsync(TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ using var dockerClient = TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientBuilder(Guid.NewGuid()).Build();
+
+ // When
+ await container.ConnectAsync(networkByName.Name, TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ await container.ConnectAsync(networkByReference, TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ var response = await dockerClient.Containers.InspectContainerAsync(container.Id, TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ // Then
+ Assert.Contains(networkByName.Name, response.NetworkSettings.Networks.Keys);
+ Assert.Contains(networkByReference.Name, response.NetworkSettings.Networks.Keys);
+ }
+}
\ No newline at end of file