# 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 `docker` CLI 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 `tls` block to serve plaintext PostgreSQL; with TLS, the certificate must cover every hostname clients use for SNI routing. - Docker Engine access for each `postgres` route. Managed database images are limited to `postgres:16`, `postgres:17`, and `postgres:18`; each route also requires a mounted Docker secret file. ## Build, configure, and run Build the independently owned Svelte plugin, then the native binary: ```sh cd ui bun install bun run build cd .. go build -o pawsql ./cmd/pawsql ``` Create a `Barkfile` and validate it before starting: ```sh ./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: ```sh 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 \ --volume "$PWD/secrets:/run/secrets:ro" \ 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. ```text 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_secret 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_secret` are required. `password_secret` is a short lowercase reference; PawSQL reads it from `/run/secrets/barkstack_`. The example therefore reads `/run/secrets/barkstack_application_postgres_password`. On first use, PawSQL uses that value to configure the database and PostgreSQL user with the route's database name. The named Docker volume preserves its data. Create the Docker Swarm secret before running `barkstack init`; Barkstack verifies that every referenced secret exists and mounts it into the PawSQL service: ```sh docker secret create barkstack_application_postgres_password /secure/path/application-postgres-password ``` For a standalone PawSQL container, mount a directory containing the equivalently named file at `/run/secrets`, as shown above. Never put the password value in the Barkfile. See [`examples/Barkfile`](examples/Barkfile) and its accompanying [`examples/docker-compose.yml`](examples/docker-compose.yml) for a two-route external PostgreSQL example with locally generated development certificates: ```sh 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 `hostname` match. 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 `database` name exactly matches the requested PostgreSQL database. This makes a route without `hostname` usable 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-` 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_timeout` starts only after the last proxied client session closes. When that countdown expires, PawSQL stops the managed container. - `traffic_idle_timeout` starts 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.