> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qwedai.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Self-hosting

> Run QWED on your own infrastructure using Docker Compose. Quick start guide with services overview including API, PostgreSQL, Redis, and Grafana monitoring.

## Quick start

```bash theme={null}
# Clone the repository
git clone https://github.com/QWED-AI/qwed-verification.git
cd qwed-verification

# Start with Docker Compose
docker-compose up -d

# Check status
docker-compose ps
```

## Services

| Service | Port | Purpose |
| - | - | - |
| QWED API | 8000 | Main API |
| PostgreSQL | 5432 | Database |
| Redis | 6379 | Cache & rate limiting |
| Grafana | 3000 | Dashboards |
| Prometheus | 9090 | Metrics |
| Jaeger | 16686 | Tracing |

## Configuration

### Environment variables

```bash theme={null}
# .env file

# Required — server will not start without these
API_KEY_SECRET=your-generated-secret
QWED_CORS_ORIGINS=https://app.yourcompany.com

# Infrastructure
DATABASE_URL=postgresql://user:pass@localhost:5432/qwed
REDIS_URL=redis://localhost:6379

# LLM provider
ACTIVE_PROVIDER=openai
OPENAI_API_KEY=sk-...

# Required — server will not start without these
API_KEY_SECRET=your-generated-secret  # python -c "import secrets; print(secrets.token_urlsafe(48))"
QWED_CORS_ORIGINS=https://app.yourcompany.com
```

