This document provides step-by-step deployment instructions for QueryGateway in self-hosted environments.
- Python 3.14+ (backend runtime —
asyncpg0.31.0+ provides CPython 3.14 wheels) - Node.js 20 LTS (frontend build)
- PostgreSQL 16+ (application database)
- Docker & Docker Compose (recommended for production)
- Oracle connectivity (target database for data queries)
- Network access to the Oracle instance(s) you plan to query
Create a .env file from .env.example and configure:
| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL |
Yes | — | PostgreSQL connection string (postgresql+asyncpg://user:pass@host:5432/db) |
ENCRYPTION_KEY |
Yes | — | Fernet encryption key for credential storage (generate: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())") |
JWT_SECRET_KEY |
Yes | — | High-entropy platform JWT signing key with at least 32 characters |
ADMIN_USERNAME |
Yes | — | Seeded platform administrator username |
ADMIN_PASSWORD_HASH |
Yes | — | Seeded administrator bcrypt hash; generate with app.auth.hashing.hash_password() |
APP_ENV |
No | development |
Environment identifier (development, staging, production) |
DEBUG |
No | false |
Enable debug mode (never true in production) |
LOG_LEVEL |
No | INFO |
Logging level (DEBUG, INFO, WARNING, ERROR) |
CORS_ORIGINS |
No | http://localhost:5173 |
Comma-separated allowed CORS origins; wildcard is rejected |
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"Store this key securely. If the key is lost, all encrypted credentials (Oracle passwords, signing secrets) become unrecoverable.
# Clone the repository
git clone <repo-url> && cd QueryGateway
# Configure environment
cp .env.example .env
# Edit .env with your values
# Build and start all services. The one-shot `migrate` service runs
# Alembic before the API starts.
docker compose build
docker compose up -d
# Verify services are running
docker compose ps
curl http://localhost/api/v1/admin/health/live
curl http://localhost # FrontendFor Oracle thick mode, set
ORACLE_CLIENT_LIB_DIR=/opt/oracle/instantclient_19_32 in .env. The backend
image contains the pinned Linux x86-64 Instant Client and validates that its
native libraries load during the image build. Leave the variable blank for
thin mode.
Services started:
migrate— one-shot Alembic migration runnerapi— FastAPI backend on the private Docker networkweb— React SPA (nginx) on host port 80db— PostgreSQL on port 5432
cd backend
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # Linux/macOS
# .venv\Scripts\activate # Windows
# Install dependencies
pip install -r requirements.txt
# Run database migrations
alembic upgrade head
# Start the server
uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 1For production, use a process manager:
# Using gunicorn with uvicorn workers
pip install gunicorn
gunicorn app.main:app \
--worker-class uvicorn.workers.UvicornWorker \
--workers 1 \
--bind 0.0.0.0:8000 \
--access-logfile - \
--error-logfile -Keep exactly one API worker while APScheduler uses its in-process job store. Each worker restores active schedules during startup, so multiple workers or replicas would execute duplicate snapshot refreshes until distributed scheduler coordination is implemented.
cd frontend
# Install dependencies
npm ci
# Build for production
npm run build
# Serve the dist/ directory with nginx, caddy, or any static file serverNginx example config:
server {
listen 80;
server_name db2api.example.com;
# Frontend SPA
location / {
root /path/to/frontend/dist;
try_files $uri $uri/ /index.html;
}
# API proxy
location /api/ {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}# Create database and user
createuser db2api_user
createdb db2api_db -O db2api_user
# Or via psql
psql -c "CREATE USER db2api_user WITH PASSWORD 'your-password';"
psql -c "CREATE DATABASE db2api_db OWNER db2api_user;"Use the Docker images as a starting point. Key considerations:
- Deploy the API as a
Deploymentwithreplicas: 1andstrategy.type: Recreatewhile the scheduler is in-process. A rolling update can overlap the old and new Pods even with one replica, causing both schedulers to execute the same jobs. - Add distributed scheduler coordination before using rolling updates or multiple API replicas.
- Use a
Servicefor internal routing and anIngressfor external access - PostgreSQL: use a managed service (e.g., AWS RDS, GCP Cloud SQL) or a StatefulSet
- Store
ENCRYPTION_KEYandDATABASE_URLas Kubernetes Secrets - Configure liveness and readiness probes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: querygateway-api
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: querygateway-api
template:
metadata:
labels:
app: querygateway-api
spec:
containers:
- name: api
image: your-registry/querygateway-api:latest
ports:
- containerPort: 8000
livenessProbe:
httpGet:
path: /api/v1/admin/health/live
port: 8000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /api/v1/admin/health/ready
port: 8000
initialDelaySeconds: 5
periodSeconds: 10Docker Compose runs migrations automatically via the migrate service before api starts. For bare-metal or VM deployments, migrations must be run before the first request:
# Docker-only migration run
docker compose up --build --force-recreate migratecd backend
alembic upgrade headTo verify current migration state:
alembic current
alembic historyTo downgrade one step (for rollback):
alembic downgrade -1| Endpoint | Purpose | Expected Response |
|---|---|---|
GET /api/v1/admin/health/live |
Liveness check | {"status": "ok"} |
GET /api/v1/admin/health/ready |
Readiness check (DB connectivity) | {"status": "ok"} |
GET /api/v1/admin/health/dashboard |
Full system dashboard | JSON with status, DB, scheduler, counts |
- Health check: use
curl http://localhost/api/v1/admin/health/livefor Docker, orcurl http://localhost:8000/api/v1/admin/health/livefor a bare-metal backend. - Database ready: use
curl http://localhost/api/v1/admin/health/readyfor Docker, orcurl http://localhost:8000/api/v1/admin/health/readyfor bare metal. - Admin UI: Open
http://localhostfor Docker, orhttp://localhost:5173for Vite development - Create first connection: Use the Connections page to add an Oracle data source
- Test connection: Click "Test" to verify Oracle connectivity
- Create first endpoint: Use the API Endpoints page wizard
- Verify data endpoint: use
curl -H "Authorization: Bearer <token>" "http://localhost/api/v1/data/<your-path>?required_param=value"for Docker, or change the origin tohttp://localhost:8000for bare metal. - Verify required inputs: omit a required live and snapshot parameter and confirm HTTP 422
- Verify snapshot selection: preview and run the schedule, confirm an in-coverage request is
filtered, and confirm an out-of-coverage request returns
snapshot_out_of_coverage - Verify scheduler restoration: for Docker, run
docker compose restart api; for bare metal, restart the single API process with its process manager (for example,sudo systemctl restart querygateway-api). Then confirm active jobs are registered again on the health dashboard.
- Set
DEBUG=falseandAPP_ENV=production - Restrict
CORS_ORIGINSto your frontend domain(s) - Use HTTPS termination (reverse proxy or load balancer)
- Rotate the
ENCRYPTION_KEYvia a secrets manager - Enable firewall rules to restrict PostgreSQL access
- Review the Security Checklist
See Operations Guide for incident troubleshooting procedures.