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):
- Starts the stack (
docker-compose.local.yml): Postgres+pgvector, LocalStack (S3), cognito-local. - Waits for Postgres.
- Applies the Supabase-compat bootstrap (
local-stack/postgres/00-supabase-compat.sql— see below). - Runs all migrations (
npm run db:migrate) + a post-migration enum sync. - 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:
- DB →
127.0.0.1:5434(notlocalhost— see troubleshooting). - S3 →
S3_ENDPOINT=http://localhost:4566(LocalStack). Setting this var is what switcheslib/storage/s3.tsinto local path-style mode. - Cognito →
COGNITO_ENDPOINT=http://localhost:9229(cognito-local). Pool + client IDs are filled in bynpm run local:up. - Stripe → test-mode keys only (
sk_test_/pk_test_).npm run devaborts viascripts/check-no-live-keys.ts(thepredevhook) if it finds ansk_livekey. 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.aitraffic.
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 onlocalhost:5432(IPv4 and IPv6) shadows the container. The stack maps host port 5434 and.env.localuses127.0.0.1(notlocalhost) to force IPv4 to the container. If you still hit the native Postgres, confirm.env.localhasDB_HOST=127.0.0.1andDB_PORT=5434.docker composenot found butdocker-composeis.npm run local:uphandles both. If running compose by hand, use whichever your install provides.- Reset everything:
npm run local:reset(drops volumes) thennpm run local:up.
Commands
| Command | Effect |
|---|---|
npm run local:up | Bring up + migrate + provision Cognito |
npm run local:down | Stop containers (keeps data) |
npm run local:reset | Stop + delete volumes (wipes local data) |
npm run dev | Next.js dev server (against the local stack) |