Skip to content
Updated Jun 9, 2026

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

ToolMin versionInstall
Docker26.0+brew install --cask docker / Docker Desktop
Docker Composev2.27+Bundled with Docker Desktop
Go1.23+brew install go
Node22.0+brew install node
Make4.xBundled on macOS/Linux
Tilt0.33+curl -fsSL https://raw.githubusercontent.com/tilt-dev/tilt/master/scripts/install.sh | bash
psql16.xbrew install postgresql@16 (client only; no server needed)

Pre-flight check. Before continuing:

bash
docker info          # must succeed - Docker daemon must be running

Docker Desktop should be configured with ≥ 8 GB RAM and ≥ 40 GB disk (Settings → Resources).

Quick Start

bash
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 workspace

Expected 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

  1. docker compose up -d - starts all infrastructure containers (~30 s with cached images).
  2. Polls pg_isready until Postgres is accepting connections (max 60 s).
  3. Runs Goose migrations for every service that has a migrations/ directory.
  4. make keycloak-set-passwords - resets all test user passwords to Olly2026.
  5. Seeds OpenSearch indexes from the seeder package.
  6. Prints the full service URL list to stdout.

Service Map

Infrastructure services (Docker Compose - always on)

ServiceContainerLocal portURLNotes
postgresolly-postgres5432postgres://olly:olly@localhost:5432/*22 databases; see data/schemas/
valkeyolly-valkey6380redis://localhost:63806379 is reserved - see SSH tunnel
kafka (Redpanda)olly-kafka9092localhost:9092KRaft mode, single-broker
kafka-uiolly-kafka-ui8081http://localhost:8081Redpanda Console
keycloakolly-keycloak8093http://localhost:8093Realms: olly-members, olly-providers, olly-internal; admin: admin/admin
openbaoolly-openbao8200http://localhost:8200Dev mode; root token: root
opensearcholly-opensearch9200http://localhost:9200Dev creds: admin/admin
temporalolly-temporal7233grpc://localhost:7233
temporal-uiolly-temporal-ui8233http://localhost:8233
apisixolly-apisix9080 / 9180http://localhost:9080Gateway / Admin API
etcdolly-etcd2379-APISIX config store
otel-collectorolly-otel-collector4317 (gRPC) / 4318 (HTTP) / 8888 (metrics)-
mailpitolly-mailpit1025 (SMTP) / 8025 (UI)http://localhost:8025Catch-all dev SMTP
mirtholly-mirth8443 / 8661https://localhost:8443EDI/HL7
api-docsolly-api-docs8098http://localhost:8098Scalar OpenAPI docs
gatusolly-gatus8099http://localhost:8099Service health dashboard
mcp-serverolly-mcp-server4099http://localhost:4099/mcpMoved 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.

ServiceContainerLocal portURL
lokiolly-loki3100http://localhost:3100
tempoolly-tempo3200http://localhost:3200
prometheusolly-prometheus9090http://localhost:9090
grafanaolly-grafana3000http://localhost:3000
jaegerolly-jaeger16686http://localhost:16686

Go microservices (make run-all or Tilt)

These are started as native Go processes (from the go.work workspace), not Docker containers:

ServicePortURLNotes
claims4001http://localhost:4001
eligibility4002http://localhost:4002
enrollment4003http://localhost:4003
billing4004http://localhost:4004
provider4005http://localhost:4005
notifications4006http://localhost:4006Thin adapter over Novu
policy-admin4007http://localhost:4007
triage4008http://localhost:4008PydanticAI agent
care4009http://localhost:4009
group-scheme4010http://localhost:4010
broker-api4011http://localhost:4011
consent4012http://localhost:4012
document-service4013http://localhost:4013
member-portal-api4014http://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.

EmailRealmPersonaRole
alice@olldemo.co.ukolly-membersAlice (29y F)member
bella@olldemo.co.ukolly-membersBella (35y F)member
clare@olldemo.co.ukolly-membersClare (62y F)member
diana@olldemo.co.ukolly-membersDiana (8y F, via parent)member
ethan@olldemo.co.ukolly-membersEthan (44y M)member
finn@olldemo.co.ukolly-membersFinn (16y M, via parent)member
grant@olldemo.co.ukolly-membersGrant (51y M)member
hugh@olldemo.co.ukolly-membersHugh (73y M)member
hr@olldemo.co.ukolly-internalEmployer adminemployer-admin
gp@olldemo.co.ukolly-providersGPprovider
admin@ollyinsurance.comolly-internalPlatform adminplatform-admin

Keycloak admin UI: http://localhost:8093 (admin / admin).

Database Access

All service databases share the same Postgres instance:

bash
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:

bash
# forwards localhost:6379 → container's 6380
ssh -L 6379:localhost:6380 olly-dev1 -N

This 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:

bash
tilt up

Tilt 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:

WorkflowUse
First boot, full restart, demo recordingmake run-all
Iterating on a single Go servicetilt up

Optional: Compose Profiles

bash
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 boot

Verification

After make local-up completes, run these spot-checks:

bash
# 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/healthcheck

If 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

SymptomLikely causeFix
docker compose up hangs on postgresOld volume corrupteddocker compose down -v then re-run (wipes all data)
Keycloak realms missing after make local-upRealm import failedmake keycloak-import
Every API request returns 401Keycloak passwords not resetmake keycloak-set-passwords
make run-all errors "go.work not found"Wrong working directorycd /root/olly (or repo root) before invoking
Triage replies "configuration error"LLM API key missingexport LLM_API_KEY=<key> in the triage service env
Port conflict on 4000-4014Another process holds the portlsof -i :<port> to identify; kill or change the service port
Tilt UI not updatingStale Tilt sessiontilt down && tilt up
OpenSearch fails to start (macOS)File-descriptor limit too lowulimit -n 65536 before docker compose up
APISIX returns 503 on every routeRoutes not seededmake seed-apisix
Demo data absent (no members/policies/claims)Seeder not runmake seed-demo-data
MCP server unreachable at :3100Port was reassignedUse :4099 - 3100 belongs to Loki

Make Targets Reference

TargetDescription
make local-upStart core Docker Compose stack + reset Keycloak passwords
make local-up-allStart core stack + --profile observe (Grafana, Tempo, Loki, Prometheus)
make local-downStop stack (volumes preserved)
make local-down-cleanStop stack and wipe all volumes
make keycloak-set-passwordsReset test user passwords to Olly2026
make keycloak-importRe-import Keycloak realm definitions
make seed-apisixSeed APISIX routes from infra/local/apisix/seed-routes.sh
make seed-demo-dataSeed demo parties, policies, claims, providers
make run-allStart all Go services in background (logs → /tmp/olly-logs/)
make stop-allStop background Go services
make buildBuild all Go services (go build ./...)
make testRun unit tests across all services
make lintRun golangci-lint across all services
make fmtRun 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.

bash
# 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=1

GOWORK=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

bash
make local-down          # docker compose down - preserves volumes
make local-down-clean    # docker compose down -v - wipes all volumes and data

Olly Health Insurance Platform