Local Development
The local stack mirrors the production topology: Docker Compose runs all infrastructure and optional Go services; Tilt adds hot reload for the Go services you are actively editing. A new developer should reach a working stack in under 10 minutes.
Prerequisites
| Tool | Min version | Install |
|---|---|---|
| Docker | 26.0+ | brew install --cask docker / Docker Desktop |
| Docker Compose | v2.27+ | Bundled with Docker Desktop |
| Go | 1.23+ | brew install go |
| Node | 22.0+ | brew install node |
| Make | 4.x | Bundled on macOS/Linux |
| Tilt | 0.33+ | curl -fsSL https://raw.githubusercontent.com/tilt-dev/tilt/master/scripts/install.sh | bash |
| psql | 16.x | brew install postgresql@16 (client only; no server needed) |
Pre-flight check. Before continuing:
docker info # must succeed - Docker daemon must be runningDocker Desktop should be configured with ≥ 8 GB RAM and ≥ 40 GB disk (Settings → Resources).
Quick Start
git clone git@github.com:OllyInsurance/olly.git
cd olly
make local-up # boots Docker Compose stack + seeds Keycloak passwords
make seed-apisix # seeds APISIX routes (bash infra/local/apisix/seed-routes.sh)
make seed-demo-data # seeds demo parties, policies, claims, providers
make run-all # starts all Go services from the go.work workspaceExpected end state: every service in the service map below is reachable on its localhost port. If you have Tilt installed, http://localhost:10350 shows all green.
What make local-up Does
docker compose up -d- starts all infrastructure containers (~30 s with cached images).- Polls
pg_isreadyuntil Postgres is accepting connections (max 60 s). - Runs Goose migrations for every service that has a
migrations/directory. make keycloak-set-passwords- resets all test user passwords toOlly2026.- Seeds OpenSearch indexes from the seeder package.
- Prints the full service URL list to stdout.
Service Map
Infrastructure services (Docker Compose - always on)
| Service | Container | Local port | URL | Notes |
|---|---|---|---|---|
| postgres | olly-postgres | 5432 | postgres://olly:olly@localhost:5432/* | 22 databases; see data/schemas/ |
| valkey | olly-valkey | 6380 | redis://localhost:6380 | 6379 is reserved - see SSH tunnel |
| kafka (Redpanda) | olly-kafka | 9092 | localhost:9092 | KRaft mode, single-broker |
| kafka-ui | olly-kafka-ui | 8081 | http://localhost:8081 | Redpanda Console |
| keycloak | olly-keycloak | 8093 | http://localhost:8093 | Realms: olly-members, olly-providers, olly-internal; admin: admin/admin |
| openbao | olly-openbao | 8200 | http://localhost:8200 | Dev mode; root token: root |
| opensearch | olly-opensearch | 9200 | http://localhost:9200 | Dev creds: admin/admin |
| temporal | olly-temporal | 7233 | grpc://localhost:7233 | |
| temporal-ui | olly-temporal-ui | 8233 | http://localhost:8233 | |
| apisix | olly-apisix | 9080 / 9180 | http://localhost:9080 | Gateway / Admin API |
| etcd | olly-etcd | 2379 | - | APISIX config store |
| otel-collector | olly-otel-collector | 4317 (gRPC) / 4318 (HTTP) / 8888 (metrics) | - | |
| mailpit | olly-mailpit | 1025 (SMTP) / 8025 (UI) | http://localhost:8025 | Catch-all dev SMTP |
| mirth | olly-mirth | 8443 / 8661 | https://localhost:8443 | EDI/HL7 |
| api-docs | olly-api-docs | 8098 | http://localhost:8098 | Scalar OpenAPI docs |
| gatus | olly-gatus | 8099 | http://localhost:8099 | Service health dashboard |
| mcp-server | olly-mcp-server | 4099 | http://localhost:4099/mcp | Moved from 3100 (conflict with Loki - see below) |
Port 3100 note. Port 3100 is owned by Loki (log aggregator). The MCP server was previously documented as 3100 - this was incorrect. The canonical local port for the MCP server is 4099. The in-repo service configuration reflects this assignment.
Observability services (--profile observe)
Start with docker compose --profile observe up -d.
| Service | Container | Local port | URL |
|---|---|---|---|
| loki | olly-loki | 3100 | http://localhost:3100 |
| tempo | olly-tempo | 3200 | http://localhost:3200 |
| prometheus | olly-prometheus | 9090 | http://localhost:9090 |
| grafana | olly-grafana | 3000 | http://localhost:3000 |
| jaeger | olly-jaeger | 16686 | http://localhost:16686 |
Go microservices (make run-all or Tilt)
These are started as native Go processes (from the go.work workspace), not Docker containers:
| Service | Port | URL | Notes |
|---|---|---|---|
| claims | 4001 | http://localhost:4001 | |
| eligibility | 4002 | http://localhost:4002 | |
| enrollment | 4003 | http://localhost:4003 | |
| billing | 4004 | http://localhost:4004 | |
| provider | 4005 | http://localhost:4005 | |
| notifications | 4006 | http://localhost:4006 | Thin adapter over Novu |
| policy-admin | 4007 | http://localhost:4007 | |
| triage | 4008 | http://localhost:4008 | PydanticAI agent |
| care | 4009 | http://localhost:4009 | |
| group-scheme | 4010 | http://localhost:4010 | |
| broker-api | 4011 | http://localhost:4011 | |
| consent | 4012 | http://localhost:4012 | |
| document-service | 4013 | http://localhost:4013 | |
| member-portal-api | 4014 | http://localhost:4014 |
Service logs land in /tmp/olly-logs/<service>.log.
Test Users
All users are created by make keycloak-set-passwords. Password for all accounts: Olly2026.
| Realm | Persona | Role | |
|---|---|---|---|
| alice@olldemo.co.uk | olly-members | Alice (29y F) | member |
| bella@olldemo.co.uk | olly-members | Bella (35y F) | member |
| clare@olldemo.co.uk | olly-members | Clare (62y F) | member |
| diana@olldemo.co.uk | olly-members | Diana (8y F, via parent) | member |
| ethan@olldemo.co.uk | olly-members | Ethan (44y M) | member |
| finn@olldemo.co.uk | olly-members | Finn (16y M, via parent) | member |
| grant@olldemo.co.uk | olly-members | Grant (51y M) | member |
| hugh@olldemo.co.uk | olly-members | Hugh (73y M) | member |
| hr@olldemo.co.uk | olly-internal | Employer admin | employer-admin |
| gp@olldemo.co.uk | olly-providers | GP | provider |
| admin@ollyinsurance.com | olly-internal | Platform admin | platform-admin |
Keycloak admin UI: http://localhost:8093 (admin / admin).
Database Access
All service databases share the same Postgres instance:
psql -h localhost -U olly -d claims # or billing, enrollment, provider, …Each service has its own database (claims, eligibility, enrollment, billing, provider, notifications, policy_admin, etc.).
Optional: SSH Tunnel for Valkey Port 6379
The Valkey container binds to port 6380 locally (6379 is reserved to avoid collisions with any Redis already running on your machine). Most service configuration points at 6380. If an older tool assumes the default port 6379 and cannot be reconfigured:
# forwards localhost:6379 → container's 6380
ssh -L 6379:localhost:6380 olly-dev1 -NThis requires SSH access to olly-dev1 (the shared Hetzner dev box). Production code must configure the port explicitly - do not ship code that relies on this tunnel.
Optional: Tilt for Hot Reload
After make local-up completes, run:
tilt upTilt watches services/<name>/**/*.go. On save it rebuilds the affected binary and restarts the process inside the running Compose stack - no manual make stop-all && make run-all needed.
Tilt UI: http://localhost:10350 - build/run status, live logs, and health for every resource. Press Ctrl-C to stop Tilt; Docker Compose services continue running.
web-admin (Next.js) is not managed by Tilt. It runs from its own container with npm run dev volume-mounted; Next.js's own watcher handles hot reload independently.
When to use which:
| Workflow | Use |
|---|---|
| First boot, full restart, demo recording | make run-all |
| Iterating on a single Go service | tilt up |
Optional: Compose Profiles
docker compose --profile observe up -d # adds loki, tempo, prometheus, grafana, jaeger
docker compose --profile mcp up -d # adds mcp-server on :4099 (included in default)
docker compose --profile minimal up -d # postgres + kafka + keycloak only - fastest bootVerification
After make local-up completes, run these spot-checks:
# Compose - all containers healthy
docker compose ps
# Postgres
pg_isready -h localhost -p 5432 -U olly
# Keycloak realm
curl -s http://localhost:8093/realms/olly-members | jq .realm
# OpenSearch cluster
curl -s http://localhost:9200/_cluster/health | jq .status
# Kafka (Redpanda)
rpk cluster info -X brokers=localhost:9092
# APISIX gateway
curl -s http://localhost:9080/healthcheckIf you have Tilt running: http://localhost:10350 should show all green. Mailpit at http://localhost:8025 should receive a test email when you trigger any Novu workflow (e.g. POST localhost:4006/v1/notifications/trigger).
Failure Modes
| Symptom | Likely cause | Fix |
|---|---|---|
docker compose up hangs on postgres | Old volume corrupted | docker compose down -v then re-run (wipes all data) |
Keycloak realms missing after make local-up | Realm import failed | make keycloak-import |
| Every API request returns 401 | Keycloak passwords not reset | make keycloak-set-passwords |
make run-all errors "go.work not found" | Wrong working directory | cd /root/olly (or repo root) before invoking |
| Triage replies "configuration error" | LLM API key missing | export LLM_API_KEY=<key> in the triage service env |
| Port conflict on 4000-4014 | Another process holds the port | lsof -i :<port> to identify; kill or change the service port |
| Tilt UI not updating | Stale Tilt session | tilt down && tilt up |
| OpenSearch fails to start (macOS) | File-descriptor limit too low | ulimit -n 65536 before docker compose up |
| APISIX returns 503 on every route | Routes not seeded | make seed-apisix |
| Demo data absent (no members/policies/claims) | Seeder not run | make seed-demo-data |
MCP server unreachable at :3100 | Port was reassigned | Use :4099 - 3100 belongs to Loki |
Make Targets Reference
| Target | Description |
|---|---|
make local-up | Start core Docker Compose stack + reset Keycloak passwords |
make local-up-all | Start core stack + --profile observe (Grafana, Tempo, Loki, Prometheus) |
make local-down | Stop stack (volumes preserved) |
make local-down-clean | Stop stack and wipe all volumes |
make keycloak-set-passwords | Reset test user passwords to Olly2026 |
make keycloak-import | Re-import Keycloak realm definitions |
make seed-apisix | Seed APISIX routes from infra/local/apisix/seed-routes.sh |
make seed-demo-data | Seed demo parties, policies, claims, providers |
make run-all | Start all Go services in background (logs → /tmp/olly-logs/) |
make stop-all | Stop background Go services |
make build | Build all Go services (go build ./...) |
make test | Run unit tests across all services |
make lint | Run golangci-lint across all services |
make fmt | Run gofmt across all services |
E2E Tests
E2E tests live in tests/e2e/ as a standalone Go module. They require the full stack and all Go services to be running.
# All E2E tests
cd tests/e2e && GOWORK=off go test ./... -v -timeout 90s -count=1
# By service
cd tests/e2e && GOWORK=off go test ./claims/... -v -timeout 90s -count=1
# Specific test
cd tests/e2e && GOWORK=off go test . -run 'TestFullMemberJourney' -v -timeout 90s -count=1GOWORK=off is required to prevent the Go workspace from interfering with tests/e2e/go.mod. Tests call skipIfUnreachable and skip gracefully when a service is down.
Cleanup
make local-down # docker compose down - preserves volumes
make local-down-clean # docker compose down -v - wipes all volumes and data