Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@
<RepositoryUrl>https://github.com/testcontainers/testcontainers-dotnet</RepositoryUrl>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)src/strongname.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<!-- Enable signing only in CI or when explicitly requested -->
<SignAssembly Condition="'$(SIGN_ASSEMBLY)' == 'true' or '$(CI)' == 'true'">true</SignAssembly>
<SignAssembly Condition="'$(SignAssembly)' == ''">false</SignAssembly>
<DebugType>embedded</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(SignAssembly)' == 'true'">
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)src/strongname.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<PropertyGroup>
<NoWarn>CA1859,CA1861,CS0618,CS1591,xUnit1044,xUnit1045</NoWarn>
</PropertyGroup>
Expand All @@ -41,9 +45,14 @@
<None Include="$(MSBuildThisFileDirectory)LICENSE" Visible="false" Pack="true" PackagePath="" />
<None Include="$(MSBuildThisFileDirectory)README.md" Visible="false" Pack="true" PackagePath="" />
</ItemGroup>
<ItemGroup>
<ItemGroup Condition="'$(SignAssembly)' == 'true'">
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Testcontainers.Tests, PublicKey=$([System.IO.File]::ReadAllText($(MSBuildThisFileDirectory)src/strongname.pub))</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
<ItemGroup Condition="'$(SignAssembly)' != 'true'">
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Testcontainers.Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>
30 changes: 29 additions & 1 deletion docs/modules/postgres.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,32 @@ The test example uses the following NuGet dependencies:

To execute the tests, use the command `dotnet test` from a terminal.

--8<-- "docs/modules/_call_out_test_projects.txt"
## Enable SSL/TLS

The PostgreSQL module supports configuring server-side SSL. Provide paths to your CA certificate, server certificate, and server private key when building the container:

```csharp
var postgreSqlContainer = new PostgreSqlBuilder()
.WithSSLSettings("/path/to/ca_cert.pem",
"/path/to/server.crt",
"/path/to/server.key")
.Build();
await postgreSqlContainer.StartAsync();
```

When connecting with Npgsql during tests, you can require SSL and (optionally) trust the test certificate:

```csharp
var csb = new Npgsql.NpgsqlConnectionStringBuilder(postgreSqlContainer.GetConnectionString())
{
SslMode = Npgsql.SslMode.Require,
// For testing only; prefer proper CA validation in production.
TrustServerCertificate = true
};
await using var connection = new Npgsql.NpgsqlConnection(csb.ConnectionString);
await connection.OpenAsync();
```

For production scenarios, validate the server certificate against a trusted CA instead of using TrustServerCertificate.

--8<-- "docs/modules/_call_out_test_projects.txt"
128 changes: 122 additions & 6 deletions src/Testcontainers.PostgreSql/PostgreSqlBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ namespace Testcontainers.PostgreSql;

/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
[PublicAPI]
public sealed class PostgreSqlBuilder : ContainerBuilder<PostgreSqlBuilder, PostgreSqlContainer, PostgreSqlConfiguration>
public sealed class
PostgreSqlBuilder : ContainerBuilder<PostgreSqlBuilder, PostgreSqlContainer, PostgreSqlConfiguration>
{
public const string PostgreSqlImage = "postgres:15.1";

Expand All @@ -14,6 +15,8 @@ public sealed class PostgreSqlBuilder : ContainerBuilder<PostgreSqlBuilder, Post

public const string DefaultPassword = "postgres";

private const string DefaultCertificatesDirectory = "/var/lib/postgresql/certs";

/// <summary>
/// Initializes a new instance of the <see cref="PostgreSqlBuilder" /> class.
/// </summary>
Expand Down Expand Up @@ -69,14 +72,122 @@ public PostgreSqlBuilder WithPassword(string password)
.WithEnvironment("POSTGRES_PASSWORD", password);
}

/// <summary>
/// Sets the PostgreSql SSL mode.
/// </summary>
/// <param name="sslMode">The PostgreSql SSL mode.</param>
/// <returns>A configured instance of <see cref="PostgreSqlBuilder" />.</returns>
public PostgreSqlBuilder WithSslMode(SslMode sslMode)
{
return Merge(DockerResourceConfiguration, new PostgreSqlConfiguration(sslMode: sslMode))
.WithEnvironment("PGSSLMODE", sslMode.ToString().ToLowerInvariant());
}
Comment on lines +75 to +84
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify SslMode enum values match PostgreSQL expectations.

