Skip to content

# Deployment

## Choosing a Deployment Path

Provisa supports six deployment paths. Choose based on your audience and operational context:

Path | Artifact / Script | Best for
--- | --- | ---
Development | `start-ui.sh` | From-source development, evaluation with full demo data
macOS installer | `Provisa-<version>-macOS.dmg` | Developer workstations, evaluation
Windows installer | `Provisa-<version>-windows-x64.exe` | Developer workstations, evaluation
Linux AppImage | `Provisa.AppImage` | On-prem servers, cloud VMs, air-gapped environments
Cloud VMs (AWS) | `terraform/deploy.sh` | Multi-node cloud deployment with load balancers
Kubernetes | `helm/provisa/` | Teams already operating K8s

### VM vs Kubernetes

Both are enterprise-grade. The VM/AppImage path is simpler: no cluster to provision, no CNI or RBAC policies to configure, and the AppImage is entirely self-contained (REQ-223). It fits naturally into existing server management tooling (Ansible, Puppet, Datadog agents, Splunk forwarders, etc.).

Choose Kubernetes only if your team already operates a K8s cluster and wants Provisa to participate in that operational model (rolling deploys, HPA, unified observability) (REQ-056). The capabilities are equivalent — Kubernetes adds operational overhead, not capability.

### Image acquisition and security scanning

All production paths require obtaining the Provisa artifacts before any deployment can run. "Air-gapped" refers to what happens at install time on the target machine — the artifacts must be acquired first.

macOS and Windows installers: Download from the GitHub releases page. Fully bundled; no internet required after download (REQ-227). Intended for dev/eval, not production — no image scanning gate expected.

AppImage path: Download from the GitHub releases page and transfer to the target machine. The AppImage bundles all component images as tarballs inside a squashfs filesystem (REQ-294) — most registry scanners cannot inspect these in-place. Contact your Provisa account team for component image digests to verify against your scanner independently.

Terraform path: The AppImage must be uploaded to S3 before running `terraform/deploy.sh`. EC2 nodes download it at boot via IAM role — they require outbound S3 access (direct or via VPC gateway endpoint). Apply the same scanning policy as the AppImage path.

Helm / Kubernetes path: Individual images must be pushed to a registry the cluster can reach. This path is most compatible with registry-based scanning (Prisma Cloud, Aqua, Trivy, AWS Inspector) — images are first-class objects scanners understand natively. For air-gapped clusters, mirror images to an internal registry and override references in `values.yaml` (REQ-294).

## Development (from source)

### Recommended: `start-ui.sh`

The easiest way to run Provisa from source. Starts all infrastructure, the backend API, and the UI dev server in one command (REQ-055). Ctrl+C shuts everything down cleanly.

Prerequisites: Docker Desktop, Node.js, Python virtualenv at `.venv/`

    ./start-ui.sh

What it does:

- Starts `docker-compose.core.yml` + `docker-compose.dev.yml` (all core + demo services) and waits for healthy (REQ-055)
- Seeds Kafka with demo data
- Syncs Python dependencies from `.venv/`
- Starts the backend API on port 8001 (logs to `.logs/server.log`) (REQ-558)
- Starts the Vite UI dev server on port 3000 (REQ-559)
- Prints URLs and waits; Ctrl+C stops everything and tears down compose

    Backend: http://localhost:8001
    UI:      http://localhost:3000

Options:

`--reset-volumes` — Runs `docker compose down -v` before starting, destroying all Docker volumes (PostgreSQL data, MinIO objects, Redis state, etc.) (REQ-170). Use when you want a completely clean slate — after a schema change during development, or when Docker has crashed and left volumes corrupt. All data will be lost.

`--observability` — Adds full tracing and metrics instrumentation. Downloads the OpenTelemetry Java agent and patches Trino's `jvm.config` to load it, instruments the Provisa backend with OTLP export, and starts the OTel collector, Prometheus, Tempo, and Grafana (`http://localhost:3100`) (REQ-330). The `jvm.config` patch is automatically reverted on Ctrl+C.

### Manual steps (backend only, no UI)

If you only need the API:

1. Install Docker Desktop
2. Start core services:

       docker compose -f docker-compose.core.yml up -d

3. Start the API:

       uvicorn main:app --reload --port 8001

4. Verify: `curl http://localhost:8001/health`

### Full stack (Provisa in container)

