Readyset Docs

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

ImageComponentBinary
querypilotRouterquerypilot
querypilot-analyticsAnalyticsquerypilot-analytics
querypilot-acceleratorStandalone Acceleratorquerypilot-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, healthcheck curl -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, sets QUERYPILOT_ANALYTICS_UNSAFE_BIND_ALL=true (containers must bind 0.0.0.0; secure the published ports at the network layer, see Security), exposes 15432/8080/9090, healthcheck curl -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

ServicePortProtocolNotes
Router, PostgreSQL wire5433PostgreSQLApplications connect here
Router, metrics9090HTTP (Prometheus)sqp_* series
Router, admin API9091HTTPAlways metrics port + 1; not separately configurable
Analytics, PostgreSQL wire15432PostgreSQLInternal; the Router's duckdb pool points here
Analytics, HTTP API8080 (18080 in Compose)HTTP/api/health, /api/status, /api/tables
Analytics, metrics9090 (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:latest

Getting 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 up

QUERYPILOT_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 volume

Pre-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 postgres

For 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.

  1. Router is healthy. curl -sf http://localhost:9091/api/config/status returns 200 with the live config path.
  2. Analytics is healthy and streaming. curl http://localhost:18080/api/health returns {"status": "ok"}; curl http://localhost:18080/api/status reports the replication phase (streaming once the snapshot completes) and per-table progress.
  3. Tables replicated. curl http://localhost:18080/api/tables lists your tables; spot-check row counts against the source.
  4. Traffic flows. Run a query through port 5433, then confirm sqp_queries_total appears and increments in curl http://localhost:9090/metrics. See the metrics reference.
  5. Routing is what you intended. curl "http://localhost:9091/api/routing/test?sql=SELECT%20count(*)%20FROM%20your_table" returns the expected target_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.