Skip to main content

Bring up the local stack (+ optionally exercise a change)

Gets a developer from nothing to a running, DB-connected local stack: sandbox infra (Mongo, MySQL, Redis, LocalStack) and the NestJS services. It is idempotent โ€” it inspects what is already up and starts only what is missing, and never fails just because something is already running. It is also the hands-on counterpart to /verify (static gate: lint/typecheck/test/build): once the stack is up you can exercise a change end-to-end (step 6).

verify-integration-local (in backend-services) builds on this skill. Discover machine-specific values (ports) at runtime; do not hardcode.

0. Prerequisiteโ€‹

  • Docker Desktop running (docker info succeeds). On macOS: open -a Docker and wait.

1. Infra โ€” sandboxโ€‹

bewith sandbox status # already healthy? then skip the next line
bewith sandbox up # only if infra is not already up

bewith sandbox up is not a no-op โ€” it kills and rebuilds the containers every run. MySQL and Mongo data survive (host bind-mounts ./mysql8, ./mongodata8); Redis is reset. So gate it on status and run up only when the stack is not already healthy.

Containers started by up: mongodb8-dev, mysql8-dev, redis-dev, localstack-dev, php-nginx-dev. (The infra MCP infra-mcp-dev is not part of up โ€” it is gated behind the compose mcp profile and started on demand by bewith sandbox mcp register.)

2. Discover the Mongo host port + align .env (do not hardcode)โ€‹

The sandbox Mongo container maps to a host port that can differ from the committed .env.

docker port mongodb8-dev 27017 # -> 0.0.0.0:<HOST_PORT> (often 27018)

Make sure backend-services .env MONGODB_URI uses <HOST_PORT> before starting the services. Creds: root / mypass, authSource=admin, DB dev.

3. Verify DB connectivity (communication)โ€‹

docker exec mongodb8-dev mongosh "mongodb://root:mypass@localhost:27017/dev?authSource=admin" --quiet --eval 'db.runCommand({ ping: 1 }).ok' # -> 1
docker exec mysql8-dev mysql -uroot -pmypass ebdb -N -e "SELECT 1;" # -> 1
docker exec redis-dev redis-cli ping # -> PONG

From the host, also confirm Mongo answers on <HOST_PORT> (this is what the apps use).

3b. Apply pending migrations (prevents MySQL schema drift)โ€‹

The MySQL ebdb dump can lag the codebase, so group creation fails with Unknown column '...'. Apply pending migrations non-interactively before starting services โ€” it is idempotent (only runs migrations not yet applied):

docker exec php-nginx-dev php yii migrate/up --interactive=0

This is the real fix for schema drift; a one-off ALTER TABLE is only a fallback when a specific migration can't run locally.

Empty ebdb (clean machine): bewith sandbox up (CLI โ‰ฅ 0.1.17) auto-loads a committed schema baseline before this step, so migrate/up here is a no-op (or applies only genuinely new deltas) instead of failing on Table 'ebdb.organizations' doesn't exist. The active migrations are deltas over that baseline โ€” there's no external dump to fetch; see sandbox/db/baseline/.

4. Start the NestJS services (headless "Debug-Watch All Services")โ€‹

This replaces clicking VSCode โ†’ Run and Debug โ†’ "Debug-Watch All Services". The helper script (a backend-services dev tool at scripts/start-local-services.mjs) reads .vscode/launch.json, starts each app's program with ts-node as a background process, logs to logs/<app>.log, and waits for the gateways.

.vscode/launch.json is gitignored โ€” if it is missing (fresh checkout/worktree), copy it from the committed .vscode/launch.template.json first (the template defines the Debug-Watch All Services / Debug-Watch Selected Services compounds the script reads).

# from the backend-services working tree whose code you want under test (apps run the cwd's source)
node scripts/start-local-services.mjs
  • Idempotent: if the gateways (INTERNAL_GATEWAY_PORT 8082, EXTERNAL_GATEWAY_PORT 8083) are already listening, it does nothing.
  • Subset: pass a comma list to start fewer apps. The minimal set for an import (DB-level verify) is: node scripts/start-local-services.mjs external-gateway,internal-gateway,integrations,groups,communities,vector-search,address-provider,auth,authorization,organizations,user (include address-provider โ€” google-maps locations are geocoded there).
  • For a UI check, start the FULL compound (no subset arg). The consumer event-grid's resources/available read path needs more than the import subset โ€” options, user-communities, user-groups, and organizations-stats as well โ€” and returns 500 if any are missing. The import subset only saves boot time for a DB-only run; when you also want the browser check, run everything.
  • Runs from process.cwd() โ€” run it from the branch under test (the apps execute that source).
  • Requires .env with the correct Mongo port (step 2).