To run the API as a container instead of on the host:

    docker compose -f docker-compose.core.yml -f docker-compose.app.yml up -d

### Services

Core (`docker-compose.core.yml`) — always required:

Service | Port | Purpose
--- | --- | ---
PostgreSQL | 5432 | Config metadata + Iceberg catalog (REQ-169)
PgBouncer | 6432 | Connection pooling (REQ-053)
Federation engine | 8080 | Query federation (REQ-028)
Redis | 6379 | Query result cache (REQ-371)
MinIO | 9000/9001 | S3-compatible object storage (REQ-029, REQ-171)

Demo (`docker-compose.dev.yml`) — optional, included by `start-ui.sh`:

Service | Port | Purpose
--- | --- | ---
MongoDB | 27017 | Demo NoSQL source
Kafka | 9092 | Demo streaming source
Schema Registry | 8081 | Demo Avro/Protobuf schema management
Debezium | — | Demo CDC connector
Elasticsearch | 9200 | Demo search source
Neo4j | 7474/7687 | Demo graph source
Fuseki | 3030 | Demo SPARQL triplestore
OpenTelemetry Collector | — | Trace collection (with `--observability`) (REQ-302)
Prometheus | 9090 | Metrics (with `--observability`) (REQ-330)
Tempo | — | Trace storage (with `--observability`) (REQ-330)
Grafana | 3100 | Dashboards (with `--observability`) (REQ-330)

### Telemetry backend (`otlp2sql`)

The `--observability` stack above (Collector → Tempo/Prometheus/Grafana) is one telemetry path. The other is `otlp2sql` (`provisa.observability.otlp2sql`): an OTLP/HTTP receiver that writes traces, metrics, and logs to a SQL database chosen by a SQLAlchemy URL, extracting the `provisa.*` span attributes at ingest so no separate compaction job runs. Writes are batched (`OTLP2SQL_BATCH_MAX_ROWS`, default 1000; `OTLP2SQL_BATCH_MAX_SECS`, default 2s).

Telemetry gets its own store, separate from the control-plane database. Select the backend with `PROVISA_OPS_DB_URL`:

`PROVISA_OPS_DB_URL` | Backend | Notes
--- | --- | ---
(unset) | dedicated DuckDB under `~/.provisa/telemetry/` | default; no server, no Docker
`clickhouse+native://user@host/otel` | ClickHouse | high-rate ingest with automatic background merges
`postgresql+psycopg2://user@host/otel` | PostgreSQL | moderate volume
`trino://user@host:8080/otel` | Trino / Iceberg | technically works, not recommended — see below

On `trino://`: the SQLAlchemy Trino dialect emits valid Trino DDL and `INSERT`s, so it is technically feasible as an `otlp2sql` backend. It is not recommended for anything but low ingest rates. Every batch flush becomes a distributed Trino `INSERT` plus an Iceberg snapshot, so high-rate telemetry produces many small files and snapshots and still needs periodic `ALTER TABLE ... EXECUTE optimize` / `expire_snapshots` — which `otlp2sql` does not run. It also puts the query engine in the ingest hot path.

For high-volume telemetry into Trino/Iceberg, use `otlp2parquet` instead: it writes parquet to object storage without going through Trino, and a scheduled Trino compaction rolls the raw files into the live Iceberg tables. For a single engine that handles both high-rate ingest and compaction, prefer ClickHouse.

Point the app and Trino OTLP exporters (`OTEL_EXPORTER_OTLP_ENDPOINT`) at the `otlp2sql` endpoint, and register the ops domain against the same `PROVISA_OPS_DB_URL` so it reads what the receiver wrote.

## macOS Installer

For developer workstations and evaluation. Fully air-gapped — no internet required after download (REQ-227).

The base installer is a native install: DuckDB federation engine + SQLite control plane + in-memory (fakeredis) cache, with no Docker, VM, Trino, Redis, or MinIO (REQ-972, REQ-979). The federation engine is a wizard choice — DuckDB (native, default), Trino-on-Docker, or an external engine (REQ-973). Observability is always-on self-telemetry viewable in Admin; the Docker collector/Prometheus/Grafana stack is an optional external demonstration, not an on/off switch (REQ-975). The demo data pack is optional and off by default (REQ-978). Trino, the Docker observability stack, and the demo are heavy add-ons resolved local-first (installer-adjacent dir, mounted volumes, `~/Downloads`, then GitHub release), so enterprises can pre-stage tarballs for air-gapped installs (REQ-977).

