JuliusBrussee/caveman · error

postgres

Error message

postgres: %s: %w

What it means

Wrapped error from caPEMFromEnvironment: os.Stat failed on the CA file path taken from the CA-file environment variable (missing file, permission, broken symlink). The environment variable name and the stat error are both carried via the format so the cause is inspectable.

Solutions

  1. Verify the path exists and is readable from the process: `ls -l $POSTGRES_CA_FILE` inside the same container/host.
  2. Mount the CA certificate file into the container and fix the env var to the absolute mounted path.
  3. If the CA is provided inline, remove the file env var and use the direct PEM variable instead.
  4. Check that the user running the process has read permission on the file and its parent directories.

Example fix

// before
export POSTGRES_CA_FILE=./certs/ca.crt // relative path, wrong cwd
// after
export POSTGRES_CA_FILE=/etc/app/certs/ca.crt // absolute path, verified mounted
Defensive patterns

Strategy: validation

Validate before calling

func caFileReadable(path string) error {
	if path == "" { return nil }
	info, err := os.Stat(path)
	if err != nil { return err }
	if !info.Mode().IsRegular() { return fmt.Errorf("not a regular file: %s", path) }
	f, err := os.Open(path); if err != nil { return err }
	f.Close()
	return nil
}

Try / catch

if _, err := postgresconfig.ParsePoolConfig(dbURL); err != nil {
	if errors.Is(err, os.ErrNotExist) {
		log.Fatalf("CA file missing: %v", err)
	}
	return err
}

Prevention

When it happens

Trigger: ParsePoolConfig/NewPool called with the CA-file env var set to a path that os.Stat cannot resolve: nonexistent file, typo in path, unreadable parent directory, or dangling symlink.

Common situations: CA file not mounted into the container; wrong mount path in Docker/Kubernetes; relative path used but working directory differs; file deleted after image build.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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

Appendix: source

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

		fallback.TLSConfig.RootCAs = roots
		fallback.TLSConfig.InsecureSkipVerify = false
		fallback.TLSConfig.ServerName = fallback.Host
	}
	return config, nil
}

func caPEMFromEnvironment() (string, error) {
	filePath := strings.TrimSpace(os.Getenv(caFileEnvironment))
	direct := strings.TrimSpace(os.Getenv(caEnvironment))
	if filePath != "" && direct != "" {
		return "", fmt.Errorf("postgres: set only %s or %s, not both", caFileEnvironment, caEnvironment)
	}
	if filePath == "" {
		return direct, nil
	}
	info, err := os.Stat(filePath)
	if err != nil {
		return "", fmt.Errorf("postgres: %s: %w", caFileEnvironment, err)
	}
	if !info.Mode().IsRegular() {
		return "", fmt.Errorf("postgres: %s must point to a regular file", caFileEnvironment)
	}
	contents, err := os.ReadFile(filePath)
	if err != nil {
		return "", fmt.Errorf("postgres: read %s: %w", caFileEnvironment, err)
	}
	return strings.TrimSpace(string(contents)), nil
}

// NewPool constructs a pool from the hardened configuration.
func NewPool(ctx context.Context, databaseURL string) (*pgxpool.Pool, error) {
	config, err := ParsePoolConfig(databaseURL)
	if err != nil {
		return nil, err
	}
	return pgxpool.NewWithConfig(ctx, config)

View on GitHub (pinned to 3ee70a1026)