Skip to main content

Local Development (Docker stack)

npm run dev runs against a local Docker stack — Postgres, S3, and Cognito emulators — not production. This is the NQU-867 isolation work: before it, a local npm run dev read and wrote the live RDS, the live S3 bucket, and the prod Cognito pool, with live Stripe keys in .env.local. That is no longer the case.

Why this matters. The "dev"-named AWS infra is production (one RDS, one ECS, serving app.nquiry.ai). A laptop pointed at it could charge real cards and mutate live data. The local stack removes that blast radius. (Bedrock is the one exception — see Known limitations.)

Prerequisites

A Docker runtime. On macOS, license-free colima:

brew install colima docker docker-compose
colima start

Docker Desktop also works. Verify with docker ps.

One command

npm run local:up

This (scripts/local-dev-up.sh):

  1. Starts the stack (docker-compose.local.yml): Postgres+pgvector, LocalStack (S3), cognito-local.
  2. Waits for Postgres.
  3. Applies the Supabase-compat bootstrap (local-stack/postgres/00-supabase-compat.sql — see below).
  4. Runs all migrations (npm run db:migrate) + a post-migration enum sync.
  5. Provisions the cognito-local user pool, app client, and a seed user, writing the generated IDs into .env.local.

Then:

npm run dev
# open http://localhost:3000/login
# sign in: test@nquiry.ai / TestPassword123!

Logging in once provisions your DB user + personal org automatically (the same createUserInDatabase path production uses).

Sample data

After logging in, click "Load Sample Investigation" on the dashboard to populate a demo investigation locally. (A provenance-faithful seed that pushes evidence through the real ingest/embedding pipeline is tracked as a follow-up.)

Configuration

Copy .env.local.example to .env.local if you don't have one. The committed example is the source of truth for the local layout. Key rules:

  • DB127.0.0.1:5434 (not localhost — see troubleshooting).
  • S3S3_ENDPOINT=http://localhost:4566 (LocalStack). Setting this var is what switches lib/storage/s3.ts into local path-style mode.
  • CognitoCOGNITO_ENDPOINT=http://localhost:9229 (cognito-local). Pool + client IDs are filled in by npm run local:up.
  • Stripetest-mode keys only (sk_test_/pk_test_). npm run dev aborts via scripts/check-no-live-keys.ts (the predev hook) if it finds an sk_live key. Live keys live ONLY in deployed Secrets Manager.

Known limitations

  • cognito-local emulates the basic admin auth flow (email + password). It does not emulate MFA (SOFTWARE_TOKEN_MFA), WebAuthn/passkeys, or refresh-token rotation (GetTokensFromRefreshToken). Local login is password-only; those flows must be tested against real Cognito.
  • Bedrock has no local emulator. AI calls (analysis, embeddings) still use ambient AWS credentials against the real, account-shared Bedrock quota until NQU-865 Phase 3. Do not run load-bearing AI/eval scripts locally without Joe's sign-off — they compete with live app.nquiry.ai traffic.

The Supabase-compat bootstrap

The schema migrations originated on Supabase and assume objects the managed platform supplied out-of-band — the auth/storage schemas, auth.users, public.users, the auth.uid()/role()/email() RLS helpers, the Supabase roles, and update_updated_at_column(). The live RDS carries these from its Supabase origin; a fresh Postgres does not, so the committed migration set cannot apply cleanly without them. 00-supabase-compat.sql supplies them, and 01-post-migrate.sql works around a migration-drift bug (two migrations ALTER TYPE audit_action_type, a type no migration creates — they meant audit_action). This fresh-DB integrity gap is tracked on NQU-865 for the staging stand-up.

Troubleshooting

  • role "app_admin" does not exist / connecting to the wrong DB. A native Homebrew Postgres on localhost:5432 (IPv4 and IPv6) shadows the container. The stack maps host port 5434 and .env.local uses 127.0.0.1 (not localhost) to force IPv4 to the container. If you still hit the native Postgres, confirm .env.local has DB_HOST=127.0.0.1 and DB_PORT=5434.
  • docker compose not found but docker-compose is. npm run local:up handles both. If running compose by hand, use whichever your install provides.
  • Reset everything: npm run local:reset (drops volumes) then npm run local:up.

Commands

CommandEffect
npm run local:upBring up + migrate + provision Cognito
npm run local:downStop containers (keeps data)
npm run local:resetStop + delete volumes (wipes local data)
npm run devNext.js dev server (against the local stack)