The implementation converts the SslMode enum to lowercase for the PGSSLMODE environment variable. Ensure that the enum values (e.g., Disable, Require, VerifyCa, VerifyFull) exactly match PostgreSQL's expected values when lowercased (e.g., disable, require, verify-ca, verify-full).


🏁 Script executed:

#!/bin/bash
# Verify SslMode enum definition matches PostgreSQL SSL mode values
ast-grep --pattern 'enum SslMode {
  $$$
}'

Length of output: 0


Let me search more broadly for the SslMode enum definition:




---

🏁 Script executed:

```shell
#!/bin/bash
# Search for SslMode enum definition using ripgrep
rg -n "enum SslMode" --type=cs

Length of output: 62


🏁 Script executed:

#!/bin/bash
# Also search for where SslMode is used/imported in the PostgreSqlBuilder file
rg -n "SslMode" src/Testcontainers.PostgreSql/PostgreSqlBuilder.cs

Length of output: 60


🏁 Script executed:

#!/bin/bash
# Find all C# files that might contain SslMode definition
fd -e cs --exec rg -l "SslMode" {} \;

Length of output: 282






🏁 Script executed:

#!/bin/bash
# Read the SslMode enum definition
cat src/Testcontainers.PostgreSql/SslMode.cs

Length of output: 610


🌐 Web query:

PostgreSQL PGSSLMODE valid values allowed

💡 Result:

Valid PGSSLMODE (sslmode) values:

  • disable, allow, prefer, require, verify-ca, verify-full. Default: prefer. Meanings: disable = no SSL; allow/prefer = fallbacks; require = encrypt but no cert validation; verify-ca = encrypt + CA validation; verify-full = encrypt + CA + hostname check. [1][2]

Fix enum value conversion for VerifyCa and VerifyFull SSL modes.

The current implementation has a critical issue: enum values VerifyCa and VerifyFull will convert to verifyca and verifyfull, but PostgreSQL expects verify-ca and verify-full with hyphens. The ToString().ToLowerInvariant() conversion removes the case-based word boundaries.

Solution: Either (1) rename the enum values to VerifyCaWithHyphens pattern, or (2) add an explicit mapping/switch statement to convert enum values to the correct PostgreSQL format with hyphens.

🤖 Prompt for AI Agents
In src/Testcontainers.PostgreSql/PostgreSqlBuilder.cs around lines 75 to 84, the
current ToString().ToLowerInvariant() call produces "verifyca" and "verifyfull"
for SslMode.VerifyCa and SslMode.VerifyFull but PostgreSQL expects "verify-ca"
and "verify-full"; replace the direct ToString call with an explicit mapping
(e.g., switch or dictionary) that returns "verify-ca" for VerifyCa,
"verify-full" for VerifyFull, and falls back to ToString().ToLowerInvariant()
for other values, then pass that mapped string to WithEnvironment("PGSSLMODE",
mappedValue).


/// <summary>
/// Sets the PostgreSql root certificate file.
/// </summary>
/// <param name="rootCertFile">The path to the root certificate file.</param>
/// <returns>A configured instance of <see cref="PostgreSqlBuilder" />.</returns>
public PostgreSqlBuilder WithRootCertificate(string rootCertFile)
{
return Merge(DockerResourceConfiguration, new PostgreSqlConfiguration(rootCertFile: rootCertFile))
.WithBindMount(rootCertFile, Path.Combine(DefaultCertificatesDirectory, "root.crt"), AccessMode.ReadOnly)
.WithEnvironment("PGSSLROOTCERT", Path.Combine(DefaultCertificatesDirectory, "root.crt"));
}

/// <summary>
/// Sets the PostgreSql client certificate and key files.
/// </summary>
/// <param name="clientCertFile">The path to the client certificate file.</param>
/// <param name="clientKeyFile">The path to the client key file.</param>
/// <returns>A configured instance of <see cref="PostgreSqlBuilder" />.</returns>
public PostgreSqlBuilder WithClientCertificate(string clientCertFile, string clientKeyFile)
{
return Merge(DockerResourceConfiguration,
new PostgreSqlConfiguration(clientCertFile: clientCertFile, clientKeyFile: clientKeyFile))
.WithBindMount(clientCertFile, Path.Combine(DefaultCertificatesDirectory, "postgresql.crt"),
AccessMode.ReadOnly)
.WithBindMount(clientKeyFile, Path.Combine(DefaultCertificatesDirectory, "postgresql.key"),
AccessMode.ReadOnly)
.WithEnvironment("PGSSLCERT", Path.Combine(DefaultCertificatesDirectory, "postgresql.crt"))
.WithEnvironment("PGSSLKEY", Path.Combine(DefaultCertificatesDirectory, "postgresql.key"));
}

/// <summary>
/// Configures the PostgreSQL server to run with SSL using the provided CA certificate, server certificate and private key.
/// This enables server-side SSL configuration with client certificate authentication.
/// </summary>
/// <param name="caCertFile">The path to the CA certificate file.</param>
/// <param name="serverCertFile">The path to the server certificate file.</param>
/// <param name="serverKeyFile">The path to the server private key file.</param>
/// <returns>A configured instance of <see cref="PostgreSqlBuilder" />.</returns>
/// <remarks>
/// This method configures PostgreSQL for server-side SSL with client certificate authentication.
/// It requires a custom PostgreSQL configuration file that enables SSL and sets the appropriate
/// certificate paths. The certificates are mounted into the container and PostgreSQL is configured
/// to use them for SSL connections.
/// </remarks>
public PostgreSqlBuilder WithSSLSettings(string caCertFile, string serverCertFile, string serverKeyFile)
{
if (string.IsNullOrWhiteSpace(caCertFile))
{
throw new ArgumentException("CA certificate file path cannot be null or empty.", nameof(caCertFile));
}

if (string.IsNullOrWhiteSpace(serverCertFile))
{
throw new ArgumentException("Server certificate file path cannot be null or empty.",
nameof(serverCertFile));
}

if (string.IsNullOrWhiteSpace(serverKeyFile))
{
throw new ArgumentException("Server key file path cannot be null or empty.", nameof(serverKeyFile));
}

const string sslConfigDir = "/tmp/testcontainers-dotnet/postgres";

var wrapperEntrypoint = @"#!/bin/sh
set -e
SSL_DIR=/tmp/testcontainers-dotnet/postgres
# Fix ownership and permissions for SSL key/cert before Postgres init runs
if [ -f ""$SSL_DIR/server.key"" ]; then
chown postgres:postgres ""$SSL_DIR/server.key"" || true
chmod 600 ""$SSL_DIR/server.key"" || true
fi
if [ -f ""$SSL_DIR/server.crt"" ]; then
chown postgres:postgres ""$SSL_DIR/server.crt"" || true
fi
if [ -f ""$SSL_DIR/ca_cert.pem"" ]; then
chown postgres:postgres ""$SSL_DIR/ca_cert.pem"" || true
fi
exec /usr/local/bin/docker-entrypoint.sh ""$@""
";
Comment on lines +150 to +165
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Consider the implications of suppressing permission errors.

The shell script uses || true to ignore failures from chown and chmod operations. While this may be necessary in some container environments where these operations fail, it could also hide legitimate permission problems that would cause PostgreSQL to fail startup with unclear errors.

Consider either:

  1. Logging when these operations fail (if the container environment supports it)
  2. Adding a comment explaining why failures are acceptable
  3. Testing that PostgreSQL startup will fail clearly if permissions are incorrect


return Merge(DockerResourceConfiguration, new PostgreSqlConfiguration(
serverCertFile: serverCertFile,
serverKeyFile: serverKeyFile,
caCertFile: caCertFile))
.WithResourceMapping(File.ReadAllBytes(caCertFile), $"{sslConfigDir}/ca_cert.pem", fileMode: Unix.FileMode644)
.WithResourceMapping(File.ReadAllBytes(serverCertFile), $"{sslConfigDir}/server.crt", fileMode: Unix.FileMode644)
.WithResourceMapping(File.ReadAllBytes(serverKeyFile), $"{sslConfigDir}/server.key", fileMode: Unix.FileMode700)
.WithResourceMapping(Encoding.UTF8.GetBytes(wrapperEntrypoint), "/usr/local/bin/docker-entrypoint-ssl.sh", fileMode: Unix.FileMode755)
.WithEntrypoint("/usr/local/bin/docker-entrypoint-ssl.sh")
.WithCommand("-c", "ssl=on")
.WithCommand("-c", $"ssl_ca_file={sslConfigDir}/ca_cert.pem")
.WithCommand("-c", $"ssl_cert_file={sslConfigDir}/server.crt")
.WithCommand("-c", $"ssl_key_file={sslConfigDir}/server.key");
}

/// <inheritdoc />
public override PostgreSqlContainer Build()
{
Validate();

// By default, the base builder waits until the container is running. However, for PostgreSql, a more advanced waiting strategy is necessary that requires access to the configured database and username.
// If the user does not provide a custom waiting strategy, append the default PostgreSql waiting strategy.
var postgreSqlBuilder = DockerResourceConfiguration.WaitStrategies.Count() > 1 ? this : WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil(DockerResourceConfiguration)));
// Ensure PostgreSQL is actually ready to accept connections over TCP, not just that the container is running.
// Always append the pg_isready-based wait strategy by default so tests using the default fixture are stable.
var postgreSqlBuilder =
WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil(DockerResourceConfiguration)));
return new PostgreSqlContainer(postgreSqlBuilder.DockerResourceConfiguration);
}
Comment on lines +187 to 192
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Clarify wait strategy behavior and verify non-breaking change.

The comment states "Always append the pg_isready-based wait strategy" but the code calls WithWaitStrategy(...), which may replace rather than append to existing wait strategies. Verify whether this is a breaking change for users who set custom wait strategies before calling Build().


🏁 Script executed:

#!/bin/bash
# Check how WithWaitStrategy works - does it append or replace?
ast-grep --pattern 'WithWaitStrategy($$$) {
  $$$
}'

Length of output: 0


I'll search for the WithWaitStrategy method implementation to verify its behavior:


---

🏁 Script executed:

```shell
#!/bin/bash
# Search for WithWaitStrategy method definition and implementation
rg -n "WithWaitStrategy" --type=cs -C5