### Steps

1. Download `Provisa-<version>-macOS.dmg` from the GitHub releases page
2. Open the DMG and drag Provisa.app to `/Applications`
3. Double-click Provisa.app — first-launch setup runs once; the wizard offers the engine, observability, and demo choices above (REQ-1007)
4. Open Terminal:

       provisa start    # start all services
       provisa status   # confirm all services are running
       provisa open     # open the UI in the browser

(REQ-224)

### Data persistence

All data is stored in `~/.provisa/` (REQ-224). To remove everything: `provisa uninstall`.

## Windows Installer

For developer workstations and evaluation. Fully air-gapped — no internet required after download (REQ-227).

Like macOS, the base Windows installer is a native tier: a standalone Python runtime + provisa wheel + DuckDB/pg_duckdb + SQLite control plane, shipping no Docker, no VM, and no container images (REQ-979). The federation engine (Trino), the observability stack, and the demo data pack are added later via separate layered installers, in order: the Container installer (`Provisa-Container-<version>.exe`, which adds WSL2 + containerd + Trino), then the Obs installer (requires the container tier), then the Demo installer (requires Core + Obs). First-launch guidance explains how to initialize the federation engine by running the Container installer (REQ-1005).

### Steps

1. Download `Provisa-<version>-windows-x64.exe` from the GitHub releases page
2. Run the installer — no admin rights required; installs to `%LOCALAPPDATA%\Programs\Provisa\`
3. Open Provisa First Launch from the Start Menu — native setup runs once and prints the next-steps guidance for the layered add-ons (REQ-1005)
4. Open a new terminal:

       provisa status
       provisa open

(REQ-224)

### Data persistence

All data is stored in `%USERPROFILE%\.provisa\`.

## Linux AppImage — Single or Multi-Node VM

### What it is

`Provisa.AppImage` is a single self-contained executable bundling (REQ-223, REQ-228):

- A rootless Docker daemon (`dockerd-rootless.sh` + `rootlesskit`) — no system Docker or root required
- All container image tarballs (PostgreSQL, PgBouncer, MinIO, Redis, Federation engine, Provisa API) (REQ-294)
- The Provisa CLI wrapper and first-launch setup script

The Provisa image is pre-built at packaging time — Python source is never included.

### When to use

- On-premises bare metal or VM (single node or multi-node)
- Cloud VMs without a K8s cluster
- Air-gapped environments (REQ-294)
- When you want simpler operations than Kubernetes

### Steps — Single Node

1. Download `Provisa.AppImage` from the GitHub releases page and transfer to the target machine
2. Make it executable:

       chmod +x Provisa.AppImage

3. Run first-launch setup:

       ./Provisa.AppImage

4. The setup wizard asks:
5. Role → select `primary`
6. RAM budget → amount of RAM to allocate (0 = all available); determines Trino worker count
7. Hostname → this node's advertised address
8. API port → default `8000` (REQ-560)
9. Setup loads all container images (~2–5 minutes), writes config, and starts services
10. Verify:

        provisa status
        curl http://localhost:8000/health

### Steps — Multi-Node (Primary)

Run these steps on the primary node first. Secondaries must be set up after the primary is running.

1. Download and transfer `Provisa.AppImage` to the primary machine
2. Open required firewall ports (secondaries will connect inbound on these):

Port | Service
--- | ---
5432 | PostgreSQL
6379 | Redis
9000 | MinIO
8080 | Federation engine coordinator
8000 | Provisa API

1. Make executable and run:

       chmod +x Provisa.AppImage
       ./Provisa.AppImage

2. The setup wizard asks:
3. Role → select `primary`
4. RAM budget, hostname, API port → answer as for single node
5. After setup completes, note the private IP of this machine — secondaries need it
6. The wizard prints an nginx upstream block — save it for your load balancer configuration
7. Verify:

       provisa status
       curl http://localhost:8000/health

### Steps — Multi-Node (Each Secondary)

Repeat these steps on each additional node after the primary is running and reachable.

1. Download and transfer `Provisa.AppImage` to the secondary machine
2. Confirm the secondary can reach the primary:

       curl http://<primary-ip>:8000/health

3. Make executable and run:

       chmod +x Provisa.AppImage
       ./Provisa.AppImage

4. The setup wizard asks:
5. Role → select `secondary`
6. Primary IP → enter the primary node's IP (connectivity is verified live)
7. RAM budget, hostname, API port → answer as above
8. Setup loads a reduced image set (no PostgreSQL, PgBouncer, MinIO, Redis — those run only on primary) (REQ-561), starts the Provisa API and a federation engine worker
9. Verify:

       provisa status
       curl http://localhost:8000/health

10. Add this node to your load balancer upstream

### Primary / secondary topology

Primary node runs all singleton services:

Service | Why singleton
--- | ---
PostgreSQL | Shared schema, app config, semantic model
Redis | Shared query result cache and subscription state (REQ-371)
MinIO | Shared object store for redirect results and MV snapshots (REQ-029)
Federation engine coordinator | All workers (primary + secondaries) register here (REQ-028)

Secondary nodes run only:

- Provisa API — stateless; reads all config from PostgreSQL on the primary at startup (REQ-057, REQ-562)
- Federation engine worker — self-registers with the coordinator on the primary (REQ-028)

All application state flows through the primary's PostgreSQL. No manual sync required. (REQ-562)

### Non-interactive (automated) first-launch

For Terraform, cloud-init, or Ansible — pass flags instead of answering prompts:

    # Primary
    ./Provisa.AppImage --non-interactive --role primary --ram-gb 32

    # Secondary
    ./Provisa.AppImage --non-interactive --role secondary --primary-ip 10.0.0.10 --ram-gb 32

Non-interactive mode installs a systemd unit (`/etc/systemd/system/provisa.service`) for start-on-boot. (REQ-563)

Flag | Description
--- | ---
`--non-interactive` | Skip all prompts; install systemd unit
`--role primary\|secondary` | Node role
`--primary-ip <ip>` | Primary node IP (required for secondary)
`--ram-gb <n>` | RAM to allocate (0 = all available)

## Cloud VM Deployment — Terraform (AWS)

Provisions a full multi-node Provisa cluster on AWS — VPC, security groups, EC2 instances, ALB, NLB — in one interactive command. (REQ-564)

### Files

File | Purpose
--- | ---
`terraform/deploy.sh` | Interactive wrapper — collects parameters, validates credentials, writes `terraform.tfvars`, runs apply
`terraform/aws/variables.tf` | All variable definitions with defaults
`terraform/aws/main.tf` | VPC, subnets, security groups, IAM, EC2, ALB, NLB
`terraform/aws/outputs.tf` | Endpoint URLs and node IPs

### Steps

1. Download `Provisa.AppImage` from the GitHub releases page

2. Upload it to an S3 bucket in your AWS account:

       aws s3 cp Provisa.AppImage s3://<your-bucket>/releases/Provisa.AppImage

3. Ensure AWS credentials are available in your shell (any of):

4. Environment variables: `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY`
5. Named profile: `export AWS_PROFILE=my-profile`

6. Active SSO session: `aws sso login`

7. (Optional) If you want SSH access to nodes, create an EC2 key pair in your target region and note the key pair name

8. Run the deploy wrapper:

       bash terraform/deploy.sh

9. Answer the wizard questions (see reference table below). The script verifies the AppImage exists in S3 before proceeding and aborts if it does not

10. Review the deployment summary and confirm

11. Terraform provisions all infrastructure (~5–10 minutes). After apply, the script prints:

        api_endpoint      = "http://<alb-dns>:8000"
        flight_endpoint   = "<nlb-dns>:8815"
        primary_ip        = "10.0.x.x"
        secondary_ips     = ["10.0.x.x", ...]

(REQ-564, REQ-143)

1. (Optional) Point DNS records at the ALB and NLB DNS names

2. Verify:

       curl http://<api_endpoint>/health

### Wizard questions

Question | Default | Notes
--- | --- | ---
Cloud provider | — | AWS only today
AWS credentials | — | Checks for active session first
Region | `us-east-1` |
Node count | `2` | 1 = primary only, no LB; 2+ = primary + secondaries + ALB/NLB
Instance type | `m7i.2xlarge` | See sizing guide below
Root volume size | `100 GB` | Per node
RAM budget | `0` (all RAM) | Determines Trino worker count per node
S3 bucket | — | Verified live before proceeding
S3 key | `releases/Provisa.AppImage` |
SSH access | No | Requires existing key pair name + admin CIDR
VPC CIDR | `10.0.0.0/16` |

### Instance sizing guide

Type | vCPU | RAM | Trino workers/node | Use case
--- | --- | --- | --- | ---
`m7i.xlarge` | 4 | 16 GB | 0 | Dev / small datasets
`m7i.2xlarge` | 8 | 32 GB | 1 | Small production
`m7i.4xlarge` | 16 | 64 GB | 2 | Medium production
`m7i.8xlarge` | 32 | 128 GB | 4 | Large production

All nodes contribute workers to one coordinator on the primary (REQ-028). A 3-node `m7i.4xlarge` cluster yields 6 Trino workers total.

### What gets provisioned

- VPC with two public subnets across two availability zones (REQ-564)
- Security groups: LB group (public ingress on 8000/8815), nodes group (LB → nodes, intra-cluster, optional SSH)
- IAM role + instance profile with S3 GetObject on the AppImage bucket
- Primary EC2 instance — runs first-launch in `--non-interactive --role primary` mode
- Secondary EC2 instances (node_count − 1) — run first-launch in `--non-interactive --role secondary --primary-ip <primary private IP>` mode; depend on primary completing first
- ALB on port 8000 — HTTP API, health-checks `/health` (REQ-560)
- NLB on port 8815 — Arrow Flight / gRPC (REQ-143)
- Both LBs attach to all nodes

### Prerequisites checklist

- [ ] IAM permissions: EC2 full, ELB full, VPC full, IAM role create, S3 GetObject on AppImage bucket
- [ ] `Provisa.AppImage` uploaded to S3
- [ ] EC2 nodes have outbound S3 access (direct internet or S3 VPC gateway endpoint)
- [ ] EC2 key pair exists in target region (if SSH is needed)
- [ ] Terraform ≥ 1.5 installed locally
- [ ] DNS records planned for ALB / NLB (optional but recommended)
- [ ] ACM certificate ready if HTTPS is required (not included in base Terraform)

### Secrets

No secrets are embedded in Terraform. The AppImage generates credentials during first-launch and writes them to `~/.provisa/config.yaml` on each node (REQ-563). For production, retrieve the admin token from the primary node after deployment:

    ssh ubuntu@<primary-public-ip> cat ~/.provisa/config.yaml | grep admin_token

## Kubernetes / Helm

### When to use

Your team already operates a Kubernetes cluster and wants Provisa to participate in that operational model (REQ-056). If you are evaluating Provisa or deploying on-premises without an existing cluster, the AppImage path is simpler.

Note: the Provisa AppImage cannot run inside a Kubernetes pod — it requires FUSE and a rootless Docker daemon, which are not available in standard pod security profiles.

### Steps

1. Confirm cluster access:

       kubectl cluster-info

2. Pull and mirror images to your internal registry (required for air-gapped or scanned environments; skip if pulling from public registries directly) (REQ-294):

Image | Used for
--- | ---
`provisa/provisa:<version>` | Provisa API
`trinodb/trino:480` | Federation engine coordinator + workers (REQ-169)
`postgres:16` | In-cluster PostgreSQL (if `postgresql.enabled`) (REQ-169)
`edoburu/pgbouncer:latest` | In-cluster PgBouncer (if `pgbouncer.enabled`) (REQ-053)
`redis:7.2` | In-cluster Redis (if `redis.enabled` and no `redis.host`) (REQ-371)
`minio/minio:latest` | In-cluster MinIO (if `minio.enabled`) (REQ-029)

For registry-scanned environments: - Push each image to your staging registry - Run your scanner (Prisma Cloud, Aqua, Trivy, AWS Inspector) and obtain approval - Promote to your production internal registry

1. Decide before installing:
2. PostgreSQL — in-cluster (`postgresql.enabled: true`) or external managed (`postgresql.host`)? External recommended for production
3. Redis — in-cluster or external (`redis.host`)? Change the default password (`redis.password`)
4. MinIO / S3 — in-cluster MinIO or native S3? For AWS, use S3 with an IAM role

5. Secrets — pass via `--set` for evaluation; use External Secrets or Vault Agent for production

6. Install the chart:

       helm install provisa helm/provisa/ \
         --set config.pgPassword=<password> \
         --set config.adminToken=<token> \
         --set s3.endpoint=https://s3.amazonaws.com \
         --set s3.bucket=my-provisa-results \
         --namespace provisa --create-namespace

If using an internal registry, add image overrides:

    --set image.repository=harbor.internal.example.com/provisa/provisa \
    --set image.tag=1.2.3 \
    --set trino.image.repository=harbor.internal.example.com/trinodb/trino \
    --set trino.image.tag=480

1. Verify pods are running:

       kubectl get pods -n provisa

2. Check the API:

       kubectl port-forward svc/provisa 8000:8000 -n provisa
       curl http://localhost:8000/health

3. (Optional) Enable ingress for external access — set `ingress.enabled: true` and configure your ingress controller

### Prerequisites checklist

- [ ] Kubernetes 1.26+, Helm 3.12+
- [ ] Storage class supporting `ReadWriteOnce` PVCs (for in-cluster stateful services)
- [ ] Images available to the cluster (public or internal registry)
- [ ] PostgreSQL endpoint + credentials (if external)
- [ ] Redis endpoint + credentials (if external)
- [ ] S3 bucket + credentials or IAM role
- [ ] Admin token chosen
- [ ] Ingress controller configured (if external access needed)

### Key values

Value | Default | Description
--- | --- | ---
`replicaCount` | `2` | Provisa API replicas (stateless) (REQ-057)
`config.pgHost` | `postgres` | PostgreSQL host
`config.pgPassword` |  | PostgreSQL password
`config.adminToken` |  | Admin API bearer token
`redis.enabled` | `true` | Deploy in-cluster Redis StatefulSet (REQ-371)
`redis.host` | `""` | Set to use external Redis
`redis.port` | `6379` |
`redis.password` | `"provisa"` | Change this
`redis.tls` | `false` |
`trino.enabled` | `true` | Deploy federation engine (REQ-028)
`trino.workers` | `2` | Federation engine worker replicas (REQ-056)
`postgresql.enabled` | `true` | Deploy in-cluster PostgreSQL (REQ-169)
`postgresql.host` | `""` | Set to use external PostgreSQL
`minio.enabled` | `true` | Deploy in-cluster MinIO (REQ-029)
`s3.endpoint` |  | S3-compatible endpoint URL
`s3.bucket` | `provisa-results` | Bucket for large result redirect (REQ-029, REQ-137)
`ingress.enabled` | `false` | Enable ingress

### Scaling

    kubectl scale deployment/provisa --replicas=5 --namespace provisa

Federation engine workers scale independently — more workers increase throughput and concurrent query capacity (REQ-056). (REQ-057)

### Updating config

    kubectl create configmap provisa-config \
      --from-file=config.yaml=./config.yaml \
      --namespace provisa --dry-run=client -o yaml | kubectl apply -f -
    kubectl rollout restart deployment/provisa --namespace provisa

## High Availability & Recovery

Provisa applies a two-tier recovery model across all deployment modes (REQ-703):

- Tier 1 — transient errors. Read operations retry for up to 30 seconds on transient errors using exponential backoff with full jitter. Tune the budget with `PROVISA_RETRY_BUDGET_SECS`. Write operations are never retried internally, and memory errors are never retryable.
- Tier 2 — component failure. An internal engine watcher detects and restarts failed software components within 2–3 minutes.

Machine-level and cluster-level failures remain the operator's responsibility — provision redundant nodes and a load balancer (Terraform and Helm paths above) for node-loss tolerance.

## Federation Engine Dependencies

The warehouse federation engines require Python packages and system-level components beyond Provisa's default install. All Python packages listed here are declared in `pyproject.toml` and installed as part of the standard `pip install provisa` or `pip install -e .` [tool-verified: `pyproject.toml` lines 44–52].

The Python packages ship with Provisa's default install — no optional extras required for any warehouse engine. The system-level items (ODBC driver, cloud CLIs, service-account keys) must be installed separately.

### Python packages (already in core dependencies)

[tool-verified: `pyproject.toml` lines 41–52]

Package | Engine | Purpose
--- | --- | ---
`databricks-sql-connector` | Databricks | SQL warehouse connection; Arrow Cloud Fetch (REQ-987)
`snowflake-connector-python[pandas]` | Snowflake | Connection + Arrow-native `fetch_arrow_table` (REQ-988)
`google-cloud-bigquery` | BigQuery | Query execution
`google-cloud-bigquery-storage` | BigQuery | Storage Read API for Arrow-native reads
`google-cloud-storage` | BigQuery | GCS staging for external-table links
`pyodbc` | Fabric, Synapse | ODBC connection to T-SQL endpoints
`azure-identity` | Fabric, Synapse | Azure AD token via `DefaultAzureCredential`
`clickhouse-connect` | ClickHouse | HTTP columnar reads
`protobuf>=6.33.5,<7` | BigQuery, gRPC | Compatibility pin — `google-cloud-*` and OTel share a protobuf runtime; `<7` keeps them aligned
`grpcio-status<1.82` | gRPC | Aligns with the `protobuf<7` pin

### System-level requirements

These are not Python packages — they must be installed on the host or container that runs Provisa.

Microsoft Fabric and Azure Synapse (ODBC)

`pyodbc` connects through the Microsoft ODBC Driver for SQL Server (`msodbcsql18`). The driver must be installed on the host — not via pip. [tool-verified: `mssql_warehouse_runtime.py` line 84 `"ODBC Driver 18 for SQL Server"` default]

macOS:

    brew install microsoft/mssql-release/msodbcsql18

Linux (Ubuntu/Debian):

    curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
    curl https://packages.microsoft.com/config/ubuntu/22.04/prod.list > /etc/apt/sources.list.d/mssql-release.list
    apt-get update && ACCEPT_EULA=Y apt-get install -y msodbcsql18

Provisa picks up the driver automatically. To override the driver name (for non-standard installs), set:

    export PROVISA_MSSQL_ODBC_DRIVER="ODBC Driver 17 for SQL Server"

Azure AD authentication (Fabric and Synapse)

Both engines authenticate via `azure.identity.DefaultAzureCredential` [tool-verified: `mssql_warehouse_runtime.py:79`, `fabric_shortcuts.py:46`]. `DefaultAzureCredential` checks credential sources in order: environment variables, workload identity, managed identity, VS Code, `az login`, and others.

For local development, `az login` is the simplest path:

    az login

For production, use managed identity (on Azure VMs or AKS) — no credential management needed. For service-principal auth, set:

    export AZURE_TENANT_ID=<tenant>
    export AZURE_CLIENT_ID=<app-id>
    export AZURE_CLIENT_SECRET=<secret>

BigQuery (service account)

`google-cloud-bigquery` uses Application Default Credentials. For local development, point to a service-account key file:

    export GOOGLE_APPLICATION_CREDENTIALS=/path/to/sa-key.json

For production on GCP (Cloud Run, GKE with Workload Identity, Compute Engine), the library picks up the attached service account automatically — no environment variable needed.

The service account needs:

- `roles/bigquery.dataViewer` — read data
- `roles/bigquery.jobUser` — run queries
- `roles/bigquery.dataEditor` — create external tables (for ATTACH)
- `roles/storage.objectViewer` — read GCS objects for external tables

Databricks (CA certificate in dev proxy environments)

If Provisa runs behind a TLS-intercepting proxy (Charles, mitmproxy, corporate proxies), the Databricks SQL connector may reject the proxy's certificate. Pass a custom CA bundle:

    export REQUESTS_CA_BUNDLE=/path/to/your/proxy-ca.pem

The Databricks connector inherits this from `requests` — no Databricks-specific env var is needed.

### Per-engine checklist

Databricks (REQ-987)

- [ ] `databricks-sql-connector` installed (default)
- [ ] Engine URL with `http_path`: `databricks://token:TOKEN@workspace.azuredatabricks.net?http_path=/sql/1.0/warehouses/xxx`
- [ ] Personal access token or service principal token
- [ ] `REQUESTS_CA_BUNDLE` set if behind TLS-intercepting proxy

