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

# Self-hosted Docker

> Optional on-prem or VPC deployment

**Most users should use [Sudoiq](https://sudoiq.com)** with an API key from [Settings](https://sudoiq.com/settings). You **do not** need Docker or self-hosting for that path.

This page is for clients who must run the **Agents API** inside their own network (single-tenant VPC, air-gapped, or contractual data residency). You are responsible for **infrastructure**, **security**, and **license compliance**.

## Reference compose file

Your **agents-api** distribution ships a **compose file for application containers only** (often named **`docker-compose-no-storage.yml`**), colocated with **`startup/`** and your team’s setup runbooks.

That layout runs **application containers only**. It does **not** start **Postgres** or **Redis**; you must provide **`DB_HOST` / `DB_*`** and **`REDIS_URL`** (or equivalent) via **`.env.fastapi_main`** and run databases **elsewhere** (RDS, ElastiCache, self-managed, etc.), consistent with the comments at the top of the compose file.

## Hardware

Plan for **at least 4 GB RAM** on the Docker host for a minimal dev-style stack that includes **Traefik**, **two API replicas** (`api-blue` / `api-green`), **queue\_runner**, and **webhooks**. Production sizing depends on traffic, model latency, and worker concurrency—scale CPU/RAM accordingly.

You must **host Postgres and Redis yourself** (the compose bundle does not start them). **Postgres** must be **PostgreSQL 17–compatible** (version and extensions aligned with the migrations and client libraries in your backend release). **Redis** must be reachable at whatever URL you set as **`REDIS_URL`**—typically the same class of network as the API (VPC, TLS to a managed cache, etc.). For both stores, prefer **managed** or otherwise **operationally elastic** services (scaling storage, instance size, replicas) so traffic spikes and growth do not leave you chasing **capacity** and **throughput** on a single undersized host.

## Services in this stack

| Service                              | Role                                                                                                                                                                                                  |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **traefik**                          | Reverse proxy / router on host port **8001**; connects to `api-blue` and `api-green` on the `app` network. Requires **Traefik v3.6+** for modern Docker API compatibility (see file header comments). |
| **api-blue** / **api-green**         | **FastAPI** app (`uvicorn fastapi_main:app`, port 8001 in-container). Blue/green traffic split via Traefik priorities. Mounts **`./keys`** read-only.                                                 |
| **queue\_runner**                    | Background worker process (`queue_runner.py`) using the same image and env.                                                                                                                           |
| **webhooks**                         | **Webhook delivery** worker (`agent_event_webhooks.py`).                                                                                                                                              |
| **psql-migrate** (profile `migrate`) | One-shot **Postgres** migration runner; not started by default.                                                                                                                                       |

Network: **`app`** (explicit name `app`). Project name **`api`** to avoid colliding with other stacks.

## Configuration

1. **Generate env and keys (first-time or rotation).** From the **application** repository root, run **`startup/generate_env.py`** so you get a **`keys/`** directory and **`.env.fastapi_main`** with generated secrets and DB passwords (see the script’s docstring for `OUTPUT_DIR` / positional output path and `--db-host` overrides). Your internal README may wrap this step; run it **before** hand-editing compose-only hosts if you want a sane baseline.
2. Place **`.env.fastapi_main`** and **`keys/`** where your compose file expects them (usually the same working directory you start compose from).
3. Set **`DB_*`**, **`REDIS_URL`**, JWT/signing variables, and any cloud credentials your backend expects—merging or overriding what `generate_env` produced if your databases live outside localhost.
4. Start from that directory:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
docker compose -f docker-compose-no-storage.yml up -d
```

5. Point [Python `AgentServiceAPIClient`](/clients/python) or the [CLI](/clients/cli) **`config --host`** at your **Traefik** ingress (for example `https://your-bastion-host:8001` or TLS-terminated URL).

## Zero-downtime rotation (blue / green)

On a **single host**, **`docker-compose-no-storage.yml`** runs two API containers—**`api-blue`** and **`api-green`**—behind **Traefik** on port **8001**. Each slot has a Traefik **router priority**; **default traffic goes to whichever slot has the higher priority** (compose defaults are roughly **blue = 100**, **green = 1**, so **blue** is live first).

Use this pattern to **rotate in a new build** without blanking the only API instance:

1. **Build or pull** the new image and point **`IMAGE_TAG`** (or your compose image override) at it—often while the current live slot keeps serving traffic.
2. **Recreate the standby slot** with the new image (for example `docker compose ... up -d api-green` after updating the green service) so it passes its **health check** before you cut over.
3. **Smoke-test the standby** from the host or bastion. The compose layout supports a pinned route via header **`X-Hit-Service: blue`** or **`X-Hit-Service: green`** so you can hit `/health` (or a narrow API check) against one slot only. Treat that header as **diagnostic tooling**, not a security boundary—it is trivially spoofable, so rely on it only from **localhost** or a **trusted admin path**.
4. **Swap priorities** by setting **`API_BLUE_PRIORITY`** and **`API_GREEN_PRIORITY`** so the slot you just validated becomes the **winner**. These variables are read from a **`.env` next to the compose file** (Compose label interpolation), not from **`.env.fastapi_main`**—see the comments in your shipped compose file for the exact mechanism.
5. **Apply the change**: `docker compose -f docker-compose-no-storage.yml up -d api-blue api-green` (and restart **`queue_runner`** / **`webhooks`** when they share the same image tag, so workers match the API version you intend to run).

Repeat the same steps on the next release, alternating which slot is “new” and which is “stable.” For database **schema** changes, run your **migration** step (for example the **`migrate`** profile in the compose file) **before** shifting default traffic to a code version that depends on the new schema.

## Tenant bootstrap (`create_tenant`)

After the database exists and migrations have been applied, create an initial **tenant** and user with the repo script (run from the **application** project root with `.env.fastapi_main` loaded):

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
python -m startup.create_tenant --tenant-name "My Company" --username admin --email admin@example.com
```

Or tenant-only:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
python -m startup.create_tenant --tenant-name "My Company" --no-user
```

See **`startup/create_tenant.py`** in the monorepo for **API key** issuance, **service account** registration, and other flags (`--get-api-key`, `--register-service-account`, etc.). Your internal **DEVELOPER-README** / runbooks describe the full first-time setup flow.

## Licensing and resale

**Resale / hosted service:** Operating this software as a **multi-tenant commercial substitute** for Sudoiq—or **reselling** managed access without the right agreements—violates the suqoid agents-api open-source license. **Do not assume** this documentation grants redistribution rights. Enterprise deployments are typically **single-customer dedicated** environments.

## Related

* [Environment variables](/reference/environment)
* [Python client](/clients/python)