5. Verify services are up + connectedโ€‹

lsof -nP -iTCP:8082 -sTCP:LISTEN && lsof -nP -iTCP:8083 -sTCP:LISTEN # gateways listening
grep -lE "successfully started|Nest application" logs/*.log # apps booted
grep -liE "econnrefused|MongoServerError|ER_ACCESS|unable to connect" logs/*.log # connection errors (should be empty)

Tail a service's log to confirm it connected to Mongo/MySQL: tail -f logs/integrations.log.

6. (Optional) Exercise a change end-to-endโ€‹

Once the stack is healthy, verify a change actually behaves โ€” the reason to run locally instead of trusting /verify alone.

  • Frontend (if the change is UI):
    cd ~/workspace/management-webapp && npm run dev # :8091, NEXT_PUBLIC_BACKEND_V2_URL=http://localhost:8083/api/v2
    # or bewith-consumer-frontend: npm run dev (:3000) โ€” set the local backend URL
    For auth, mint a local JWT via the permissions-demo-tokens skill (demo user per role).
  • Exercise + verify: hit the touched endpoint(s) โ€” prefer a saved Postman request (postman-endpoint) or curl โ€” and confirm the response. For SQS flows, the schedule โ†’ wait for the every-minute cron โ†’ check history pattern applies; start sqs-consumer (npm run start:dev) if the change touches notifications. For UI, drive the actual screen (states: default/loading/empty/error/success).
  • Report: what was exercised, the observed result, and anything that couldn't be verified locally.

Needs a human (surface, never fake)โ€‹

  • Secrets in .env (AWS keys, JWT secret, encryption keys) โ€” copy from the team template / vault; never paste into chat or a committed file.
  • Data dumps โ€” seed SQL/Mongo from s3://bewith-dev-dumps/โ€ฆ (needs AWS creds) or a dump from a senior dev (sandbox/README.md). An empty DB boots but most flows need data.
  • Port conflicts (3306/6379/27017/8082-8084/8091) โ€” detect and report; don't kill processes blindly.

Boundariesโ€‹

  • Verifies, doesn't ship. No push/PR/merge โ€” that's the normal flow / /continue.
  • Honest results only. If infra won't come up or a secret/dump is missing, say exactly what's blocked โ€” never report "verified" on a partial run.
  • Test strategy (what to cover, unit vs integration vs e2e) is qa-engineer; this skill executes a local run.

Stopโ€‹

  • Services: /kill-service-ports, or test -s logs/local-services.pids && awk '{print $1}' logs/local-services.pids | xargs kill.
  • Infra: bewith sandbox down.

Gotchasโ€‹

  • Mongo host port is dynamic โ€” discover via docker port mongodb8-dev 27017; the committed .env may be stale (27017 vs 27018). Apps and scripts must use the live port.
  • Services run the cwd's source โ€” start from the branch under test, or you will run stale code.
  • Running from a git worktree โ€” copy the gitignored files first. A fresh worktree lacks .env and .vscode/launch.json (both gitignored). Without .env the services boot with production defaults and crash at startup โ€” getaddrinfo ENOTFOUND redis (RedisService), SQS queueUrl is required for SqsTransportStrategy, JwtStrategy requires a secret or key (and environment: production in the logs). Copy both from the main checkout before running the script: cp <main>/.env <worktree>/.env and cp <main>/.vscode/launch.json <worktree>/.vscode/launch.json. (The start script preflights these and fails fast with this message.) The external-gateway also loads its SSL cert relative to __dirname (../../../../developer-kit/nginx-selfsigned.{key,crt}); from a worktree that resolves to <repo>/.claude/worktrees/developer-kit/ โ€” create that dir and copy the certs from the main repo's developer-kit/, or external-gateway crashes on ENOENT and never binds 8083.
  • Adding one service while the stack is up: the script's idempotency only skips when both gateways are already listening; to start a single missing microservice (e.g. address-provider) while gateways are up, run it directly: node -r ts-node/register -r tsconfig-paths/register apps/<app>/src/main.ts &.
  • ts-node boot is slow for many apps; the first start of the full compound takes a while. A curated subset starts faster.
  • DB reads can be done directly via docker exec ... mongosh / ... mysql if the local DB MCP (sandbox/infra-mcp) is not registered in the session (MCP servers register at session start).

Scriptsโ€‹

  • backend-services/scripts/start-local-services.mjs โ€” headless equivalent of "Debug-Watch All Services" (parses .vscode/launch.json, spawns apps with ts-node, health-checks gateways, idempotent). Lives in backend-services because it is repo-specific (its launch.json + apps). Optional first arg = comma-separated subset of app names; override the compound with the COMPOUND env var.

Cross-referencesโ€‹