Length of output: 115359


Add check to preserve user-provided wait strategies in PostgreSqlBuilder.Build().

The code unconditionally calls WithWaitStrategy(), which replaces any custom wait strategies users may have configured. Other database builders (MariaDb, MySql, MongoDb, etc.) check DockerResourceConfiguration.WaitStrategies.Count() > 1 to avoid overwriting user-provided strategies.

Additionally, the comment states "Always append" but the code replaces rather than appends. The PostgreSQL builder should:

  1. Only apply the pg_isready wait strategy if the user hasn't already configured a custom one
  2. Update the comment to accurately reflect the behavior

Apply the same pattern used in MariaDbBuilder.Build() at line 80:

var postgreSqlBuilder = DockerResourceConfiguration.WaitStrategies.Count() > 1 
    ? this 
    : WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil(DockerResourceConfiguration)));
🤖 Prompt for AI Agents
In src/Testcontainers.PostgreSql/PostgreSqlBuilder.cs around lines 187 to 192,
the Build() method unconditionally calls WithWaitStrategy which replaces any
user-provided wait strategies and contradicts the "Always append" comment;
change the logic to follow the MariaDbBuilder pattern by only applying the
pg_isready wait strategy when no custom wait strategies exist (i.e., when
DockerResourceConfiguration.WaitStrategies.Count() <= 1) so existing user
strategies are preserved, and update the preceding comment to accurately state
that the pg_isready wait strategy is appended only when the user hasn't
configured a custom wait strategy.


