One container or process, with its store on SQLite on a volume or on PostgreSQL. It runs the same image and code as the other two shapes, so moving on later is a configuration change, not a migration project.
GET /events on the instanceOnly the proxy publishes ports, so QueryAPIGate is reachable only through it.
services:
queryapigate:
image: ghcr.io/anantharajuc/queryapigate:0.47.0 # pin a version: upgrades should be deliberate
restart: unless-stopped
env_file: .env
environment:
QUERYAPIGATE_TRUST_PROXY: "1" # one proxy in front: Caddy
volumes:
- queryapigate-data:/data # queryapigate.db lives here
networks: [internal]
proxy:
image: caddy:2
restart: unless-stopped
ports: ["443:443", "80:80"]
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
networks: [internal]
depends_on: [queryapigate]
networks:
internal:
volumes:
queryapigate-data:
caddy-data:api.example.com {
reverse_proxy queryapigate:5000
}QUERYAPIGATE_API_KEY=<openssl rand -hex 32> QUERYAPIGATE_SECRET_KEY=<a Fernet key> # encrypts connection passwords in the store WAREHOUSE_PASSWORD=<your database's password> # referred to as ${WAREHOUSE_PASSWORD}
403 open_access_remote until a key is set.Caddy gets and renews a certificate by itself once DNS points at the host and ports 80 and 443 are open. Then open /console, sign in with the admin key, add a connection, and create scoped keys for each caller.
docker compose run --rm queryapigate queryapigate init # optional: inactive template connections docker compose up -d docker compose ps # becomes "healthy" curl https://api.example.com/health
HEALTHCHECK on /health every 30 seconds.gthread threads, a 120-second timeout.queryapigate mcp. The -h2 tag adds Java for H2 and generic JDBC./data; relative paths resolve against it.| How | |
|---|---|
| Logs | docker compose logs -f queryapigate: one line per request, with the request id and key name |
| Upgrades | Read the changelog, back up, bump the pinned tag, docker compose pull && docker compose up -d. The store is migrated on first start. |
| Monitoring | /metrics for Prometheus. Set QUERYAPIGATE_METRICS_TOKEN, since its labels name your keys, queries and connections. |
| Optional services | Same image, other commands: redis:7-alpine + QUERYAPIGATE_REDIS_URL for a cache that survives restarts; queryapigate events --host 0.0.0.0 for live events to many clients; queryapigate mcp --host 0.0.0.0 for AI agents. Give each the same .env and volume. |
It uses SQLite's backup API, so the copy is one consistent moment, including changes still in the write-ahead log. Measured on a running server: a cp taken moments earlier was missing the last two keys created (30 keys against 32).
docker compose exec queryapigate queryapigate backup /data/backup.db --force docker compose cp queryapigate:/data/backup.db "./backups/queryapigate-$(date +%F).db" docker compose exec queryapigate rm /data/backup.db
To restore, stop the server, put the backup in place as queryapigate.db, and delete the old -wal and -shm files.
QUERYAPIGATE_SECRET_KEY (without it, encrypted passwords can't be read; keep it in your secrets manager, not next to the backup), the admin key, the ${VAR} values connections refer to, and the databases QueryAPIGate queries. Scoped keys are in the store as hashes, so they keep working after a restore.tests/test_backup_restore.py fills a store, backs it up, loses it, restores it and checks the admin API sees the same.Always one worker per process: /metrics is per process, and without Redis so are rate limits. Put a TLS proxy in front and set QUERYAPIGATE_TRUST_PROXY to the number of proxies.
pip install "queryapigate[server]" QUERYAPIGATE_API_KEY=… gunicorn --bind 127.0.0.1:5000 --workers 1 --worker-class gthread --threads 8 \ --timeout 120 "queryapigate.app:create_app()"
queryapigate migrate-to-postgres copies the SQLite store across once, before you switch traffic.Moving between shapes is a configuration change. This is what each step adds; the step for this page is highlighted.
QUERYAPIGATE_API_KEY=…
QUERYAPIGATE_SECRET_KEY=…
One process or container, with the store as SQLite on a volume. The key is required in a container; the secret key encrypts connection passwords at rest.
QUERYAPIGATE_DATABASE_URL=postgres://…
QUERYAPIGATE_REDIS_URL=redis://…
A shared PostgreSQL store and Redis, so any instance answers any request and limits are counted once. Run queryapigate events behind the proxy for live events. Guide 42 →
store.type: postgres · replicas: 3
existingSecret: queryapigate
The same two URLs and the key live in one Secret; the chart refuses several pods on SQLite, several without Redis, PostgreSQL without a database URL, and secrets put in config. Guide 54 →