5.9 KiB
PawSQL
PawSQL is a TLS-terminating PostgreSQL router. It accepts PostgreSQL clients on one address, chooses a configured route from the TLS Server Name Indication (SNI) or database name, and proxies the PostgreSQL stream to an external or PawSQL-managed PostgreSQL server.
Prerequisites
- Go 1.24 or later to build and run PawSQL natively.
- Docker Engine and a usable
dockerCLI to build the PawSQL image. PawSQL also needs them in its own execution environment when it manages PostgreSQL containers. - Optional: a TLS certificate and private key readable by PawSQL. Omit the
tlsblock to serve plaintext PostgreSQL; with TLS, the certificate must cover every hostname clients use for SNI routing. - Docker Engine access for each
postgresroute. Managed database images are limited topostgres:16,postgres:17, andpostgres:18.
Build, configure, and run
Build the independently owned Svelte plugin, then the native binary:
cd ui
bun install
bun run build
cd ..
go build -o pawsql ./cmd/pawsql
Create a Barkfile and validate it before starting:
./pawsql validate --config Barkfile
./pawsql --config Barkfile --admin-listen :9090
--config defaults to Barkfile. The PostgreSQL listener, TLS material, and at least one database route are required. --admin-listen defaults to :9090 and serves PawSQL's embedded Barkstack plugin at /barkstack/ui/; pass an empty value to disable it. The admin port is intended for Barkstack over localhost or a private overlay network, not direct publication.
To build and run the PawSQL container image for routes reachable from that container:
docker build -t pawsql .
docker run --rm --publish 5432:5432 \
--volume "$PWD/Barkfile:/etc/pawsql/Barkfile:ro" \
--volume "$PWD/tls:/etc/pawsql/tls:ro" \
--volume /var/run/docker.sock:/var/run/docker.sock \
pawsql
The supplied image includes the compiled PawSQL Svelte plugin and Docker CLI; production does not run Node or Bun. The Docker CLI lets managed postgres routes create, start, and stop containers through the mounted Docker socket. The socket grants PawSQL root-equivalent control of the Docker host; mount it only for trusted Barkfiles and trusted administrators. Publish PostgreSQL port 5432 as needed, but leave admin port 9090 unpublished and let Barkstack proxy the UI.
Barkfile
A Barkfile has one pawsql block. Each database has a unique name and exactly one route type: an upstream external PostgreSQL address or a postgres managed database.
pawsql {
listen :5432
tls {
cert /etc/pawsql/tls/fullchain.pem
key /etc/pawsql/tls/privkey.pem
}
database reporting {
hostname reports.db.example.com
upstream reporting.internal:5432
}
database application {
hostname app.db.example.com
postgres {
image postgres:17
volume pawsql-application-data
password_env APPLICATION_POSTGRES_PASSWORD
idle_timeout 10m
traffic_idle_timeout 1h
}
}
}
listen is PawSQL's TCP address. cert and key identify the client-facing TLS certificate and key. hostname is optional; it is used only for SNI routing. upstream is the address of an existing PostgreSQL server.
For a managed postgres route, image, volume, and password_env are required. On first use, PawSQL reads the named environment variable to create the database container and configures the database and PostgreSQL user with the route's database name. The named Docker volume preserves its data. Set the password environment variable in PawSQL's environment, not in the Barkfile.
See examples/Barkfile and its accompanying examples/docker-compose.yml for a two-route external PostgreSQL example with locally generated development certificates:
cd examples
docker compose up --build
Routing and TLS
When the Barkfile configures tls, PawSQL handles PostgreSQL's SSL negotiation and terminates client TLS before proxying PostgreSQL bytes to the selected upstream:
- With SNI: PawSQL uses the TLS server name to select an exact configured
hostnamematch. Hostname matching is case-insensitive and ignores a trailing dot. An unknown SNI name is rejected; PawSQL does not fall back to a database-name route when SNI is present. - Without SNI: After TLS is established, PawSQL reads the PostgreSQL startup message and selects the route whose
databasename exactly matches the requested PostgreSQL database. This makes a route withouthostnameusable by non-SNI clients.
Use a certificate trusted by clients and containing the SNI hostname they present. Clients that do not send SNI must request the configured database route name.
Without a tls block, PawSQL serves plaintext PostgreSQL: clients connect without SSL negotiation, and routes are selected only by database name. Hostname routing is unavailable because it relies on TLS SNI.
Managed PostgreSQL lifecycle
Managed PostgreSQL is lazy: PawSQL creates or starts its pawsql-<database> container only when a client selects that route, waits for PostgreSQL to accept connections, then proxies the session. PawSQL stops managed containers but does not remove their data volumes.
Two optional Go-duration controls govern stopping a managed container; 0 disables either control:
idle_timeoutstarts only after the last proxied client session closes. When that countdown expires, PawSQL stops the managed container.traffic_idle_timeoutstarts for an open session and resets whenever PawSQL proxies bytes in either direction. When it expires, PawSQL stops the managed container even though sessions remain open.
traffic_idle_timeout is intentionally aggressive: it terminates open but silent sessions. Do not enable it for workloads that keep idle connections, transactions, listeners, or connection-pool sessions alive unless that interruption is acceptable.