Deployment
Deploy QueryPilot with Docker or Docker Compose and validate the deployment afterwards.
QueryPilot ships as Docker images. Every deployment path on this page produces the same two-process shape: the Router (binary querypilot) proxying client traffic, and Analytics (binary querypilot-analytics) replicating tables from your PostgreSQL into DuckDB via CDC. Reads route to DuckDB, writes stay on PostgreSQL, and applications connect to the Router instead of the database directly. PostgreSQL itself is external; none of these deployments run it for you.
MySQL support is in preview. MySQL proxying and MySQL CDC are functional, but PostgreSQL is the fully supported GA path. Where a deployment option below takes a MySQL source, treat it as preview.
Images and registries
| Image | Component | Binary |
|---|---|---|
querypilot | Router | querypilot |
querypilot-analytics | Analytics | querypilot-analytics |
querypilot-accelerator | Standalone Accelerator | querypilot-accelerator |
Pre-release builds are published to public.ecr.aws/readyset with tags of the form X.Y.Z-<sha> (for example 0.1.0-72807f4e). Production releases are published to Docker Hub with X.Y.Z tags plus a moving latest. Published bits are the same images that passed the release gates; a pre-release tag is a build you can pull ahead of the corresponding production release.
The images are runtime-only: a non-root sqp user (UID/GID 999), the binary, curl for healthchecks, and nothing else. Baked-in defaults:
- Router image: reads config from
QUERYPILOT_CONFIG=/etc/sqp/querypilot.toml, exposes 5433/9090/9091, healthcheckcurl -sf http://localhost:9091/api/config/status(10s interval, 5s start period). - Analytics image: reads config from
QUERYPILOT_ANALYTICS_CONFIG=/etc/sqp/querypilot-analytics.toml, setsQUERYPILOT_ANALYTICS_UNSAFE_BIND_ALL=true(containers must bind0.0.0.0; secure the published ports at the network layer, see Security), exposes 15432/8080/9090, healthcheckcurl -sf http://localhost:8080/api/health(15s interval, 600s start period). The long start period accommodates the initial CDC snapshot, which can take tens of minutes on large tables.
Port conventions
| Service | Port | Protocol | Notes |
|---|---|---|---|
| Router, PostgreSQL wire | 5433 | PostgreSQL | Applications connect here |
| Router, metrics | 9090 | HTTP (Prometheus) | sqp_* series |
| Router, admin API | 9091 | HTTP | Always metrics port + 1; not separately configurable |
| Analytics, PostgreSQL wire | 15432 | PostgreSQL | Internal; the Router's duckdb pool points here |
| Analytics, HTTP API | 8080 (18080 in Compose) | HTTP | /api/health, /api/status, /api/tables |
| Analytics, metrics | 9090 (19090 in Compose) | HTTP (Prometheus) | Replication lag, database size |
The Router's admin API port is derived, not configured: it binds on the metrics IP at the metrics port + 1. The shipped healthchecks and validation tooling assume the 9090/9091 pair. The Compose deployment uses host networking, so both containers share the host's port space; there the Analytics config shifts its HTTP API to 18080 and metrics to 19090 to avoid colliding with the Router's 9090.
Single node with docker run
For a Router-only deployment on one host, run the image with a config mounted at /etc/sqp/querypilot.toml:
docker run -d --name querypilot --network host \
-v "$(pwd)/querypilot.toml:/etc/sqp/querypilot.toml:ro" \
readysettech/querypilot:latestGetting Started walks through this path end to end, including a minimal config and first proxied query.
Docker Compose
The provided deploy/docker-compose.yml runs Analytics and the Router together against an external PostgreSQL reachable on localhost. It uses host networking, so all ports bind directly on the host and the config templates' 127.0.0.1 backend addresses work as-is. Treat the Compose file and its two config templates as starting points you adapt, not turnkey production artifacts.
Edit the config templates in deploy/configs/.
querypilot-analytics.toml (Analytics): set source_url to your PostgreSQL connection string and tables to the tables you want replicated. The template binds the PostgreSQL wire on 0.0.0.0:15432, the HTTP API on 0.0.0.0:18080, and metrics on 0.0.0.0:19090. Set timezone under [duckdb] to match your PostgreSQL server for correct TIMESTAMPTZ::date casts. See the Analytics configuration reference.
querypilot-router.toml (Router): one PostgreSQL listener on 0.0.0.0:5433 and two pools, duckdb (role replica, backend 127.0.0.1:15432, which is Analytics) and postgres (role primary, backend 127.0.0.1:5432). Set each pool's username/password/database to your credentials; these also serve as the client-auth credentials (see Security). [routing] enables read_write_split with read_pool = "duckdb" and write_pool = "postgres", and the template includes commented-out [[routing.pattern_rules]] examples for steering aggregations, GROUP BY, DISTINCT, and CTEs to DuckDB while keeping indexed point lookups on PostgreSQL. Pattern rules take priority over the read/write split; lower priority numbers win. See the Router configuration reference.
Start the stack.
cd deploy/
QUERYPILOT_IMAGE=readysettech/querypilot:latest \
QUERYPILOT_ANALYTICS_IMAGE=readysettech/querypilot-analytics:latest \
docker compose upQUERYPILOT_IMAGE and QUERYPILOT_ANALYTICS_IMAGE select the images. The Router service waits for Analytics to report healthy (the Compose file probes the host-networking port, http://localhost:18080/api/health) before starting. DuckDB data persists in the named volume duckdb-data, mounted at /var/lib/sqp-duckdb.
Connect.
psql -h localhost -p 5433 -U <pool-user> -d <database>Stop.
docker compose down # stop containers, keep DuckDB data
docker compose down -v # stop and wipe the DuckDB volumePre-flight: check the source database
CDC replication needs the source database configured for it. Before any deployment, run the provided deploy/check-database.sh from a machine that can reach the database:
PG_HOST=<your-pg-host> PG_USER=<user> PGPASSWORD=<password> \
./deploy/check-database.sh --backend postgresFor PostgreSQL it checks wal_level = logical, max_replication_slots >= 1, max_wal_senders >= 1, that the user has the REPLICATION attribute, and that pg_hba.conf permits replication connections. A MySQL source (preview) is checked with --backend mysql.
Post-deploy validation
Work through this checklist after any deployment.
- Router is healthy.
curl -sf http://localhost:9091/api/config/statusreturns 200 with the live config path. - Analytics is healthy and streaming.
curl http://localhost:18080/api/healthreturns{"status": "ok"};curl http://localhost:18080/api/statusreports the replication phase (streaming once the snapshot completes) and per-table progress. - Tables replicated.
curl http://localhost:18080/api/tableslists your tables; spot-check row counts against the source. - Traffic flows. Run a query through port 5433, then confirm
sqp_queries_totalappears and increments incurl http://localhost:9090/metrics. See the metrics reference. - Routing is what you intended.
curl "http://localhost:9091/api/routing/test?sql=SELECT%20count(*)%20FROM%20your_table"returns the expectedtarget_pool.
The provided deploy/validate-deployment.sh automates all of this plus row-count cross-validation between PostgreSQL and DuckDB, and can mirror a query through all three paths (source PostgreSQL, the Router, DuckDB direct) with --query "SELECT ...". It needs psql, curl, jq, and python3, and takes its connection settings from the environment variables shown in its header.
Day-2 concerns (reload, shutdown, monitoring, slots) are covered in Operations; the shipped Grafana dashboards and alert rules are in Extras.