diff --git a/.env.example b/.env.example
index 6c4adec6..5eeaf6bb 100644
--- a/.env.example
+++ b/.env.example
@@ -18,6 +18,10 @@ PGUSER=your_username
# Database password
PGPASSWORD=your_password_here
+# SSL mode for database connection (default: prefer)
+# Valid values: disable, allow, prefer, require, verify-ca, verify-full
+#PGSSLMODE=prefer
+
# Application name for database connection (default: pgschema)
# This appears in pg_stat_activity and can help identify connections
PGAPPNAME=pgschema
@@ -40,4 +44,7 @@ PGAPPNAME=pgschema
#PGSCHEMA_PLAN_USER=postgres
# Plan database password
-#PGSCHEMA_PLAN_PASSWORD=your_plan_db_password
\ No newline at end of file
+#PGSCHEMA_PLAN_PASSWORD=your_plan_db_password
+
+# Plan database SSL mode (default: prefer)
+#PGSCHEMA_PLAN_SSLMODE=prefer
\ No newline at end of file
diff --git a/cmd/apply/apply.go b/cmd/apply/apply.go
index 657cb3af..a699cb9d 100644
--- a/cmd/apply/apply.go
+++ b/cmd/apply/apply.go
@@ -38,6 +38,9 @@ var (
applyPlanDBDatabase string
applyPlanDBUser string
applyPlanDBPassword string
+
+ applySSLMode string
+ applyPlanDBSSLMode string
)
var ApplyCmd = &cobra.Command{
@@ -76,6 +79,10 @@ func init() {
ApplyCmd.Flags().StringVar(&applyPlanDBDatabase, "plan-db", "", "Plan database name (env: PGSCHEMA_PLAN_DB)")
ApplyCmd.Flags().StringVar(&applyPlanDBUser, "plan-user", "", "Plan database user (env: PGSCHEMA_PLAN_USER)")
ApplyCmd.Flags().StringVar(&applyPlanDBPassword, "plan-password", "", "Plan database password (env: PGSCHEMA_PLAN_PASSWORD)")
+ ApplyCmd.Flags().StringVar(&applyPlanDBSSLMode, "plan-sslmode", "prefer", "Plan database SSL mode (env: PGSCHEMA_PLAN_SSLMODE)")
+
+ // SSL mode flag
+ ApplyCmd.Flags().StringVar(&applySSLMode, "sslmode", "prefer", "SSL mode for database connection (disable, allow, prefer, require, verify-ca, verify-full) (env: PGSSLMODE)")
// Mark file and plan as mutually exclusive
ApplyCmd.MarkFlagsMutuallyExclusive("file", "plan")
@@ -96,6 +103,10 @@ type ApplyConfig struct {
Quiet bool // Suppress plan display and progress messages (useful for tests)
LockTimeout string
ApplicationName string
+ SSLMode string
+ // Plan database configuration (needed when GeneratePlan checks provider SSL mode)
+ PlanDBHost string
+ PlanDBSSLMode string
}
// ApplyMigration applies a migration plan to update a database schema.
@@ -127,6 +138,9 @@ func ApplyMigration(config *ApplyConfig, provider postgres.DesiredStateProvider)
Schema: config.Schema,
File: config.File,
ApplicationName: config.ApplicationName,
+ SSLMode: config.SSLMode,
+ PlanDBHost: config.PlanDBHost,
+ PlanDBSSLMode: config.PlanDBSSLMode,
}
// Generate plan using shared logic
@@ -146,7 +160,7 @@ func ApplyMigration(config *ApplyConfig, provider postgres.DesiredStateProvider)
// Validate schema fingerprint if plan has one
if migrationPlan.SourceFingerprint != nil {
- err := validateSchemaFingerprint(migrationPlan, config.Host, config.Port, config.DB, config.User, config.Password, config.Schema, config.ApplicationName, ignoreConfig)
+ err := validateSchemaFingerprint(migrationPlan, config.Host, config.Port, config.DB, config.User, config.Password, config.SSLMode, config.Schema, config.ApplicationName, ignoreConfig)
if err != nil {
return err
}
@@ -191,7 +205,7 @@ func ApplyMigration(config *ApplyConfig, provider postgres.DesiredStateProvider)
Database: config.DB,
User: config.User,
Password: config.Password,
- SSLMode: "prefer",
+ SSLMode: config.SSLMode,
ApplicationName: config.ApplicationName,
}
@@ -265,6 +279,19 @@ func RunApply(cmd *cobra.Command, args []string) error {
}
}
+ // Derive final sslmode: use flag if explicitly set, otherwise check environment variable
+ finalSSLMode := applySSLMode
+ if cmd == nil || !cmd.Flags().Changed("sslmode") {
+ if envSSLMode := os.Getenv("PGSSLMODE"); envSSLMode != "" {
+ finalSSLMode = envSSLMode
+ }
+ }
+
+ // Validate sslmode
+ if err := util.ValidateSSLMode(finalSSLMode); err != nil {
+ return err
+ }
+
// Build configuration
config := &ApplyConfig{
Host: applyHost,
@@ -277,6 +304,7 @@ func RunApply(cmd *cobra.Command, args []string) error {
NoColor: applyNoColor,
LockTimeout: applyLockTimeout,
ApplicationName: applyApplicationName,
+ SSLMode: finalSSLMode,
}
var provider postgres.DesiredStateProvider
@@ -312,13 +340,20 @@ func RunApply(cmd *cobra.Command, args []string) error {
config.File = applyFile
// Apply environment variables to plan database flags (only needed for File Mode)
- util.ApplyPlanDBEnvVars(cmd, &applyPlanDBHost, &applyPlanDBDatabase, &applyPlanDBUser, &applyPlanDBPassword, &applyPlanDBPort)
+ util.ApplyPlanDBEnvVars(cmd, &applyPlanDBHost, &applyPlanDBDatabase, &applyPlanDBUser, &applyPlanDBPassword, &applyPlanDBPort, &applyPlanDBSSLMode)
// Validate plan database flags if plan-host is provided
if err := util.ValidatePlanDBFlags(applyPlanDBHost, applyPlanDBDatabase, applyPlanDBUser); err != nil {
return err
}
+ // Validate plan database sslmode if plan-host is provided
+ if applyPlanDBHost != "" {
+ if err := util.ValidateSSLMode(applyPlanDBSSLMode); err != nil {
+ return fmt.Errorf("plan database: %w", err)
+ }
+ }
+
// Derive final plan database password
finalPlanPassword := applyPlanDBPassword
if finalPlanPassword == "" {
@@ -337,18 +372,24 @@ func RunApply(cmd *cobra.Command, args []string) error {
Schema: applySchema,
File: applyFile,
ApplicationName: applyApplicationName,
+ SSLMode: finalSSLMode,
// Plan database configuration
PlanDBHost: applyPlanDBHost,
PlanDBPort: applyPlanDBPort,
PlanDBDatabase: applyPlanDBDatabase,
PlanDBUser: applyPlanDBUser,
PlanDBPassword: finalPlanPassword,
+ PlanDBSSLMode: applyPlanDBSSLMode,
}
provider, err = planCmd.CreateDesiredStateProvider(planConfig)
if err != nil {
return err
}
defer provider.Stop()
+
+ // Propagate plan DB fields so ApplyMigration -> GeneratePlan knows the provider type
+ config.PlanDBHost = applyPlanDBHost
+ config.PlanDBSSLMode = applyPlanDBSSLMode
}
// Apply the migration
@@ -356,10 +397,10 @@ func RunApply(cmd *cobra.Command, args []string) error {
}
// validateSchemaFingerprint validates that the current database schema matches the expected fingerprint
-func validateSchemaFingerprint(migrationPlan *plan.Plan, host string, port int, db, user, password, schema, applicationName string, ignoreConfig *ir.IgnoreConfig) error {
+func validateSchemaFingerprint(migrationPlan *plan.Plan, host string, port int, db, user, password, sslmode, schema, applicationName string, ignoreConfig *ir.IgnoreConfig) error {
// Get current state from target database with ignore config
// This ensures ignored objects are excluded from fingerprint calculation
- currentStateIR, err := util.GetIRFromDatabase(host, port, db, user, password, schema, applicationName, ignoreConfig)
+ currentStateIR, err := util.GetIRFromDatabase(host, port, db, user, password, sslmode, schema, applicationName, ignoreConfig)
if err != nil {
return fmt.Errorf("failed to get current database state for fingerprint validation: %w", err)
}
diff --git a/cmd/dump/dump.go b/cmd/dump/dump.go
index 7fcd89ec..59fc167e 100644
--- a/cmd/dump/dump.go
+++ b/cmd/dump/dump.go
@@ -21,6 +21,7 @@ var (
multiFile bool
file string
noComments bool
+ sslmode string
)
// DumpConfig holds configuration for dump execution
@@ -34,6 +35,7 @@ type DumpConfig struct {
MultiFile bool
File string
NoComments bool
+ SSLMode string
}
var DumpCmd = &cobra.Command{
@@ -55,6 +57,7 @@ func init() {
DumpCmd.Flags().BoolVar(&multiFile, "multi-file", false, "Output schema to multiple files organized by object type")
DumpCmd.Flags().StringVar(&file, "file", "", "Output file path (required when --multi-file is used)")
DumpCmd.Flags().BoolVar(&noComments, "no-comments", false, "Do not output object comment headers")
+ DumpCmd.Flags().StringVar(&sslmode, "sslmode", "prefer", "SSL mode for database connection (disable, allow, prefer, require, verify-ca, verify-full) (env: PGSSLMODE)")
}
// ExecuteDump executes the dump operation with the given configuration
@@ -73,7 +76,7 @@ func ExecuteDump(config *DumpConfig) (string, error) {
}
// Get IR from database using the shared utility
- schemaIR, err := util.GetIRFromDatabase(config.Host, config.Port, config.DB, config.User, config.Password, config.Schema, "pgschema", ignoreConfig)
+ schemaIR, err := util.GetIRFromDatabase(config.Host, config.Port, config.DB, config.User, config.Password, config.SSLMode, config.Schema, "pgschema", ignoreConfig)
if err != nil {
return "", fmt.Errorf("failed to get database schema: %w", err)
}
@@ -110,6 +113,19 @@ func runDump(cmd *cobra.Command, args []string) error {
}
}
+ // Derive final sslmode: use flag if explicitly set, otherwise check environment variable
+ finalSSLMode := sslmode
+ if cmd == nil || !cmd.Flags().Changed("sslmode") {
+ if envSSLMode := os.Getenv("PGSSLMODE"); envSSLMode != "" {
+ finalSSLMode = envSSLMode
+ }
+ }
+
+ // Validate sslmode
+ if err := util.ValidateSSLMode(finalSSLMode); err != nil {
+ return err
+ }
+
// Create config from command-line flags
config := &DumpConfig{
Host: host,
@@ -121,6 +137,7 @@ func runDump(cmd *cobra.Command, args []string) error {
MultiFile: multiFile,
File: file,
NoComments: noComments,
+ SSLMode: finalSSLMode,
}
// Execute dump
diff --git a/cmd/plan/external_db_integration_test.go b/cmd/plan/external_db_integration_test.go
index b1357ec5..4aa19972 100644
--- a/cmd/plan/external_db_integration_test.go
+++ b/cmd/plan/external_db_integration_test.go
@@ -109,6 +109,7 @@ func TestExternalDatabase_VersionMismatch(t *testing.T) {
targetDatabase,
targetUser,
targetPassword,
+ "prefer",
)
require.NoError(t, err, "should detect PostgreSQL version")
assert.NotEmpty(t, pgVersion, "version should not be empty")
diff --git a/cmd/plan/plan.go b/cmd/plan/plan.go
index b2623fdb..afd5e3bd 100644
--- a/cmd/plan/plan.go
+++ b/cmd/plan/plan.go
@@ -37,6 +37,9 @@ var (
planDBDatabase string
planDBUser string
planDBPassword string
+
+ planSSLMode string
+ planDBSSLMode string
)
var PlanCmd = &cobra.Command{
@@ -66,6 +69,10 @@ func init() {
PlanCmd.Flags().StringVar(&planDBDatabase, "plan-db", "", "Plan database name (env: PGSCHEMA_PLAN_DB)")
PlanCmd.Flags().StringVar(&planDBUser, "plan-user", "", "Plan database user (env: PGSCHEMA_PLAN_USER)")
PlanCmd.Flags().StringVar(&planDBPassword, "plan-password", "", "Plan database password (env: PGSCHEMA_PLAN_PASSWORD)")
+ PlanCmd.Flags().StringVar(&planDBSSLMode, "plan-sslmode", "prefer", "Plan database SSL mode (env: PGSCHEMA_PLAN_SSLMODE)")
+
+ // SSL mode flag
+ PlanCmd.Flags().StringVar(&planSSLMode, "sslmode", "prefer", "SSL mode for database connection (disable, allow, prefer, require, verify-ca, verify-full) (env: PGSSLMODE)")
// Output flags
PlanCmd.Flags().StringVar(&outputHuman, "output-human", "", "Output human-readable format to stdout or file path")
@@ -78,7 +85,7 @@ func init() {
func runPlan(cmd *cobra.Command, args []string) error {
// Apply environment variables to plan database flags
- util.ApplyPlanDBEnvVars(cmd, &planDBHost, &planDBDatabase, &planDBUser, &planDBPassword, &planDBPort)
+ util.ApplyPlanDBEnvVars(cmd, &planDBHost, &planDBDatabase, &planDBUser, &planDBPassword, &planDBPort, &planDBSSLMode)
// Validate plan database flags if plan-host is provided
if err := util.ValidatePlanDBFlags(planDBHost, planDBDatabase, planDBUser); err != nil {
@@ -93,6 +100,14 @@ func runPlan(cmd *cobra.Command, args []string) error {
}
}
+ // Derive final sslmode: use flag if explicitly set, otherwise check environment variable
+ finalSSLMode := planSSLMode
+ if cmd == nil || !cmd.Flags().Changed("sslmode") {
+ if envSSLMode := os.Getenv("PGSSLMODE"); envSSLMode != "" {
+ finalSSLMode = envSSLMode
+ }
+ }
+
// Derive final plan database password
finalPlanPassword := planDBPassword
if finalPlanPassword == "" {
@@ -101,6 +116,16 @@ func runPlan(cmd *cobra.Command, args []string) error {
}
}
+ // Validate sslmode values
+ if err := util.ValidateSSLMode(finalSSLMode); err != nil {
+ return err
+ }
+ if planDBHost != "" {
+ if err := util.ValidateSSLMode(planDBSSLMode); err != nil {
+ return fmt.Errorf("plan database: %w", err)
+ }
+ }
+
// Create plan configuration
config := &PlanConfig{
Host: planHost,
@@ -111,12 +136,14 @@ func runPlan(cmd *cobra.Command, args []string) error {
Schema: planSchema,
File: planFile,
ApplicationName: "pgschema",
+ SSLMode: finalSSLMode,
// Plan database configuration
PlanDBHost: planDBHost,
PlanDBPort: planDBPort,
PlanDBDatabase: planDBDatabase,
PlanDBUser: planDBUser,
PlanDBPassword: finalPlanPassword,
+ PlanDBSSLMode: planDBSSLMode,
}
// Create desired state provider (embedded postgres or external database)
@@ -164,6 +191,8 @@ type PlanConfig struct {
PlanDBDatabase string
PlanDBUser string
PlanDBPassword string
+ SSLMode string
+ PlanDBSSLMode string
}
// CreateDesiredStateProvider creates either an embedded PostgreSQL instance or connects to an external database
@@ -176,6 +205,7 @@ func CreateDesiredStateProvider(config *PlanConfig) (postgres.DesiredStateProvid
config.DB,
config.User,
config.Password,
+ config.SSLMode,
)
if err != nil {
return nil, fmt.Errorf("failed to detect PostgreSQL version: %w", err)
@@ -197,6 +227,7 @@ func CreateDesiredStateProvider(config *PlanConfig) (postgres.DesiredStateProvid
Database: config.PlanDBDatabase,
Username: config.PlanDBUser,
Password: config.PlanDBPassword,
+ SSLMode: config.PlanDBSSLMode,
TargetMajorVersion: targetMajorVersion,
}
return postgres.NewExternalDatabase(externalConfig)
@@ -250,7 +281,7 @@ func GeneratePlan(config *PlanConfig, provider postgres.DesiredStateProvider) (*
}
// Get current state from target database
- currentStateIR, err := util.GetIRFromDatabase(config.Host, config.Port, config.DB, config.User, config.Password, config.Schema, config.ApplicationName, ignoreConfig)
+ currentStateIR, err := util.GetIRFromDatabase(config.Host, config.Port, config.DB, config.User, config.Password, config.SSLMode, config.Schema, config.ApplicationName, ignoreConfig)
if err != nil {
return nil, fmt.Errorf("failed to get current state from database: %w", err)
}
@@ -279,7 +310,16 @@ func GeneratePlan(config *PlanConfig, provider postgres.DesiredStateProvider) (*
schemaToInspect = config.Schema
}
- desiredStateIR, err := util.GetIRFromDatabase(providerHost, providerPort, providerDB, providerUsername, providerPassword, schemaToInspect, config.ApplicationName, ignoreConfig)
+ // For embedded postgres, always use "disable" since it starts without SSL configured.
+ // For external plan databases, use the configured PlanDBSSLMode (defaulting to "prefer").
+ providerSSLMode := "disable"
+ if config.PlanDBHost != "" {
+ providerSSLMode = config.PlanDBSSLMode
+ if providerSSLMode == "" {
+ providerSSLMode = "prefer"
+ }
+ }
+ desiredStateIR, err := util.GetIRFromDatabase(providerHost, providerPort, providerDB, providerUsername, providerPassword, providerSSLMode, schemaToInspect, config.ApplicationName, ignoreConfig)
if err != nil {
return nil, fmt.Errorf("failed to get desired state: %w", err)
}
@@ -692,4 +732,6 @@ func ResetFlags() {
planDBDatabase = ""
planDBUser = ""
planDBPassword = ""
+ planSSLMode = "prefer"
+ planDBSSLMode = "prefer"
}
diff --git a/cmd/util/connection.go b/cmd/util/connection.go
index 6ff42f9c..9eba0422 100644
--- a/cmd/util/connection.go
+++ b/cmd/util/connection.go
@@ -84,8 +84,22 @@ func buildDSN(config *ConnectionConfig) string {
return strings.Join(parts, " ")
}
+// ValidateSSLMode validates that the given sslmode is a valid PostgreSQL SSL mode.
+func ValidateSSLMode(mode string) error {
+ switch mode {
+ case "disable", "allow", "prefer", "require", "verify-ca", "verify-full":
+ return nil
+ default:
+ return fmt.Errorf("invalid sslmode %q: must be one of disable, allow, prefer, require, verify-ca, verify-full", mode)
+ }
+}
+
// GetIRFromDatabase gets the IR from a database with ignore configuration
-func GetIRFromDatabase(host string, port int, db, user, password, schemaName, applicationName string, ignoreConfig *ir.IgnoreConfig) (*ir.IR, error) {
+func GetIRFromDatabase(host string, port int, db, user, password, sslmode, schemaName, applicationName string, ignoreConfig *ir.IgnoreConfig) (*ir.IR, error) {
+ if sslmode == "" {
+ sslmode = "prefer"
+ }
+
// Build database connection
config := &ConnectionConfig{
Host: host,
@@ -93,7 +107,7 @@ func GetIRFromDatabase(host string, port int, db, user, password, schemaName, ap
Database: db,
User: user,
Password: password,
- SSLMode: "prefer",
+ SSLMode: sslmode,
ApplicationName: applicationName,
}
diff --git a/cmd/util/connection_test.go b/cmd/util/connection_test.go
new file mode 100644
index 00000000..ea26dffe
--- /dev/null
+++ b/cmd/util/connection_test.go
@@ -0,0 +1,21 @@
+package util
+
+import "testing"
+
+func TestValidateSSLMode(t *testing.T) {
+ // Valid modes
+ validModes := []string{"disable", "allow", "prefer", "require", "verify-ca", "verify-full"}
+ for _, mode := range validModes {
+ if err := ValidateSSLMode(mode); err != nil {
+ t.Errorf("ValidateSSLMode(%q) returned error: %v", mode, err)
+ }
+ }
+
+ // Invalid modes
+ invalidModes := []string{"", "reqiure", "DISABLE", "ssl", "none"}
+ for _, mode := range invalidModes {
+ if err := ValidateSSLMode(mode); err == nil {
+ t.Errorf("ValidateSSLMode(%q) should have returned error", mode)
+ }
+ }
+}
diff --git a/cmd/util/env.go b/cmd/util/env.go
index 32c96c9d..d3f74d25 100644
--- a/cmd/util/env.go
+++ b/cmd/util/env.go
@@ -79,7 +79,7 @@ func PreRunEWithEnvVarsAndConnectionAndApp(dbPtr, userPtr *string, hostPtr *stri
// ApplyPlanDBEnvVars applies environment variables to plan database connection parameters
// This is used in the plan command to populate plan-* flags from PGSCHEMA_PLAN_* environment variables
-func ApplyPlanDBEnvVars(cmd *cobra.Command, hostPtr, dbPtr, userPtr, passwordPtr *string, portPtr *int) {
+func ApplyPlanDBEnvVars(cmd *cobra.Command, hostPtr, dbPtr, userPtr, passwordPtr *string, portPtr *int, sslmodePtr *string) {
// Apply environment variables if flags were not explicitly set
if GetEnvWithDefault("PGSCHEMA_PLAN_HOST", "") != "" && !cmd.Flags().Changed("plan-host") {
*hostPtr = GetEnvWithDefault("PGSCHEMA_PLAN_HOST", "")
@@ -96,6 +96,9 @@ func ApplyPlanDBEnvVars(cmd *cobra.Command, hostPtr, dbPtr, userPtr, passwordPtr
if GetEnvWithDefault("PGSCHEMA_PLAN_PASSWORD", "") != "" && !cmd.Flags().Changed("plan-password") {
*passwordPtr = GetEnvWithDefault("PGSCHEMA_PLAN_PASSWORD", "")
}
+ if GetEnvWithDefault("PGSCHEMA_PLAN_SSLMODE", "") != "" && !cmd.Flags().Changed("plan-sslmode") {
+ *sslmodePtr = GetEnvWithDefault("PGSCHEMA_PLAN_SSLMODE", "")
+ }
}
// ValidatePlanDBFlags validates plan database flags when plan-host is provided
diff --git a/docs/cli/apply.mdx b/docs/cli/apply.mdx
index 21ff17d1..05e682be 100644
--- a/docs/cli/apply.mdx
+++ b/docs/cli/apply.mdx
@@ -114,6 +114,14 @@ pgschema apply --host localhost --db myapp --user postgres --password mypassword
See [dotenv (.env)](/cli/dotenv) for detailed configuration options.
+
+ SSL mode for database connection (env: PGSSLMODE)
+
+ Valid values: `disable`, `allow`, `prefer`, `require`, `verify-ca`, `verify-full`
+
+ For `verify-ca` and `verify-full` modes, you can configure certificate paths using standard PostgreSQL environment variables (`PGSSLROOTCERT`, `PGSSLCERT`, `PGSSLKEY`).
+
+
Schema name to apply changes to
diff --git a/docs/cli/dotenv.mdx b/docs/cli/dotenv.mdx
index 63a688a9..4e431007 100644
--- a/docs/cli/dotenv.mdx
+++ b/docs/cli/dotenv.mdx
@@ -37,6 +37,10 @@ pgschema supports all standard PostgreSQL environment variables:
Database password
+
+ SSL mode for database connection. Valid values: `disable`, `allow`, `prefer`, `require`, `verify-ca`, `verify-full`
+
+
Application name visible in `pg_stat_activity`
@@ -53,6 +57,9 @@ PGDATABASE=myapp
PGUSER=postgres
PGPASSWORD=secretpassword
+# Optional: SSL mode (disable, allow, prefer, require, verify-ca, verify-full)
+PGSSLMODE=prefer
+
# Optional: Custom application name
PGAPPNAME=pgschema
```
diff --git a/docs/cli/dump.mdx b/docs/cli/dump.mdx
index ca3c68f4..4684d41a 100644
--- a/docs/cli/dump.mdx
+++ b/docs/cli/dump.mdx
@@ -118,6 +118,14 @@ pgschema apply --host staging-host --db myapp --user postgres --file current.sql
See [dotenv (.env)](/cli/dotenv) for detailed configuration options.
+
+ SSL mode for database connection (env: PGSSLMODE)
+
+ Valid values: `disable`, `allow`, `prefer`, `require`, `verify-ca`, `verify-full`
+
+ For `verify-ca` and `verify-full` modes, you can configure certificate paths using standard PostgreSQL environment variables (`PGSSLROOTCERT`, `PGSSLCERT`, `PGSSLKEY`).
+
+
Schema name to dump
diff --git a/docs/cli/plan-db.mdx b/docs/cli/plan-db.mdx
index 7858d131..2153d8fa 100644
--- a/docs/cli/plan-db.mdx
+++ b/docs/cli/plan-db.mdx
@@ -190,6 +190,12 @@ CREATE TABLE orders (
Environment variable: `PGSCHEMA_PLAN_PASSWORD`
+
+ Plan database SSL mode. Valid values: `disable`, `allow`, `prefer`, `require`, `verify-ca`, `verify-full`
+
+ Environment variable: `PGSCHEMA_PLAN_SSLMODE`
+
+
### Using Environment Variables
@@ -207,6 +213,7 @@ PGSCHEMA_PLAN_PORT=5432
PGSCHEMA_PLAN_DB=pgschema_plan
PGSCHEMA_PLAN_USER=postgres
PGSCHEMA_PLAN_PASSWORD=planpassword
+PGSCHEMA_PLAN_SSLMODE=prefer
# Run plan with external database
pgschema plan --file schema.sql
@@ -226,6 +233,7 @@ export PGSCHEMA_PLAN_HOST=localhost
export PGSCHEMA_PLAN_DB=pgschema_plan
export PGSCHEMA_PLAN_USER=postgres
export PGSCHEMA_PLAN_PASSWORD=planpassword
+export PGSCHEMA_PLAN_SSLMODE=prefer
# Run plan
pgschema plan --file schema.sql
diff --git a/docs/cli/plan.mdx b/docs/cli/plan.mdx
index 5db20f15..1c331519 100644
--- a/docs/cli/plan.mdx
+++ b/docs/cli/plan.mdx
@@ -116,6 +116,14 @@ pgschema plan --host localhost --db myapp --user postgres --password mypassword
See [dotenv (.env)](/cli/dotenv) for detailed configuration options.
+
+ SSL mode for database connection (env: PGSSLMODE)
+
+ Valid values: `disable`, `allow`, `prefer`, `require`, `verify-ca`, `verify-full`
+
+ For `verify-ca` and `verify-full` modes, you can configure certificate paths using standard PostgreSQL environment variables (`PGSSLROOTCERT`, `PGSSLCERT`, `PGSSLKEY`).
+
+
Schema name to target for comparison
diff --git a/docs/coding-agent.mdx b/docs/coding-agent.mdx
index 39e5e253..247e95f5 100644
--- a/docs/coding-agent.mdx
+++ b/docs/coding-agent.mdx
@@ -83,6 +83,7 @@ Use these connection parameters for all commands:
- `--db` - Database name (required)
- `--user` - Database user (required)
- `--password` - Database password (or use PGPASSWORD environment variable)
+- `--sslmode` - SSL mode (default: prefer, or use PGSSLMODE environment variable)
- `--schema` - Target schema (default: public)
### Multi-File Schema Management
diff --git a/internal/postgres/embedded.go b/internal/postgres/embedded.go
index 551ac012..8a26f9b9 100644
--- a/internal/postgres/embedded.go
+++ b/internal/postgres/embedded.go
@@ -45,15 +45,19 @@ type EmbeddedPostgresConfig struct {
// DetectPostgresVersionFromDB connects to a database and detects its version
// This is a convenience function that opens a connection, detects the version, and closes it
-func DetectPostgresVersionFromDB(host string, port int, database, user, password string) (PostgresVersion, error) {
+func DetectPostgresVersionFromDB(host string, port int, database, user, password, sslmode string) (PostgresVersion, error) {
// Build connection config
+ finalSSLMode := sslmode
+ if finalSSLMode == "" {
+ finalSSLMode = "prefer"
+ }
config := &util.ConnectionConfig{
Host: host,
Port: port,
Database: database,
User: user,
Password: password,
- SSLMode: "prefer",
+ SSLMode: finalSSLMode,
}
// Connect to database
diff --git a/internal/postgres/external.go b/internal/postgres/external.go
index 52268bc1..0837a3f7 100644
--- a/internal/postgres/external.go
+++ b/internal/postgres/external.go
@@ -30,9 +30,18 @@ type ExternalDatabaseConfig struct {
Database string
Username string
Password string
+ SSLMode string
TargetMajorVersion int // Expected major version to match
}
+// sslModeOrDefault returns the configured SSL mode, defaulting to "prefer" if empty
+func (c *ExternalDatabaseConfig) sslModeOrDefault() string {
+ if c.SSLMode == "" {
+ return "prefer"
+ }
+ return c.SSLMode
+}
+
// NewExternalDatabase creates a new external database connection for desired state validation.
// It validates the connection, checks version compatibility, and generates a temporary schema name.
func NewExternalDatabase(config *ExternalDatabaseConfig) (*ExternalDatabase, error) {
@@ -43,7 +52,7 @@ func NewExternalDatabase(config *ExternalDatabaseConfig) (*ExternalDatabase, err
Database: config.Database,
User: config.Username,
Password: config.Password,
- SSLMode: "prefer",
+ SSLMode: config.sslModeOrDefault(),
}
// Connect to database