Configuration (config.yaml)
pgbx keeps everything about your connections in one file, config.yaml. It holds references, never
secrets: a password is written as $PGPASSWORD and read from your environment (or your secrets source) when a
command runs. pgbx contains no connection code of its own: a connection is a plain connection string or an
adapter, an external program that hands pgbx a connection string
(ADR 0003).
Where it lives
Section titled “Where it lives”| path | |
|---|---|
| Linux, macOS | ~/.config/pgbx/config.yaml ($XDG_CONFIG_HOME/pgbx/config.yaml when set) |
| Windows | %APPDATA%\pgbx\config.yaml |
| any | $PGBX_CONFIG_DIR/config.yaml when PGBX_CONFIG_DIR is set |
pgbx writes it with mode 0600 (its directory 0700). Treat it like ~/.ssh/config: it names commands pgbx runs.
pgbx profile add | edit | remove | use rewrite it for you; you can also edit it by hand (comments are not
kept when pgbx rewrites it). pgbx memories import never brings profiles or adapters with it.
A full example
Section titled “A full example”default: prod # the profile used when none is named (pgbx profile use NAME)secrets: env # env (default) | a .env file | a handler command, see belowadapters: # name -> the command that runs it (no shell; a string or a list) ssh: node ~/.config/pgbx/adapters/ssh/ssh-adapter.js aws: node ~/.config/pgbx/adapters/aws/aws-adapter.js corp: [/usr/local/bin/corp-pg, --region, eu]profiles: dev: # a plain connection string url: postgres://$PGUSER:$PGPASSWORD@localhost:5432/shop prod: # an adapter + whatever settings that adapter reads adapter: ssh pg_host: localhost user: app password: $PGPASSWORD dbname: shop s3-endpoint: https://s3.eu-central-1.amazonaws.com # pgbx's own keys sit next to them s3-bucket: my-backups server-name: db1 credentials-file: ~/.config/pgbx/prod.credentials prod-eu: adapter: aws target: i-0abc123def4567890 db_host: shop.cluster-xyz.eu-west-1.rds.amazonaws.com region: eu-west-1 aws_profile: acme-prod user: app password: ${SHOP_DB_PASSWORD} ready_timeout: 60sAdapters
Section titled “Adapters”adapters: maps a name to a command. A string is split into words like a shell would (quotes work, no
variables, globs or pipes) and run without a shell; a list is used as is. A leading ~/ is expanded. The
command runs in the config directory, so a relative path is relative to it.
The installer copies four examples to <config dir>/adapters/ (PGBX_ADAPTERS_DIR overrides): ssh, aws,
gcp and azure. They are Node scripts (Node 18+); pgbx itself never needs Node. pgbx profile add NAME --adapter ssh ... registers the example by itself when it is there; --adapter-command CMD defines any other
adapter, and the reply says exactly what it will run. See
Connect through SSH, AWS, GCP, Azure or your own adapter.
Profiles
Section titled “Profiles”A profile has either url: or adapter:, never both.
url:a connection string, URL (postgres://user:pass@host:port/db?...) or key=value (host=... user=... dbname=...).adapter:an adapter name, plus any settings: everything that is not one of pgbx’s own keys goes to the adapter, with$VARs expanded.
pgbx’s own keys:
| key | meaning |
|---|---|
admin-db |
the admin database (default: pgbx.admin_db, else postgres) |
s3-endpoint, s3-bucket, s3-region, server-name, credentials-file |
for --from-s3; S3 keys stay in the credentials file |
ready_timeout |
how long the adapter may take to answer (30s default; 60s, 2m) |
sslmode, sslrootcert |
TLS to Postgres (see TLS); an adapter gets them too, in its settings |
Without --db, a command uses the database named in the connection string, else postgres. A connection
string without a user uses PGUSER, else postgres; without a password, PGPASSWORD (or ~/.pgpass for
pg_restore).
Which connection a command uses
Section titled “Which connection a command uses”--url URL(one run, no profile)--profile NAME--host/--portflags (direct, no profile)PGBX_URLPGBX_PROFILE- the default profile
PGHOST/PGPORT/PGUSERand the built-in defaults (/var/run/postgresql,5432,postgres)
Every CLI connection to Postgres (query, status, doctor, jobs, serve, setup client,
db-restore --from-s3, …) uses TLS the way psql does, with libpq’s sslmode:
sslmode |
TLS | certificate check |
|---|---|---|
disable |
never | |
allow, prefer (the default) |
when the server offers it, else plain | none |
require |
always | none (encrypted, but not authenticated) |
verify-ca |
always | the certificate chains to a trusted root; the host name is not checked |
verify-full |
always | trusted root, and the certificate names the host you connect to |
Trusted roots for verify-ca / verify-full: the PEM file in sslrootcert; else ~/.postgresql/root.crt if
it exists (as libpq); else the Mozilla roots built into pgbx plus your operating system’s store
(sslrootcert=system asks for these explicitly and, alone, means verify-full, as in libpq 16).
Where the settings come from, first match wins, for each of the two:
- the connection string:
postgres://...?sslmode=verify-full&sslrootcert=/etc/pgbx/ca.pem, orsslmode=... sslrootcert=...in key=value form (an adapter’s URL counts here); - the profile’s
sslmode:/sslrootcert:keys ($VARs and~/allowed); PGSSLMODE/PGSSLROOTCERT;- the defaults above.
A Unix socket never uses TLS (libpq ignores sslmode there too). pg_restore (for db-restore --from-s3) gets
the same settings as PGSSLMODE / PGSSLROOTCERT; when pgbx used its default roots that is
PGSSLROOTCERT=system, which needs pg_restore 16 or newer (older ones: set sslrootcert to a file). pgbx
has no client certificates (sslcert / sslkey). TLS is rustls: no OpenSSL, still one static binary.
profiles: prod: sslrootcert: ~/.config/pgbx/rds-global-bundle.pem$VAR references
Section titled “$VAR references”Anywhere in a profile, in --url or in PGBX_URL:
$NAMEand${NAME}are replaced when a command runs;$$is a literal$. A$not followed by a name stays as it is.- A missing (or empty) variable is an error that names it:
$PGPASSWORD is not set (looked in the environment). Its value is never printed. - Values that land in the
user:password@part of a URL are percent-encoded for you, so a password with@,:or/in it works. - Type them in single quotes so your shell leaves them for pgbx:
pgbx profile add dev --url 'postgres://app:$PGPASSWORD@db:5432/shop'. - pgbx refuses to store a literal password: in a
url, or in a setting whose name containspassword,passwd,secretortoken.
Secrets
Section titled “Secrets”Each $VAR comes from the process environment first, so PGPASSWORD=... pgbx ... always wins. Then from
the secrets: source:
secrets: |
|
|---|---|
env |
the default: the environment only |
.secrets/.env, /path/db.env |
a .env file (a path ending in .env; relative to the config dir): KEY=value lines, export KEY=value, # comments, optional single or double quotes |
node secret-handler.js |
any other value is a command: pgbx runs it once per variable as <command> NAME; stdout is the value (one trailing newline stripped). A non-zero exit, empty output or no answer within 10 s is an error that names the variable, never a value |
pgbx ships no secret-manager integrations; a handler is a few lines. For sec, Vault, 1Password, AWS Secrets
Manager and so on:
#!/bin/sh# secrets: sh ~/.config/pgbx/secret.shcase "$1" in PGPASSWORD) exec aws secretsmanager get-secret-value --secret-id shop/db --query SecretString --output text ;; *) echo "unknown secret $1" >&2; exit 1 ;;esac// secrets: node secret-handler.jsconst { execFileSync } = require('node:child_process');const name = process.argv[2];const ids = { PGPASSWORD: 'op://prod/shop-db/password' };if (!ids[name]) { console.error(`unknown secret ${name}`); process.exit(1); }process.stdout.write(execFileSync('op', ['read', ids[name]]));Redaction
Section titled “Redaction”Values are used in memory only. Every output, --json or text, and every pgbx serve API answer, masks
passwords in connection strings (postgres://app:***@db:5432/shop, password=***) and replaces any value that
came from the secrets source, or from a secret-looking environment variable (PGPASSWORD, *_TOKEN, …), with
***. pgbx profile show and list mask the same way. Child tools (pg_restore) get the password through
their environment, never their arguments.
The adapter protocol (v1)
Section titled “The adapter protocol (v1)”- pgbx starts the adapter’s command in its own process group (Unix) or Job Object (Windows), with stdin and stdout as pipes and stderr captured.
- It writes one line:
{"action":"start","name":"prod","config":{...}}, the profile’s adapter settings with$VARs expanded. - The adapter prints exactly one line on stdout within
ready_timeout(30 s):{"url":"postgres://...","state":"ready","name":"prod"}. Any otherstateis a failure and pgbx shows it with the adapter’s stderr (masked).namemust match the profile. Anything else on stdout first (a log line) is a protocol error: logs go to stderr. - pgbx uses the URL and does not talk to the adapter again until it is done.
- Then it writes
{"action":"stop"}and closes stdin (a closed stdin also means stop, so the adapter exits if pgbx dies), waits up to 5 s, and kills what is left of the process group (SIGTERM, then SIGKILL; on Windows the Job Object is terminated). Ctrl-C does the same before pgbx exits.
A one-off command starts its adapter on its first connection and stops it when it finishes. pgbx serve keeps
one adapter per connection for its whole run.
The URL an adapter returns may carry sslmode / sslrootcert; they win over the profile’s keys (see TLS).
Migrating from profiles.json (pgbx 0.5)
Section titled “Migrating from profiles.json (pgbx 0.5)”The first time pgbx 0.6 runs and finds profiles.json but no config.yaml, it moves the profiles over, tells
you once, and keeps the old file as profiles.json.migrated:
- a direct profile (
host,port,user) becomesurl: postgres://user@host:port/(a socket directory is percent-encoded in the host); - an SSH profile (
ssh,ssh-port,ssh-jump,host,port,user) becomesadapter: sshwithtarget,ssh_port,jump,pg_host,pg_port,user, andadapters: ssh:points at the example in<config dir>/adapters/ssh/(copyadapters/from the release or the repo there if it is missing); tunnel-idleis dropped: there are no background tunnels any more.
The S3 keys and admin-db are carried over as they were.