JuliusBrussee/caveman · critical

postgres: or is required in production

Error message

postgres: %s or %s is required in production

What it means

ParsePoolConfig enforces that in production a TLS CA is available via one of two environment variables (caEnvironment or caFileEnvironment, e.g. DB_CA_PEM or DB_CA_FILE). If production mode is set and neither is present, startup is refused so the pool never connects with an unverifiable server certificate. The message interpolates the two env var names the operator must set.

Solutions

  1. Set the CA PEM env var named in the error (e.g. DB_CA_PEM) with the full certificate bundle.
  2. Or set the file-path variable (e.g. DB_CA_FILE) to a mounted CA file readable by the process.
  3. In k8s, mount the CA as a Secret/ConfigMap volume and point the env var at it.
  4. Verify sslmode=verify-full is also set in DATABASE_URL (production requires it alongside the CA).
  5. Confirm the file is readable by the container user (permissions) and contains valid PEM.

Example fix

// before
docker run production-image  # no CA configured
// after
docker run -e DB_CA_FILE=/certs/rds-combined-ca-bundle.pem -v $PWD/certs:/certs:ro production-image
Defensive patterns

Strategy: validation

Validate before calling

func checkProdTLSConfig() error {
    if os.Getenv("DB_ENV") != "production" {
        return nil
    }
    if os.Getenv("DB_CA_PEM") == "" && os.Getenv("DB_CA_FILE") == "" {
        return errors.New("production requires DB_CA_PEM or DB_CA_FILE")
    }
    if p := os.Getenv("DB_CA_FILE"); p != "" {
        if _, err := os.ReadFile(p); err != nil {
            return fmt.Errorf("DB_CA_FILE unreadable: %w", err)
        }
    }
    return nil
}

Try / catch

pool, err := postgresconfig.NewPool(ctx, dsn)
if err != nil {
    if strings.Contains(err.Error(), "is required in production") {
        return fmt.Errorf("deployment missing TLS CA: set %s or %s: %w", "DB_CA_PEM", "DB_CA_FILE", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling ParsePoolConfig or NewPool with production=true (production build/env flag) while caPEMFromEnvironment() returns empty — i.e., neither the inline CA PEM variable nor the CA file path variable is set to a readable value.

Common situations: Deploying the production image without injecting the RDS/Cloud SQL CA bundle; k8s Secret mounted at the wrong path so the CA_FILE var points nowhere; local dev compose files promoted to prod unchanged; variable renamed in a newer release while old manifests persist.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

		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 == "" {
		return config, nil
	}
	roots := x509.NewCertPool()
	if !roots.AppendCertsFromPEM([]byte(caPEM)) {
		return nil, fmt.Errorf("postgres: %s contains no valid certificate", caEnvironment)
	}
	if config.ConnConfig.TLSConfig == nil {
		return nil, errors.New("postgres: CA certificate configured while TLS is disabled")
	}
	config.ConnConfig.TLSConfig.RootCAs = roots
	config.ConnConfig.TLSConfig.InsecureSkipVerify = false
	config.ConnConfig.TLSConfig.ServerName = config.ConnConfig.Host
	for _, fallback := range config.ConnConfig.Fallbacks {
		if fallback.TLSConfig == nil {
			if production {
				return nil, errors.New("postgres: production connection includes a plaintext fallback")

View on GitHub (pinned to 3ee70a1026)