103 lines
5.0 KiB
Markdown
103 lines
5.0 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.
|
|
- A TLS certificate and private key readable by PawSQL. 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`.
|
|
|
|
## Build, configure, and run
|
|
|
|
Build a native binary:
|
|
|
|
```sh
|
|
go build -o pawsql ./cmd/pawsql
|
|
```
|
|
|
|
Create a `Barkfile` and validate it before starting:
|
|
|
|
```sh
|
|
./pawsql validate --config Barkfile
|
|
./pawsql --config Barkfile
|
|
```
|
|
|
|
`--config` defaults to `Barkfile`. The listener, TLS material, and at least one database route are required.
|
|
|
|
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 \
|
|
pawsql
|
|
```
|
|
|
|
The supplied image includes the Docker CLI so managed `postgres` routes can create, start, and stop their 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.
|
|
|
|
## 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_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`](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
|
|
|
|
PawSQL requires 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.
|
|
|
|
## 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.
|