Local Development
This guide covers the full local development environment: infrastructure services via Docker Compose, environment variable configuration, database setup, and the Makefile workflow.
Prerequisites
| Tool | Version | Install |
|---|---|---|
| Node.js | 20+ (LTS) | nodejs.org or nvm install 20 |
| pnpm | 10+ | npm install -g pnpm@10 |
| Docker | Desktop or OrbStack | docker.com or orbstack.dev |
| Wrangler CLI | 4+ | npm install -g wrangler |
OrbStack is recommended over Docker Desktop on macOS. It starts faster, uses less memory, and
handles the TigerBeetle io_uring requirement with fewer issues.
Docker Compose Services
Start all infrastructure services with a single command:
docker compose up -d
Service Reference
| Service | Image | Port | Purpose |
|---|---|---|---|
| Postgres | pgvector/pgvector:pg17 | 5432 | Primary database (Neon-compatible) |
| TigerBeetle | Custom (see infra/docker/tigerbeetle/) | 8080 | Double-entry accounting ledger |
| Valkey | valkey/valkey:8-bookworm | 6379 | KV cache / session store (Redis-compatible) |
| SpiceDB | authzed/spicedb:v1.38.1 | 50051 (gRPC), 8443 (HTTP) | Zanzibar-style RBAC authorization |
| Grafana | grafana/grafana:latest | 3333 | Monitoring dashboards |
| Metabase | metabase/metabase:latest | 3334 | BI / SQL analytics |
| Documenso | documenso/documenso:latest | 3335 | E-signature workflows |
| Drizzle Studio | ghcr.io/drizzle-team/gateway:latest | 4983 | Visual database browser |
| Mailpit | axllent/mailpit:latest | 8025 (UI), 1025 (SMTP) | Email capture and testing |
Connection Strings
Once services are running, use these connection strings in your .env:
DATABASE_URL=postgresql://openinsure:openinsure@localhost:5432/openinsure
TIGERBEETLE_URL=http://localhost:8080
TIGERBEETLE_API_KEY=localdev
SPICEDB_GRPC_PRESHARED_KEY=localdev
Service Management
# Start all services
docker compose up -d
# Start specific services only
docker compose up -d postgres tigerbeetle redis
# View logs (all services)
docker compose logs -f
# View logs (single service)
docker compose logs -f tigerbeetle
# Stop all services (keep data)
docker compose down
# Stop + wipe all data (fresh start)
docker compose down -v
Service Details
Postgres runs with pgvector enabled (Postgres 17). An init script at infra/docker/postgres-init.sql creates the openinsure and documenso databases on first start. Data persists in the pg_data Docker volume.
TigerBeetle runs in single-replica mode with a custom Docker build (infra/docker/tigerbeetle/Dockerfile). It requires privileged: true for io_uring support. The HTTP API proxy on port 8080 matches the interface the API workers expect in production.
SpiceDB runs with an in-memory datastore (no persistence). The schema is loaded from packages/auth/spice/schema.zed. The gRPC preshared key is localdev.
Mailpit captures all outbound SMTP on port 1025. Open http://localhost:8025 to view captured emails (OTP codes, policy documents, notifications).
Environment Configuration
-
Copy the root
.env.example:cp .env.example .env -
Copy the API worker dev vars:
cp apps/api/.dev.vars.example apps/api/.dev.vars -
Set required variables in
.env:DATABASE_URL=postgresql://openinsure:openinsure@localhost:5432/openinsure
JWT_SECRET=your-secret-at-least-32-chars-long
API_SECRET=your-api-secret
SERVICE_SECRET=your-service-secret -
Match
.dev.varsto your.env(especiallyAPI_SECRET,JWT_SECRET, and DB connection).
Key Environment Variables
| Variable | Description |
|---|---|
DATABASE_URL | Postgres connection string (used by Drizzle migrations) |
JWT_SECRET | API JWT signing secret (min 32 chars) |
API_SECRET | System bearer secret for machine/demo flows |
SERVICE_SECRET | Service token exchange secret |
PORTAL_SECRET | Producer portal token exchange secret |
ADMIN_SECRET | Admin token exchange secret |
AUTH_URL | Auth Worker base URL |
CLOUDFLARE_ACCOUNT_ID | Required for Wrangler and deploy tooling |
CLOUDFLARE_API_TOKEN | Required for Wrangler and deploy tooling |
Database Setup
Run Migrations
# Apply all Drizzle migrations against DATABASE_URL
pnpm --filter @openinsure/db db:migrate
# Or via Make
make db-migrate
This runs the squashed bootstrap migration plus all forward Drizzle migrations.
Seed Rating Data
# Load base rate tables (recommended for local dev)
pnpm db:seed:rating
# Or via Make
make db-seed
Generate Schema Types
After modifying schemas in packages/db/src/schema/:
make db-generate
D1 (Underwriting Workbench)
The underwriting workbench uses a separate Cloudflare D1 database (oi-submissions):
cd apps/underwriting-workbench
npx wrangler d1 migrations apply oi-submissions --local
Drizzle Studio
Open http://localhost:4983 in your browser. The master password is localdev.
Make Commands
The Makefile in the repo root provides shortcuts for common operations:
Development
| Command | Description |
|---|---|
make dev | Start all dev servers (Turborepo) |
make dev-api | Start API worker only |
make dev-workbench | Start underwriting workbench only |
make dev-portal | Start policyholder portal only |
make dev-producer | Start producer portal only |
make dev-admin | Start admin dashboard only |
Quality
| Command | Description |
|---|---|
make test | Run all tests (pnpm turbo test) |
make test-api | Run API tests only |
make test-watch | Vitest watch mode |
make test-coverage | Run tests with V8 coverage enforcement |
make lint | Lint check (Biome) |
make format | Auto-fix lint (Biome, no formatting -- oxfmt owns formatting) |
make typecheck | Type-check all packages |
make ci | Full local CI: lint + typecheck + test + pages build |
Database
| Command | Description |
|---|---|
make db-migrate | Run database migrations |
make db-generate | Generate Drizzle schema types |
make db-seed | Seed rating data |
Build & Deploy
| Command | Description |
|---|---|
make build | Build all packages and apps |
make ship | Commit, push, and deploy all apps |
make ship-api | Deploy API worker only |
make ship-workbench | Deploy underwriting workbench only |
make clean | Remove build artifacts and caches |
Dev Server Ports
When running make dev, each app starts on its own port:
| App | URL | Filter |
|---|---|---|
| API Worker | http://localhost:8787 | apps/api |
| Underwriting Workbench | http://localhost:3000 | apps/underwriting-workbench |
| Producer Portal | http://localhost:3001 | apps/producer-portal |
| Admin Dashboard | http://localhost:3002 | apps/admin |
| Policyholder Portal | http://localhost:3003 | apps/portal |
| Ops Workbench (Vite SPA) | http://localhost:5173 | apps/workbench |
| Astro Docs | http://localhost:4321 | apps/docs |
| Finance Portal | http://localhost:3006 | apps/finance-portal |
| Compliance Portal | http://localhost:3007 | apps/compliance-portal |
Formatting & Linting
OpenInsure uses two tools with distinct responsibilities:
- oxfmt -- authoritative code formatter (Rust-based, Prettier-compatible). Config:
.oxfmtrc.json. - Biome -- linter only (formatter disabled). Config:
biome.json.
# Format all files
oxfmt .
# Check formatting (CI gate)
oxfmt --check .
# Lint with auto-fix
biome check --write .
# Lint check only (no writes)
biome check .
Git Hooks (Husky + lint-staged)
On every commit, Husky runs:
biome check --write-- lint auto-fixoxfmt --write-- formatting (runs last, gets final word)- Credential check -- blocks
.env, secrets, API keys - No-any check -- blocks
anytype usage
Getting a Dev JWT
For local API testing, exchange API_SECRET for a short-lived superadmin JWT:
curl -s -X POST http://localhost:8787/auth/demo \
-H "Content-Type: application/json" \
-d '{"secret":"'"$API_SECRET"'"}' | jq -r '.token'
Export the token for subsequent calls:
export TOKEN="<paste-token>"
# Test the API
curl -s http://localhost:8787/v1/submissions \
-H "Authorization: Bearer $TOKEN" | jq '.'
Troubleshooting
TigerBeetle fails to start
TigerBeetle requires io_uring which needs privileged: true in the Docker Compose config. On OrbStack this works automatically. On Docker Desktop for Mac, ensure you have the latest version and that the Linux VM has sufficient resources (4 GB+ RAM recommended).
Port conflicts
If port 3000 is already in use (e.g., by another dev tool), start individual apps on custom ports:
PORT=3003 pnpm --filter @openinsure/workbench dev
SpiceDB schema not loaded
SpiceDB uses an in-memory datastore -- schema must be re-applied after every restart:
# Load the authorization schema
zed schema write packages/auth/spice/schema.zed \
--endpoint=localhost:50051 \
--insecure \
--token=localdev
Fresh start
When things get stuck, wipe everything and start over:
docker compose down -v # Remove all containers + volumes
pnpm install # Reinstall dependencies
make db-migrate # Re-run migrations
docker compose up -d # Restart services
make dev # Start dev servers
Quickstart
Step-by-step guide from clone to first API call.
Testing
Vitest setup, Playwright E2E, mocking patterns, and coverage.
CI/CD
How the CircleCI pipeline runs format, lint, test, build, deploy.
Database Migrations
Drizzle migration workflow, squash strategy, and production rollout.