JuliusBrussee/caveman · error

postgres: production DATABASE_URL must be a Postgres URL

Error message

postgres: production DATABASE_URL must be a Postgres URL

What it means

ParsePoolConfig refuses to build a pgxpool.Config from a DATABASE_URL that is not a parseable postgres:// or postgresql:// URL with a hostname when the process runs in production. The library enforces this because the production TLS posture (verify-full, pinned CA) is only expressible through a URL it can inspect; anything else would silently bypass the security checks.

Solutions

  1. Rewrite DATABASE_URL as postgres://user:pass@host:5432/dbname with a real hostname
  2. Confirm the deployment actually intends production (RUNTIME_ENV/GO_ENV) — if it is local dev, unset the production flag
  3. URL-encode the connection string (special chars in password break url.Parse)
  4. Add the sslmode=verify-full query parameter too, since this check is followed by the verify-full requirement

Example fix

// before
DATABASE_URL="host=db.internal user=app dbname=prod sslmode=verify-full"
// after
DATABASE_URL="postgres://app:secret@db.internal:5432/prod?sslmode=verify-full"
Defensive patterns

Strategy: validation

Validate before calling

u, err := url.Parse(os.Getenv("DATABASE_URL"))
if err != nil || (u.Scheme != "postgres" && u.Scheme != "postgresql") || u.Hostname() == "" {
    return fmt.Errorf("DATABASE_URL must be postgres://user:pass@host:5432/db")
}

Type guard

func isPostgresURL(raw string) bool {
    u, err := url.Parse(raw)
    return err == nil && (u.Scheme == "postgres" || u.Scheme == "postgresql") && u.Hostname() != ""
}

Try / catch

cfg, err := postgresconfig.ParsePoolConfig(databaseURL)
if err != nil {
    if strings.Contains(err.Error(), "must be a Postgres URL") {
        return nil, fmt.Errorf("DATABASE_URL malformed for production: %w", err)
    }
    return nil, err
}

Prevention

When it happens

Trigger: Calling NewPool/ParsePoolConfig in production (runtimeenv.IsProduction() true) with a URL whose scheme is not postgres/postgresql (e.g. a bare DSN string like 'host=db user=app', or http://), a URL that fails url.Parse, or a URL without a hostname.

Common situations: Deploying with a keyword/value DSN copied from local dev, leaving DATABASE_URL set to a docker-compose placeholder, or a staging DATABASE_URL promoted to production unchanged.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/2e6c089b10abf178. Report an issue: GitHub.

Appendix: source

Thrown at shared/platform/postgresconfig/postgresconfig.go:34

	"github.com/JuliusBrussee/caveman/shared/platform/runtimeenv"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
)

const (
	caEnvironment     = "CAVE_POSTGRES_CA_CERT"
	caFileEnvironment = "CAVE_POSTGRES_CA_CERT_FILE"
)

// ParsePoolConfig validates the connection string and applies the managed
// database CA to every TLS path. Production rejects sslmode=require because it
// encrypts without authenticating the server; verify-full is mandatory.
func ParsePoolConfig(databaseURL string) (*pgxpool.Config, error) {
	production := runtimeenv.IsProduction()
	if production {
		parsed, err := url.Parse(databaseURL)
		if err != nil || (parsed.Scheme != "postgres" && parsed.Scheme != "postgresql") || parsed.Hostname() == "" {
			return nil, errors.New("postgres: production DATABASE_URL must be a Postgres URL")
		}
		if parsed.Query().Get("sslmode") != "verify-full" {
			return nil, errors.New("postgres: production DATABASE_URL requires sslmode=verify-full")
		}
	}

	config, err := pgxpool.ParseConfig(databaseURL)
	if err != nil {
		return nil, fmt.Errorf("postgres: parse DATABASE_URL: %w", err)
	}
	caPEM, err := caPEMFromEnvironment()
	if err != nil {
		return nil, err
	}
	if production && caPEM == "" {
		return nil, fmt.Errorf("postgres: %s or %s is required in production", caEnvironment, caFileEnvironment)
	}
	if caPEM == "" {

View on GitHub (pinned to 3ee70a1026)