Expand Down Expand Up @@ -135,7 +246,11 @@ private sealed class WaitUntil : IWaitUntil
public WaitUntil(PostgreSqlConfiguration configuration)
{
// Explicitly specify the host to ensure readiness only after the initdb scripts have executed, and the server is listening on TCP/IP.
_command = new List<string> { "pg_isready", "--host", "localhost", "--dbname", configuration.Database, "--username", configuration.Username };
_command = new List<string>
{
"pg_isready", "--host", "localhost", "--dbname", configuration.Database, "--username",
configuration.Username
};
}

/// <summary>
Expand All @@ -154,7 +269,8 @@ public async Task<bool> UntilAsync(IContainer container)

if (execResult.Stderr.Contains("pg_isready was not found"))
{
throw new NotSupportedException($"The '{container.Image.FullName}' image does not contain: pg_isready. Please use 'postgres:9.3' onwards.");
throw new NotSupportedException(
$"The '{container.Image.FullName}' image does not contain: pg_isready. Please use 'postgres:9.3' onwards.");
}

return 0L.Equals(execResult.ExitCode);
Expand Down
65 changes: 64 additions & 1 deletion src/Testcontainers.PostgreSql/PostgreSqlConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,35 @@ public sealed class PostgreSqlConfiguration : ContainerConfiguration
/// <param name="database">The PostgreSql database.</param>
/// <param name="username">The PostgreSql username.</param>
/// <param name="password">The PostgreSql password.</param>
/// <param name="sslMode">The PostgreSql SSL mode.</param>
/// <param name="rootCertFile">The path to the PostgreSql root certificate file.</param>
/// <param name="clientCertFile">The path to the PostgreSql client certificate file.</param>
/// <param name="clientKeyFile">The path to the PostgreSql client key file.</param>
/// <param name="serverCertFile">The path to the PostgreSql server certificate file.</param>
/// <param name="serverKeyFile">The path to the PostgreSql server key file.</param>
/// <param name="caCertFile">The path to the PostgreSql CA certificate file.</param>
public PostgreSqlConfiguration(
string database = null,
string username = null,
string password = null)
string password = null,
SslMode? sslMode = null,
string rootCertFile = null,
string clientCertFile = null,
string clientKeyFile = null,
string serverCertFile = null,
string serverKeyFile = null,
string caCertFile = null)
{
Database = database;
Username = username;
Password = password;
SslMode = sslMode;
RootCertFile = rootCertFile;
ClientCertFile = clientCertFile;
ClientKeyFile = clientKeyFile;
ServerCertFile = serverCertFile;
ServerKeyFile = serverKeyFile;
CaCertFile = caCertFile;
}

