From 2868289b6fb3b7f9ec6509c2a8983ad967bbcee7 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Thu, 13 Nov 2025 20:03:30 +0100 Subject: [PATCH 1/4] feat: Support configuring Docker API version --- docs/custom_configuration/index.md | 1 + ...erDesktopEndpointAuthenticationProvider.cs | 6 +++++ ...ontainersEndpointAuthenticationProvider.cs | 6 +++++ src/Testcontainers/Clients/DockerApiClient.cs | 2 +- .../Configurations/CustomConfiguration.cs | 5 ++++ .../EnvironmentConfiguration.cs | 13 ++++++++-- .../Configurations/ICustomConfiguration.cs | 8 +++++++ .../PropertiesFileConfiguration.cs | 7 ++++++ .../Configurations/TestcontainersSettings.cs | 10 ++++++++ .../Configurations/CustomConfigurationTest.cs | 24 +++++++++++++++++++ 10 files changed, 79 insertions(+), 3 deletions(-) diff --git a/docs/custom_configuration/index.md b/docs/custom_configuration/index.md index d7e5eb371..b745f2a53 100644 --- a/docs/custom_configuration/index.md +++ b/docs/custom_configuration/index.md @@ -4,6 +4,7 @@ Testcontainers supports various configurations to set up your test environment. | Properties File | Environment Variable | Description | Default | |---------------------------------|------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------|------------------------------| +| `docker.api.version` | `DOCKER_API_VERSION` | The Docker API version to use. | `1.44` | | `docker.config` | `DOCKER_CONFIG` | The directory path that contains the Docker configuration (`config.json`) file. | `~/.docker/` | | `docker.host` | `DOCKER_HOST` | The Docker daemon socket to connect to. | - | | `docker.context` | `DOCKER_CONTEXT` | The Docker context to connect to. | - | diff --git a/src/Testcontainers/Builders/DockerDesktopEndpointAuthenticationProvider.cs b/src/Testcontainers/Builders/DockerDesktopEndpointAuthenticationProvider.cs index 02d164d6f..9411c6b89 100644 --- a/src/Testcontainers/Builders/DockerDesktopEndpointAuthenticationProvider.cs +++ b/src/Testcontainers/Builders/DockerDesktopEndpointAuthenticationProvider.cs @@ -25,6 +25,12 @@ public override bool IsApplicable() return !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && DockerEngine != null; } + /// + public Version GetDockerApiVersion() + { + return null; + } + /// public string GetDockerConfig() { diff --git a/src/Testcontainers/Builders/TestcontainersEndpointAuthenticationProvider.cs b/src/Testcontainers/Builders/TestcontainersEndpointAuthenticationProvider.cs index 04335eb00..4e4e9780e 100644 --- a/src/Testcontainers/Builders/TestcontainersEndpointAuthenticationProvider.cs +++ b/src/Testcontainers/Builders/TestcontainersEndpointAuthenticationProvider.cs @@ -57,6 +57,12 @@ public override IDockerEndpointAuthenticationConfiguration GetAuthConfig() return new DockerEndpointAuthenticationConfiguration(_dockerEngine); } + /// + public Version GetDockerApiVersion() + { + return _customConfiguration.GetDockerApiVersion(); + } + /// public string GetDockerConfig() { diff --git a/src/Testcontainers/Clients/DockerApiClient.cs b/src/Testcontainers/Clients/DockerApiClient.cs index 927bb397a..172db1a09 100644 --- a/src/Testcontainers/Clients/DockerApiClient.cs +++ b/src/Testcontainers/Clients/DockerApiClient.cs @@ -134,7 +134,7 @@ private static IDockerClient GetDockerClient(Guid sessionId, IDockerEndpointAuth { using (var dockerClientConfiguration = dockerEndpointAuthConfig.GetDockerClientConfiguration(sessionId)) { - return dockerClientConfiguration.CreateClient(); + return dockerClientConfiguration.CreateClient(TestcontainersSettings.DockerApiVersion); } } } diff --git a/src/Testcontainers/Configurations/CustomConfiguration.cs b/src/Testcontainers/Configurations/CustomConfiguration.cs index 81c79c003..eed39241c 100644 --- a/src/Testcontainers/Configurations/CustomConfiguration.cs +++ b/src/Testcontainers/Configurations/CustomConfiguration.cs @@ -15,6 +15,11 @@ protected CustomConfiguration(IReadOnlyDictionary properties) _properties = properties; } + protected virtual Version GetDockerApiVersion(string propertyName) + { + return _properties.TryGetValue(propertyName, out var propertyValue) && !string.IsNullOrEmpty(propertyValue) && Version.TryParse(propertyValue, out var dockerApiVersion) ? dockerApiVersion : null; + } + protected virtual string GetDockerConfig(string propertyName) { return GetPropertyValue(propertyName); diff --git a/src/Testcontainers/Configurations/EnvironmentConfiguration.cs b/src/Testcontainers/Configurations/EnvironmentConfiguration.cs index 7a2f9b9c3..c33c62a43 100644 --- a/src/Testcontainers/Configurations/EnvironmentConfiguration.cs +++ b/src/Testcontainers/Configurations/EnvironmentConfiguration.cs @@ -10,6 +10,8 @@ namespace DotNet.Testcontainers.Configurations /// internal class EnvironmentConfiguration : CustomConfiguration, ICustomConfiguration { + private const string DockerApiVersion = "DOCKER_API_VERSION"; + private const string DockerConfig = "DOCKER_CONFIG"; private const string DockerHost = "DOCKER_HOST"; @@ -54,11 +56,12 @@ static EnvironmentConfiguration() public EnvironmentConfiguration() : base(new[] { - DockerAuthConfig, - DockerCertPath, + DockerApiVersion, DockerConfig, DockerHost, DockerContext, + DockerAuthConfig, + DockerCertPath, DockerTls, DockerTlsVerify, DockerHostOverride, @@ -82,6 +85,12 @@ public EnvironmentConfiguration() public static ICustomConfiguration Instance { get; } = new EnvironmentConfiguration(); + /// + public Version GetDockerApiVersion() + { + return GetDockerApiVersion(DockerApiVersion); + } + /// public string GetDockerConfig() { diff --git a/src/Testcontainers/Configurations/ICustomConfiguration.cs b/src/Testcontainers/Configurations/ICustomConfiguration.cs index a9964cac4..57e63aa27 100644 --- a/src/Testcontainers/Configurations/ICustomConfiguration.cs +++ b/src/Testcontainers/Configurations/ICustomConfiguration.cs @@ -10,6 +10,14 @@ namespace DotNet.Testcontainers.Configurations /// internal interface ICustomConfiguration { + /// + /// Gets the Docker API version custom configuration. + /// + /// The Docker API version custom configuration. + /// https://dotnet.testcontainers.org/custom_configuration/. + [CanBeNull] + Version GetDockerApiVersion(); + /// /// Gets the Docker config custom configuration. /// diff --git a/src/Testcontainers/Configurations/PropertiesFileConfiguration.cs b/src/Testcontainers/Configurations/PropertiesFileConfiguration.cs index 7ae19fe46..a4412010e 100644 --- a/src/Testcontainers/Configurations/PropertiesFileConfiguration.cs +++ b/src/Testcontainers/Configurations/PropertiesFileConfiguration.cs @@ -56,6 +56,13 @@ public PropertiesFileConfiguration(params string[] lines) public static ICustomConfiguration Instance { get; } = new PropertiesFileConfiguration(); + /// + public Version GetDockerApiVersion() + { + const string propertyName = "docker.api.version"; + return GetDockerApiVersion(propertyName); + } + /// public string GetDockerConfig() { diff --git a/src/Testcontainers/Configurations/TestcontainersSettings.cs b/src/Testcontainers/Configurations/TestcontainersSettings.cs index cf72ac995..cf316298b 100644 --- a/src/Testcontainers/Configurations/TestcontainersSettings.cs +++ b/src/Testcontainers/Configurations/TestcontainersSettings.cs @@ -42,6 +42,16 @@ static TestcontainersSettings() { } + /// + /// Gets or sets the Docker API version. + /// + /// + /// https://github.com/moby/moby/releases/tag/docker-v29.0.0. + /// + [CanBeNull] + public static Version DockerApiVersion { get; set; } + = EnvironmentConfiguration.Instance.GetDockerApiVersion() ?? PropertiesFileConfiguration.Instance.GetDockerApiVersion() ?? new Version(1, 44); + /// /// Gets or sets the Docker host override value. /// diff --git a/tests/Testcontainers.Tests/Unit/Configurations/CustomConfigurationTest.cs b/tests/Testcontainers.Tests/Unit/Configurations/CustomConfigurationTest.cs index 1d123dea5..652c10be1 100644 --- a/tests/Testcontainers.Tests/Unit/Configurations/CustomConfigurationTest.cs +++ b/tests/Testcontainers.Tests/Unit/Configurations/CustomConfigurationTest.cs @@ -15,6 +15,7 @@ public sealed class EnvironmentConfigurationTest : IDisposable static EnvironmentConfigurationTest() { + EnvironmentVariables.Add("DOCKER_API_VERSION"); EnvironmentVariables.Add("DOCKER_CONFIG"); EnvironmentVariables.Add("DOCKER_HOST"); EnvironmentVariables.Add("DOCKER_CONTEXT"); @@ -34,6 +35,18 @@ static EnvironmentConfigurationTest() EnvironmentVariables.Add("TESTCONTAINERS_NAMED_PIPE_CONNECTION_TIMEOUT"); } + [Theory] + [InlineData("", "", null)] + [InlineData("DOCKER_API_VERSION", "", null)] + [InlineData("DOCKER_API_VERSION", "version", null)] + [InlineData("DOCKER_API_VERSION", "1.52", "1.52")] + public void GetDockerApiVersionCustomConfiguration(string propertyName, string propertyValue, string expected) + { + SetEnvironmentVariable(propertyName, propertyValue); + ICustomConfiguration customConfiguration = new EnvironmentConfiguration(); + Assert.Equal(expected, customConfiguration.GetDockerApiVersion()?.ToString()); + } + [Theory] [InlineData("", "", null)] [InlineData("DOCKER_CONFIG", "", null)] @@ -259,6 +272,17 @@ private static void SetEnvironmentVariable(string propertyName, string propertyV public sealed class PropertiesFileConfigurationTest { + [Theory] + [InlineData("", null)] + [InlineData("docker.api.version=", null)] + [InlineData("docker.api.version=version", null)] + [InlineData("docker.api.version=1.52", "1.52")] + public void GetDockerApiVersionCustomConfiguration(string configuration, string expected) + { + ICustomConfiguration customConfiguration = new PropertiesFileConfiguration(new[] { configuration }); + Assert.Equal(expected, customConfiguration.GetDockerApiVersion()?.ToString()); + } + [Theory] [InlineData("", null)] [InlineData("docker.config=", null)] From aa40a9310d5639cf9887bb23a54c28ba9fe18011 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Fri, 14 Nov 2025 07:34:12 +0100 Subject: [PATCH 2/4] fix: Bump dind image version --- .../Fixtures/Containers/Unix/DockerTlsFixture.cs | 2 +- .../Fixtures/Containers/Unix/OpenSsl1_1_1Fixture.cs | 2 +- .../Fixtures/Containers/Unix/OpenSsl3_1Fixture.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Testcontainers.Tests/Fixtures/Containers/Unix/DockerTlsFixture.cs b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/DockerTlsFixture.cs index 1ed591185..810872503 100644 --- a/tests/Testcontainers.Tests/Fixtures/Containers/Unix/DockerTlsFixture.cs +++ b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/DockerTlsFixture.cs @@ -9,7 +9,7 @@ public sealed class DockerTlsFixture : ProtectDockerDaemonSocket { public DockerTlsFixture() : base(new ContainerBuilder() - .WithCommand("--tlsverify=false"), "20.10.18") + .WithCommand("--tlsverify=false"), "29.0.0") { } diff --git a/tests/Testcontainers.Tests/Fixtures/Containers/Unix/OpenSsl1_1_1Fixture.cs b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/OpenSsl1_1_1Fixture.cs index 37705ec29..3e87a9ab7 100644 --- a/tests/Testcontainers.Tests/Fixtures/Containers/Unix/OpenSsl1_1_1Fixture.cs +++ b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/OpenSsl1_1_1Fixture.cs @@ -5,7 +5,7 @@ namespace DotNet.Testcontainers.Tests.Fixtures [UsedImplicitly] public sealed class OpenSsl1_1_1Fixture : DockerMTls { - public OpenSsl1_1_1Fixture() : base("20.10.18") + public OpenSsl1_1_1Fixture() : base("29.0.0") { } } diff --git a/tests/Testcontainers.Tests/Fixtures/Containers/Unix/OpenSsl3_1Fixture.cs b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/OpenSsl3_1Fixture.cs index 0a4768b96..9b50c72f3 100644 --- a/tests/Testcontainers.Tests/Fixtures/Containers/Unix/OpenSsl3_1Fixture.cs +++ b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/OpenSsl3_1Fixture.cs @@ -5,7 +5,7 @@ namespace DotNet.Testcontainers.Tests.Fixtures [UsedImplicitly] public sealed class OpenSsl3_1Fixture : DockerMTls { - public OpenSsl3_1Fixture() : base("24.0.5") + public OpenSsl3_1Fixture() : base("29.0.0") { } } From c2b334f7a71511a35fc7d02fb944c4a8529793f6 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:41:01 +0100 Subject: [PATCH 3/4] fix: User older API for outdated image --- .../DockerEndpointAuthenticationProvider.cs | 2 +- src/Testcontainers/Clients/DockerApiClient.cs | 2 +- ...ckerEndpointAuthenticationConfiguration.cs | 6 ++++++ ...ckerEndpointAuthenticationConfiguration.cs | 6 ++++++ .../Containers/Unix/OpenSsl1_1_1Fixture.cs | 2 +- .../Unix/ProtectDockerDaemonSocketTest.cs | 19 +++++++++++++++++-- 6 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/Testcontainers/Builders/DockerEndpointAuthenticationProvider.cs b/src/Testcontainers/Builders/DockerEndpointAuthenticationProvider.cs index 76d18058a..738050db8 100644 --- a/src/Testcontainers/Builders/DockerEndpointAuthenticationProvider.cs +++ b/src/Testcontainers/Builders/DockerEndpointAuthenticationProvider.cs @@ -41,7 +41,7 @@ public virtual bool IsAvailable() { using (var dockerClientConfiguration = authConfig.GetDockerClientConfiguration(ResourceReaper.DefaultSessionId)) { - using (var dockerClient = dockerClientConfiguration.CreateClient()) + using (var dockerClient = dockerClientConfiguration.CreateClient(authConfig.Version)) { try { diff --git a/src/Testcontainers/Clients/DockerApiClient.cs b/src/Testcontainers/Clients/DockerApiClient.cs index 172db1a09..d2cc9dedd 100644 --- a/src/Testcontainers/Clients/DockerApiClient.cs +++ b/src/Testcontainers/Clients/DockerApiClient.cs @@ -134,7 +134,7 @@ private static IDockerClient GetDockerClient(Guid sessionId, IDockerEndpointAuth { using (var dockerClientConfiguration = dockerEndpointAuthConfig.GetDockerClientConfiguration(sessionId)) { - return dockerClientConfiguration.CreateClient(TestcontainersSettings.DockerApiVersion); + return dockerClientConfiguration.CreateClient(dockerEndpointAuthConfig.Version); } } } diff --git a/src/Testcontainers/Configurations/AuthConfigs/DockerEndpointAuthenticationConfiguration.cs b/src/Testcontainers/Configurations/AuthConfigs/DockerEndpointAuthenticationConfiguration.cs index 94a0103fa..57c22671a 100644 --- a/src/Testcontainers/Configurations/AuthConfigs/DockerEndpointAuthenticationConfiguration.cs +++ b/src/Testcontainers/Configurations/AuthConfigs/DockerEndpointAuthenticationConfiguration.cs @@ -10,6 +10,9 @@ namespace DotNet.Testcontainers.Configurations [PublicAPI] public readonly struct DockerEndpointAuthenticationConfiguration : IDockerEndpointAuthenticationConfiguration { + // https://github.com/moby/moby/releases/tag/docker-v29.0.0. + private static readonly Version DockerEngineApi = EnvironmentConfiguration.Instance.GetDockerApiVersion() ?? PropertiesFileConfiguration.Instance.GetDockerApiVersion() ?? new Version(1, 44); + // Since the static `TestcontainersSettings` class holds the detected container // runtime information from the auto-discovery mechanism, we can't add a static // `NamedPipeConnectionTimeout` property to it because that would create a @@ -30,6 +33,9 @@ public DockerEndpointAuthenticationConfiguration(Uri endpoint, Credentials crede Credentials = credentials; } + /// + public Version Version => DockerEngineApi; + /// public Uri Endpoint { get; } diff --git a/src/Testcontainers/Configurations/AuthConfigs/IDockerEndpointAuthenticationConfiguration.cs b/src/Testcontainers/Configurations/AuthConfigs/IDockerEndpointAuthenticationConfiguration.cs index b303ce4a6..cafcf2277 100644 --- a/src/Testcontainers/Configurations/AuthConfigs/IDockerEndpointAuthenticationConfiguration.cs +++ b/src/Testcontainers/Configurations/AuthConfigs/IDockerEndpointAuthenticationConfiguration.cs @@ -10,6 +10,12 @@ namespace DotNet.Testcontainers.Configurations [PublicAPI] public interface IDockerEndpointAuthenticationConfiguration { + /// + /// Gets the Docker API version. + /// + [CanBeNull] + Version Version { get; } + /// /// Gets the Docker API endpoint. /// diff --git a/tests/Testcontainers.Tests/Fixtures/Containers/Unix/OpenSsl1_1_1Fixture.cs b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/OpenSsl1_1_1Fixture.cs index 3e87a9ab7..37705ec29 100644 --- a/tests/Testcontainers.Tests/Fixtures/Containers/Unix/OpenSsl1_1_1Fixture.cs +++ b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/OpenSsl1_1_1Fixture.cs @@ -5,7 +5,7 @@ namespace DotNet.Testcontainers.Tests.Fixtures [UsedImplicitly] public sealed class OpenSsl1_1_1Fixture : DockerMTls { - public OpenSsl1_1_1Fixture() : base("29.0.0") + public OpenSsl1_1_1Fixture() : base("20.10.18") { } } diff --git a/tests/Testcontainers.Tests/Unit/Containers/Unix/ProtectDockerDaemonSocketTest.cs b/tests/Testcontainers.Tests/Unit/Containers/Unix/ProtectDockerDaemonSocketTest.cs index e73f7367a..19dee143f 100644 --- a/tests/Testcontainers.Tests/Unit/Containers/Unix/ProtectDockerDaemonSocketTest.cs +++ b/tests/Testcontainers.Tests/Unit/Containers/Unix/ProtectDockerDaemonSocketTest.cs @@ -3,6 +3,7 @@ namespace DotNet.Testcontainers.Tests.Unit using System; using System.Linq; using System.Threading.Tasks; + using Docker.DotNet; using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Clients; using DotNet.Testcontainers.Configurations; @@ -20,7 +21,7 @@ private static IDockerEndpointAuthenticationConfiguration GetAuthConfig(ProtectD return new IDockerEndpointAuthenticationProvider[] { new MTlsEndpointAuthenticationProvider(customConfiguration), new TlsEndpointAuthenticationProvider(customConfiguration) }.First(authProvider => authProvider.IsApplicable()).GetAuthConfig(); } - public sealed class MTlsOpenSsl1_1_1 : IClassFixture + public sealed class MTlsOpenSsl1_1_1 : IClassFixture, IDockerEndpointAuthenticationConfiguration { private readonly ProtectDockerDaemonSocket _fixture; @@ -32,11 +33,25 @@ public MTlsOpenSsl1_1_1(OpenSsl1_1_1Fixture dockerMTlsFixture) _authConfig = GetAuthConfig(dockerMTlsFixture); } + // The outdated image isn't compatible with the default Docker Engine API version. + // For this test, we're overriding the version. + public Version Version + => null; + + public Uri Endpoint + => _authConfig.Endpoint; + + public Credentials Credentials + => _authConfig.Credentials; + + public DockerClientConfiguration GetDockerClientConfiguration(Guid sessionId = default) + => _authConfig.GetDockerClientConfiguration(sessionId); + [Fact] public async Task GetVersionReturnsVersion() { // Given - var client = new TestcontainersClient(Guid.Empty, _authConfig, NullLogger.Instance); + var client = new TestcontainersClient(Guid.Empty, this, NullLogger.Instance); // When var version = await client.System.GetVersionAsync(TestContext.Current.CancellationToken) From c7a749bfd8494200651f0cfb721104b09eb3e69c Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Fri, 14 Nov 2025 15:29:31 +0100 Subject: [PATCH 4/4] chore: Remove unnecessary change --- .../Configurations/TestcontainersSettings.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Testcontainers/Configurations/TestcontainersSettings.cs b/src/Testcontainers/Configurations/TestcontainersSettings.cs index cf316298b..cf72ac995 100644 --- a/src/Testcontainers/Configurations/TestcontainersSettings.cs +++ b/src/Testcontainers/Configurations/TestcontainersSettings.cs @@ -42,16 +42,6 @@ static TestcontainersSettings() { } - /// - /// Gets or sets the Docker API version. - /// - /// - /// https://github.com/moby/moby/releases/tag/docker-v29.0.0. - /// - [CanBeNull] - public static Version DockerApiVersion { get; set; } - = EnvironmentConfiguration.Instance.GetDockerApiVersion() ?? PropertiesFileConfiguration.Instance.GetDockerApiVersion() ?? new Version(1, 44); - /// /// Gets or sets the Docker host override value. ///