Snowflake (REQ-988)

- [ ] `snowflake-connector-python[pandas]` installed (default)
- [ ] Engine URL: `snowflake://user:pass@account.snowflakecomputing.com/database`
- [ ] `account` in `PROVISA_ENGINE_URL` or `federation_hints`

BigQuery (REQ-989)

- [ ] `google-cloud-bigquery`, `google-cloud-bigquery-storage`, `google-cloud-storage` installed (default)
- [ ] `GOOGLE_APPLICATION_CREDENTIALS` set (dev) or workload identity configured (prod)
- [ ] `GOOGLE_CLOUD_PROJECT` set if the project cannot be inferred from the service account
- [ ] Service account has BigQuery Data Viewer + Job User roles

Microsoft Fabric (REQ-989)

- [ ] `pyodbc` + `azure-identity` installed (default)
- [ ] `msodbcsql18` system driver installed
- [ ] `FABRIC_SQL_SERVER` and `FABRIC_DATABASE` set
- [ ] Azure AD auth: `az login` (dev) or managed identity / service principal (prod)
- [ ] `FABRIC_WORKSPACE_ID` set if using external object-storage links

Azure Synapse (REQ-989)

- [ ] Same Python + system requirements as Fabric
- [ ] `SYNAPSE_SQL_SERVER` and `SYNAPSE_DATABASE` set
- [ ] Same Azure AD auth setup as Fabric

