118 lines
6.5 KiB
Markdown
118 lines
6.5 KiB
Markdown
# 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_<reference>`. 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-<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_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.
|