<Warning>
  `API_KEY_SECRET` and `QWED_CORS_ORIGINS` are mandatory as of v5.0.0. See the [deployment guide](/advanced/deployment#environment-variables-reference) for the full reference.
</Warning>

### API configuration

```python theme={null}
# config.py
SETTINGS = {
    "rate_limit_enabled": True,
    "rate_limit_per_minute": 60,
    "cache_ttl_seconds": 3600,
    "max_query_length": 10000,
    "allowed_engines": ["math", "logic", "code", "sql"],
}
```

## Docker

### API only

```dockerfile theme={null}
FROM python:3.13-slim-bookworm
WORKDIR /app
COPY . .
RUN pip install -e .
EXPOSE 8000
CMD ["uvicorn", "qwed_new.api.main:app", "--host", "0.0.0.0"]
```

### Build and run

```bash theme={null}
docker build -t qwed-api .
docker run -p 8000:8000 \
  -e QWED_API_KEY=... \
  -e API_KEY_SECRET=... \
  -e QWED_CORS_ORIGINS="https://app.yourcompany.com" \
  qwed-api
```

## Kubernetes

```yaml theme={null}
apiVersion: apps/v1
kind: Deployment
metadata:
  name: qwed-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: qwed-api
  template:
    metadata:
      labels:
        app: qwed-api
    spec:
      containers:
      - name: qwed
        image: qwed/qwed-api:latest
        ports:
        - containerPort: 8000
        env:
        - name: QWED_API_KEY
          valueFrom:
            secretKeyRef:
              name: qwed-secrets
              key: api-key
```

> 🏢 **Enterprise Support Coming Soon:** Managed hosting, dedicated support, and SLA guarantees. Contact [support@qwedai.com](mailto:support@qwedai.com)

## Scaling

### Horizontal scaling

* Stateless API servers behind load balancer
* Redis for distributed rate limiting
* PostgreSQL with read replicas

### Performance tuning

```bash theme={null}
# Increase workers
uvicorn qwed_new.api.main:app --workers 4

# Enable connection pooling
export QWED_DB_POOL_SIZE=20
```

### Availability limits

QWED bounds request size, slow work, and sandbox usage so one request can't stall or exhaust a server process. The defaults suit most deployments. Raise them only if your host has the headroom.

| Variable | Default | What it bounds |
| - | - | - |
| `QWED_CODE_VERIFY_TIMEOUT` | `10` | Seconds a `/verify/code` scan can run. A timeout returns `503`. |
| `QWED_PROVIDER_TIMEOUT` | `30` | Seconds an LLM provider call can run for `/verify/natural_language`, `/verify/logic`, and `/verify/fact`. Calls run off the event loop. A timeout fails closed: `/verify/fact` returns `503`, the others return an `ERROR` result. |
| `QWED_Z3_TIMEOUT` | `15` | Seconds a `/verify/logic` Z3 solve can run. Solves run one at a time on a dedicated thread. |
| `QWED_BATCH_MAX_JOBS` | `1000` | Jobs kept in the `/verify/batch` job store. QWED evicts the oldest finished job first and never evicts a running job. An evicted job returns `404` from `GET /verify/batch/{job_id}`. When every stored job is active, new submissions get `503 Batch capacity reached — retry shortly.` |
| `QWED_BATCH_ITEM_TIMEOUT` | `60` | Seconds a single batch item can run. A timed-out item is marked `failed` so the job can finish. |
| `QWED_SANDBOX_MAX_CONCURRENT` | `4` | Sandbox containers running at once per server process, across `/verify/consensus` and `/verify/stats` with the Docker executor. |
| `QWED_SANDBOX_ACQUIRE_TIMEOUT` | `10` | Seconds a request waits for a free sandbox slot. If none frees in time, QWED rejects the run with `Sandbox capacity reached — retry shortly.` instead of starting another container. |

```bash theme={null}
# Example: a larger host that runs more sandboxed verifications in parallel
export QWED_SANDBOX_MAX_CONCURRENT=8
export QWED_PROVIDER_TIMEOUT=45
```

Request body caps are fixed. Every `/verify` JSON route accepts up to 1,000,000 bytes. `/verify/stats` and `/verify/image` accept their 10 MB file limit plus envelope headroom. QWED rejects an oversized body with `413` before processing it. See [Request limits](/api/endpoints#request-limits) for the full error list.

<Note>
  `QWED_SANDBOX_MAX_CONCURRENT` applies per worker process. With `uvicorn --workers 4` and the default of 4, a host can run up to 16 sandbox containers. Size the value against your host's memory, CPU, and PID limits.
</Note>

## Monitoring

### Health check

```bash theme={null}
curl http://localhost:8000/health
```

### Prometheus metrics

```text theme={null}
qwed_verification_total{engine="math", status="verified"}
qwed_verification_latency_seconds
qwed_cache_hit_rate
```

Scrape `GET /metrics/prometheus` (the Prometheus text exposition format — `/metrics` returns JSON). The endpoint requires a platform operator: create a dedicated scraper account, add its user ID to `QWED_METRICS_OPERATOR_USER_IDS` (fail-closed when unset — every caller is denied, including org owners/admins), mint the account an API key, and send it as a header on the scrape. See [All-tenant metrics authorization](/advanced/security-hardening#all-tenant-metrics-authorization).

```yaml theme={null}
# prometheus scrape config
scrape_configs:
  - job_name: 'qwed-api'
    metrics_path: '/metrics/prometheus'
    static_configs:
      - targets: ['localhost:8000']
    http_headers:
      X-Api-Key:
        values: ['qwed_live_********************************wDHo']  # masked v2 example (see API key format) — render the real one from your secret pipeline
```

## Security

1. **Always use HTTPS** in production
2. **Set `API_KEY_SECRET`** — mandatory, no default value
3. **Set `QWED_CORS_ORIGINS`** — mandatory, explicitly list allowed origins
4. **Set strong API keys**
5. **Enable rate limiting**
6. **Use network isolation**
7. **Rotate secrets regularly**
8. **Configure `QWED_METRICS_OPERATOR_USER_IDS`** if anything needs all-tenant metrics — unset means deny-all


## Related topics

- [QWED Security GitHub App: deterministic PR verification](/advanced/github-app.md)
- [QWED architecture: translation, verification, guards](/architecture.md)
- [LLM configuration](/getting-started/llm-configuration.md)
- [Custom providers](/getting-started/custom-providers.md)
- [Support QWED development](/support.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.