ClickHouse (REQ-986)

- [ ] `clickhouse-connect` installed (default)
- [ ] Engine URL: `clickhouse+http://user:pass@host:8123/database`
- [ ] `secure: "true"` in `federation_hints` for TLS (port 8443)

## Environment Variables

Variable | Default | Purpose
--- | --- | ---
`PG_PASSWORD` |  | PostgreSQL password
`PROVISA_CONFIG` | `config/provisa.yaml` | Path to config file (REQ-528)
`PROVISA_REDIRECT_ENABLED` | `false` | Enable large result redirect to S3 (REQ-029, REQ-137)
`PROVISA_REDIRECT_THRESHOLD` | `1000` | Row count threshold for redirect (REQ-029)
`PROVISA_REDIRECT_BUCKET` | `provisa-results` | S3 bucket (REQ-029)
`PROVISA_REDIRECT_ENDPOINT` |  | S3-compatible endpoint URL (REQ-029)
`PROVISA_REDIRECT_TTL` | `3600` | Presigned URL TTL (seconds) (REQ-141)
`REDIS_HOST` | `localhost` | Redis host
`REDIS_PORT` | `6379` | Redis port
`REDIS_PASSWORD` |  | Redis password
`REDIS_TLS` | `false` | Enable TLS for Redis
`TRINO_HOST` | `localhost` | Trino federation engine coordinator host (REQ-028, REQ-054)
`TRINO_PORT` | `8080` | Trino federation engine coordinator HTTP port (REQ-028, REQ-054)
`PROVISA_ENGINE` | `duckdb` | Active federation engine key (REQ-989); overrides persisted config
`PROVISA_ENGINE_URL` |  | Connection URL for URL-driven engines (Databricks, Snowflake, ClickHouse, BigQuery, Fabric, Synapse, SQLAlchemy)
`PROVISA_MATERIALIZE_URL` |  | Materialization store URL override; defaults to engine's own store
`PROVISA_MSSQL_ODBC_DRIVER` | `ODBC Driver 18 for SQL Server` | ODBC driver name for Fabric / Synapse
`GOOGLE_APPLICATION_CREDENTIALS` |  | Path to GCP service-account key JSON (BigQuery)
`GOOGLE_CLOUD_PROJECT` |  | GCP project ID (BigQuery; inferred from service account when unset)
`FABRIC_SQL_SERVER` |  | Microsoft Fabric SQL analytics endpoint hostname
`FABRIC_DATABASE` |  | Fabric database name
`FABRIC_WORKSPACE_ID` |  | Fabric workspace GUID (required for external object-storage shortcuts)
`SYNAPSE_SQL_SERVER` |  | Azure Synapse dedicated SQL pool or serverless hostname
`SYNAPSE_DATABASE` |  | Synapse database name
`AZURE_TENANT_ID` |  | Azure AD tenant (service-principal auth for Fabric/Synapse)
`AZURE_CLIENT_ID` |  | Azure AD application client ID
`AZURE_CLIENT_SECRET` |  | Azure AD application client secret
`REQUESTS_CA_BUNDLE` |  | Custom CA bundle path (Databricks connector, dev TLS-proxy)

## CLI Commands

    provisa start              # Start all services
    provisa stop               # Stop all services
    provisa restart            # Restart
    provisa status             # Show service health
    provisa open               # Open the UI in the browser
    provisa logs               # Tail service logs
    provisa export             # Print current config as YAML to stdout
    provisa export FILE        # Write current config as YAML to FILE
    provisa import FILE        # Replace running config with YAML from FILE

(REQ-224, REQ-164)

### Config promotion workflow (dev → test → prod)

All environment-specific settings (connection strings, secrets, ports) belong in environment variables or secret managers — not in the exported config. The exported YAML captures your semantic model: sources, domains, roles, views. (REQ-164)

    # On dev — export after making changes in the UI
    provisa export > config.yaml
    git add config.yaml && git commit -m "chore: update semantic model"
    git push

    # On test/prod — pull and import
    git pull
    provisa import config.yaml