Local Development
Full local development workflow from env configuration to running all apps
The local development workflow follows a strict sequence: configure environment, start infrastructure, start Temporal runtime, run migrations, then start applications.
Prerequisites
| Requirement | Details |
|---|---|
| Node.js | >= 22 |
| pnpm | 10.x (corepack enable && corepack prepare pnpm@10.28.0 --activate) |
| Docker | Must be running (for infrastructure services) |
| 1Password CLI | op binary, optional for local dev. Only needed if using secrets from 1Password vaults |
Step-by-step setup
1. Install dependencies
pnpm install2. Configure environment
pnpm env:configureThis script copies .env.example to .env (if it does not exist) and upserts all required keys with local development defaults:
| Key | Default value |
|---|---|
NODE_ENV | development |
API_PORT | 4000 |
DATABASE_URL | postgres://postgres:postgres@localhost:5432/<domain_identifier> |
AUTH_SECRET | local-dev-auth-secret-123456 |
API_CORS_ORIGIN | http://localhost:3000 |
API_BASE_URL | http://localhost:4000 |
DEV_CLOUDFLARE_TUNNEL_ENABLED | false |
DEV_CLOUDFLARE_TUNNEL_URL | optional (defaults to http://127.0.0.1:${API_PORT}) |
DEV_CLOUDFLARE_TUNNEL_LOG_FILE | .data/cloudflared/dev-tunnel.log |
DEV_CLOUDFLARE_TUNNEL_LOCK_DIR | /tmp/tx-agent-kit-dev-tunnel.lock |
DEV_CLOUDFLARE_TUNNEL_STALE_TIMEOUT_SECONDS | 120 |
DEV_CLOUDFLARE_TUNNEL_MISSING_PID_GRACE_SECONDS | 15 |
TEMPORAL_RUNTIME_MODE | cli |
TEMPORAL_ADDRESS | localhost:7233 |
TEMPORAL_TASK_QUEUE | tx-agent-kit |
OTEL_EXPORTER_OTLP_ENDPOINT | http://localhost:4318 |
The script is idempotent. Running it again updates existing keys without duplicating them.
3. Start infrastructure
pnpm infra:ensureThis starts all Docker Compose services under the infra profile and waits for health checks to pass. Services include PostgreSQL, Redis, the OTEL collector, Jaeger, Prometheus, Grafana, and Loki.
If infrastructure is already running, the command exits immediately. See Docker Stack for service details.
4. Start local Temporal CLI runtime
pnpm temporal:dev:upUse pnpm temporal:dev:status to verify readiness.
5. Run database migrations
pnpm db:migrate
pnpm db:schemas:applyDrizzle ORM migrations run against the database specified in DATABASE_URL. Reapplied desired-state SQL then runs from packages/infra/db/schemas/.
6. Start all applications
pnpm devThis runs all application dev servers in parallel via Turborepo:
| App | URL | Port |
|---|---|---|
| Web | http://localhost:3000 | 3000 |
| API | http://localhost:4000 | 4000 |
| Worker | (background process) | n/a |
| Mobile | http://localhost:8081/status (Metro status, default) | 8081 |
You can also start individual applications:
pnpm dev:web # Next.js only
pnpm dev:api # Effect HTTP API only
pnpm dev:worker # Temporal worker only
pnpm dev:mobile # Expo React Native only
pnpm dev:mobile:web # Expo web preview (intentional browser target)pnpm dev:mobile runs Expo/Metro on MOBILE_PORT (default 8081). Open the actual app using iOS Simulator, Android Emulator, or Expo Go. http://localhost:8081 is the Metro/web endpoint and is usually not the best native mobile UX.
To expose your local API via Cloudflare tunnel while developing, set:
DEV_CLOUDFLARE_TUNNEL_ENABLED=trueOptional settings:
DEV_CLOUDFLARE_TUNNEL_URL(defaults tohttp://127.0.0.1:${API_PORT})DEV_CLOUDFLARE_TUNNEL_LOG_FILEDEV_CLOUDFLARE_TUNNEL_TOKEN(for named tunnel mode)DEV_CLOUDFLARE_TUNNEL_LOCK_DIRDEV_CLOUDFLARE_TUNNEL_STALE_TIMEOUT_SECONDSDEV_CLOUDFLARE_TUNNEL_MISSING_PID_GRACE_SECONDS
Tunnel ownership is lock-guarded across worktrees. If another worktree already owns the lock, pnpm dev continues but skips tunnel startup in the current worktree.
Check current lock owner/status:
pnpm dev:tunnel:statusIf you specifically want browser preview, run pnpm dev:mobile:web so Expo serves the app intentionally for web.
7. Open local dev views (optional)
pnpm dev:openRun this after pnpm dev is up. It opens local app/dev dashboards (web, mobile Metro status, API Swagger, Temporal UI, Grafana, Prometheus, Jaeger) in Brave Browser with a Google Chrome fallback.
Verifying the setup
After starting all services, verify everything works:
# Check API health
curl http://localhost:4000/health
# Verify API Swagger endpoint
curl -f http://localhost:4000/docs > /dev/null
# Verify OpenAPI JSON endpoint
curl -f http://localhost:4000/openapi.json > /dev/null
# Open web app
open http://localhost:3000
# Open API Swagger UI
open http://localhost:4000/docs
# Check Temporal CLI runtime
pnpm temporal:dev:status
# Open Grafana dashboards
open http://localhost:3001Rebuilding contracts
When you change the API, regenerate the OpenAPI spec and client hooks:
# Generate OpenAPI spec from API routes
pnpm openapi:generate
# Regenerate web + mobile API client hooks
pnpm api:client:generateRunning quality checks
# Quick checks (reduced output for agent workflows)
pnpm lint:quiet
pnpm type-check:quiet
pnpm test:quiet
# Full output (for debugging failures)
pnpm lint
pnpm type-check
pnpm test
# Integration tests (resets test DB, requires running infra)
pnpm test:integrationDocker is infra-only
In local development, Docker runs only infrastructure services. Applications run as native Node.js processes for fast hot-reload. In staging and production, applications are deployed as container images built by pnpm deploy:build-images.