/// <summary>
Expand Down Expand Up @@ -61,6 +82,13 @@ public PostgreSqlConfiguration(PostgreSqlConfiguration oldValue, PostgreSqlConfi
Database = BuildConfiguration.Combine(oldValue.Database, newValue.Database);
Username = BuildConfiguration.Combine(oldValue.Username, newValue.Username);
Password = BuildConfiguration.Combine(oldValue.Password, newValue.Password);
SslMode = BuildConfiguration.Combine(oldValue.SslMode, newValue.SslMode);
RootCertFile = BuildConfiguration.Combine(oldValue.RootCertFile, newValue.RootCertFile);
ClientCertFile = BuildConfiguration.Combine(oldValue.ClientCertFile, newValue.ClientCertFile);
ClientKeyFile = BuildConfiguration.Combine(oldValue.ClientKeyFile, newValue.ClientKeyFile);
ServerCertFile = BuildConfiguration.Combine(oldValue.ServerCertFile, newValue.ServerCertFile);
ServerKeyFile = BuildConfiguration.Combine(oldValue.ServerKeyFile, newValue.ServerKeyFile);
CaCertFile = BuildConfiguration.Combine(oldValue.CaCertFile, newValue.CaCertFile);
}

/// <summary>
Expand All @@ -77,4 +105,39 @@ public PostgreSqlConfiguration(PostgreSqlConfiguration oldValue, PostgreSqlConfi
/// Gets the PostgreSql password.
/// </summary>
public string Password { get; }

/// <summary>
/// Gets the PostgreSql SSL mode.
/// </summary>
public SslMode? SslMode { get; }

/// <summary>
/// Gets the path to the PostgreSql root certificate file.
/// </summary>
public string RootCertFile { get; }

/// <summary>
/// Gets the path to the PostgreSql client certificate file.
/// </summary>
public string ClientCertFile { get; }

/// <summary>
/// Gets the path to the PostgreSql client key file.
/// </summary>
public string ClientKeyFile { get; }

/// <summary>
/// Gets the path to the PostgreSql server certificate file.
/// </summary>
public string ServerCertFile { get; }

/// <summary>
/// Gets the path to the PostgreSql server key file.
/// </summary>
public string ServerKeyFile { get; }

/// <summary>
/// Gets the path to the PostgreSql CA certificate file.
/// </summary>
public string CaCertFile { get; }
}
2 changes: 1 addition & 1 deletion src/Testcontainers.PostgreSql/PostgreSqlContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public async Task<ExecResult> ExecScriptAsync(string scriptContent, Cancellation
await CopyAsync(Encoding.Default.GetBytes(scriptContent), scriptFilePath, fileMode: Unix.FileMode644, ct: ct)
.ConfigureAwait(false);

return await ExecAsync(new[] { "psql", "--username", _configuration.Username, "--dbname", _configuration.Database, "--file", scriptFilePath }, ct)
return await ExecAsync(new[] { "psql", "--host", "localhost", "--username", _configuration.Username, "--dbname", _configuration.Database, "--file", scriptFilePath }, ct)
.ConfigureAwait(false);
Comment on lines +47 to 48
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t force TCP localhost without providing credentials — risk of password prompt/failure.

With --host localhost, libpq won’t use the Unix socket/peer auth; if PGPASSWORD isn’t set in the container, psql may fail. Use a DSN embedding creds (escaped) or make TCP conditional on SSL mode.

Apply this diff:

-        return await ExecAsync(new[] { "psql", "--host", "localhost", "--username", _configuration.Username, "--dbname", _configuration.Database, "--file", scriptFilePath }, ct)
+        var dsn = $"postgresql://{Uri.EscapeDataString(_configuration.Username)}:{Uri.EscapeDataString(_configuration.Password)}@localhost:{PostgreSqlBuilder.PostgreSqlPort}/{_configuration.Database}";
+        return await ExecAsync(new[] { "psql", dsn, "--file", scriptFilePath }, ct)
             .ConfigureAwait(false);

Alternative: only add --host localhost when SSL is enabled, otherwise keep the default socket path to preserve prior behavior.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return await ExecAsync(new[] { "psql", "--host", "localhost", "--username", _configuration.Username, "--dbname", _configuration.Database, "--file", scriptFilePath }, ct)
.ConfigureAwait(false);
var dsn = $"postgresql://{Uri.EscapeDataString(_configuration.Username)}:{Uri.EscapeDataString(_configuration.Password)}@localhost:{PostgreSqlBuilder.PostgreSqlPort}/{_configuration.Database}";
return await ExecAsync(new[] { "psql", dsn, "--file", scriptFilePath }, ct)
.ConfigureAwait(false);
🤖 Prompt for AI Agents
In src/Testcontainers.PostgreSql/PostgreSqlContainer.cs around lines 47-48, the
call to psql currently forces TCP by passing "--host localhost", which prevents
using the Unix socket and can trigger a password prompt if PGPASSWORD is not
set; remove the hardcoded --host or make it conditional: either (A) omit "--host
localhost" to use the default socket when SSL is not required, or (B) when you
must force TCP (e.g. SSL enabled), build a proper connection string/DSN that
embeds and escapes username/password/host/dbname (or set PGPASSWORD in the
environment passed to ExecAsync) and pass that via "--dbname" (or a connection
URI), ensuring credentials are escaped to avoid shell injection. Ensure the code
branches so that --host is only added when TCP/SSL is required and credentials
are provided via a secure, escaped DSN or environment variable.

}
}
27 changes: 27 additions & 0 deletions src/Testcontainers.PostgreSql/SslMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace Testcontainers.PostgreSql;

/// <summary>
/// Represents the SSL mode for PostgreSQL connections.
/// </summary>
public enum SslMode
{
/// <summary>
/// SSL is disabled.
/// </summary>
Disable,

/// <summary>
/// SSL is required.
/// </summary>
Require,

/// <summary>
/// SSL is required, and the server certificate is verified against the root certificate.
/// </summary>
VerifyCa,

/// <summary>
/// SSL is required, and the server certificate is verified against the root certificate and the common name.
/// </summary>
VerifyFull
}
Loading
Loading