Getting Started (Self-Host)
This guide brings Eparch up on a single host with Docker Compose, verifies it is healthy, applies migrations, and opens the UI. For the per-variable reference, see configuration.md.
Eparch’s runtime dependencies are PostgreSQL + pgvector, Redis, MinIO/S3, and OPA — all included in the compose files. There is no Kafka, Milvus, or Vault.
Just want to try it? (one command)
If you only need Docker + openssl, skip the manual setup below:
make quickstart # or: scripts/quickstart.sh
This generates secrets, starts the two-container monolith in local-auth +
mock-model mode (no WorkOS, no model server needed), and prints the URL
(http://localhost:8031/). Register the first account to become admin.
scripts/quickstart.sh down stops it; reset wipes data + secrets. The rest of
this guide covers a real deployment (your own secrets, model, and IdP).
1. Prerequisites
For the container self-host (recommended)
| Tool | Version | Notes |
|---|---|---|
| Docker Engine | 24+ | |
| Docker Compose | v2 (docker compose) |
The v2 plugin, not the legacy docker-compose binary |
openssl |
any | To generate the JWT signing key and secret key |
For local development (make dev-local)
| Tool | Version | Notes |
|---|---|---|
| Go | 1.25+ | |
| pnpm | 9+ | Builds/serves the platform UI |
| Docker + Compose | as above | Runs the infra stack |
| buf | latest | Only needed to regenerate protobuf stubs (make proto-gen) |
| air | latest | Hot-reload; auto-installed into ./bin on first make dev-local |
make dev-deps installs the Go tooling (buf, air, migrate, golangci-lint,
sqlc) into ./bin.
2. Choose a deployment topology
| File | Containers | When to use |
|---|---|---|
deploy/docker-compose.prod.yml |
10 app containers + Postgres/Redis/MinIO/OPA/migrate | You want to scale or restart services independently |
deploy/docker-compose.monolith.yml |
2 app containers (eparch + identity) + backends |
Solo operator / minimal operational surface |
Both run the same platform and the same one-shot migrate job. In the
monolith, the nine data-plane services run in a single eparch process
(selected via EPARCH_SERVICES), while identity stays in its own container on
purpose: it connects to Postgres privileged to bypass RLS during cross-tenant
login, so it cannot share the monolith’s single POSTGRES_URL. That security
boundary is the deployment boundary.
3. Configure secrets
cp deploy/.env.example deploy/.env
Fill in deploy/.env (never commit it — it is gitignored):
| Var | How to produce it |
|---|---|
POSTGRES_PASSWORD |
Superuser password (used by migrations and identity) |
EPARCH_APP_PASSWORD |
Runtime eparch_app role password (must match infra/compose/postgres-init/02-roles.sql, or edit that file) |
EPARCH_SECRET_KEY |
openssl rand -base64 32 — the AES-256 key for the tool-registry secret store |
JWT_RSA_PRIVATE_KEY |
openssl genrsa 2048 — PEM RSA key the identity service signs platform JWTs with |
RUNTIME_SVC_TOKEN |
Static service-account JWT for internal service-to-service calls |
MINIO_ACCESS_KEY / MINIO_SECRET_KEY |
Object-store credentials (defaults: minioadmin) |
WORKOS_API_KEY / WORKOS_CLIENT_ID / WORKOS_REDIRECT_URI |
WorkOS AuthKit credentials from the WorkOS dashboard |
Model backend. Point the gateway at an OpenAI-compatible endpoint with
LLM_GATEWAY_URL(chat) andLLM_EMBEDDING_URL(embeddings).LLM_EMBEDDING_URLis required whenENV != localso RAG never silently returns stub vectors. Upstream auth is optional — a self-hosted vLLM/Ollama on a trusted network needs none. For a keyed endpoint (DigitalOcean/OpenAI) setLLM_API_KEY, or pointLLM_CONFIGat amodels.yamlfor multiple providers. See model-providers.md for the full contract.
Generate the two keys quickly:
openssl rand -base64 32 # → paste into EPARCH_SECRET_KEY
openssl genrsa 2048 | awk 'BEGIN{ORS="\\n"}1' # → paste into JWT_RSA_PRIVATE_KEY
4. Bring the stack up
# Ten-container topology:
docker compose -f deploy/docker-compose.prod.yml --env-file deploy/.env up -d --build
# ...or the monolith:
docker compose -f deploy/docker-compose.monolith.yml --env-file deploy/.env up -d --build
Startup order is handled for you: Postgres becomes healthy → the migrate
container runs every service’s migrations as the superuser and exits → the app
services start (they depend_on the migrate job completing successfully).
5. What “healthy” looks like
docker compose -f deploy/docker-compose.prod.yml ps
postgres,redis,minioreporthealthy.migrateshowsExited (0)— it is a one-shot job and exiting cleanly is correct.opaand all app services arerunning.
Check the gateway’s HTTP liveness/readiness probes (served on the gateway HTTP port, 8031):
curl -f http://localhost:8031/healthz # liveness
curl -f http://localhost:8031/readyz # readiness
Both return 200 OK and require no auth. The identity service’s JWKS endpoint
should also serve keys:
curl -s http://localhost:8011/.well-known/jwks.json
Publishing the gateway API. The gateway’s public REST/chat/SSE API and the health probes listen on the HTTP port
8031; gRPC is8030. The compose files publish8030by default — adjust the gatewayports:mapping to expose8031if you serve the REST API to external clients.
6. Run / re-run migrations
The migrate container runs migrations automatically on every up, and it is
idempotent — each service records its own version in <svc>_schema_migrations,
so re-running is safe. To run migrations manually against a running Postgres:
docker compose -f deploy/docker-compose.prod.yml run --rm migrate
For local (non-container) development, the Makefile target applies every service’s migrations against the local dev database:
make migrate-up # uses POSTGRES_URL or the eparch_dev default
See operations.md for up/down details.
7. Reach the UI
- Container self-host: the platform UI is served by the
platform-uibuild; reach the platform through the gateway. In local development the Vite dev server runs on http://localhost:5173 and proxies/api/*to the gateway (:8031) and/auth/*to identity (:8011). - Local dev (
make dev-local): open http://localhost:5173.
Port map
Application services (container-internal defaults)
| Service | gRPC | HTTP | Notes |
|---|---|---|---|
| gateway | 8030 | 8031 | HTTP serves public REST, chat/SSE, /healthz, /readyz, /metrics |
| identity | 8010 | 8011 | HTTP serves OIDC login + JWKS |
| agent-runtime | 8001 | — | |
| rag | 8040 | — | |
| eval | 8045 | — | |
| tool-registry | 8050 | — | |
| tenant | 8055 | — | |
| event-broker | 8060 | — | |
| audit | 8070 | — | |
| agent-config | 8086 | — | |
| platform-ui | — | 5173 | Vite dev server (local dev) |
The gateway also exposes a Prometheus metrics endpoint on METRICS_PORT
(default 9090).
Infrastructure (published to the host in local dev)
| Component | Port(s) | Notes |
|---|---|---|
| PostgreSQL + pgvector | 5432 | |
| Redis | 6379 | |
| MinIO | 9000 (API), 9001 (console) | |
| OPA | 8181 | |
| OTel Collector | 4317 (gRPC), 4318 (HTTP), 8889 (Prometheus) | dev observability |
| Jaeger UI | 16686 | dev only |
| Prometheus | 9090 | dev only |
| Grafana | 3000 | dev only |
| Alertmanager | 9093 | dev only |
The Prometheus/Grafana/Jaeger stack is part of the local infra profile for development. For production, set
OTEL_EXPORTER_OTLP_ENDPOINTto a managed OTLP backend instead of self-hosting the observability farm.
Next steps
- configuration.md — every environment variable, grouped by concern.
- model-providers.md — connect your model backend.
- operations.md — backups, upgrades, troubleshooting.