Local development stack
The local dev stack is what you’ll iterate against. It’s completely self-contained — no external accounts, no provider API keys required to boot. Same compose file is consulted on every CI run.
Topology
Section titled “Topology”docker-compose.yml — services + profiles
infra (always-on) postgres :5433 → 5432 inside the container redis :6380 → 6379 mailpit :1026 SMTP, :8026 UI minio :9000 S3 API, :9001 console (minioadmin / minioadmin)
init (one-shot, exit cleanly) env-sync — materialises per-app .env files from root .env deps — bun install on the shared workspace volume minio-init — creates the dev bucket migrate — runs Drizzle migrations against postgres
services (profile: full) data-provider :8082 api :3011 worker frontend :5173 (Vite dev server)Bring it up
Section titled “Bring it up”-
Seed the root env file.
Terminal window cp .env.example .envThe defaults work as-is for local development.
-
Boot.
Terminal window bun installbun run dev:stackbun run dev:stackisscripts/sync-env.ts+docker compose --profile full up -d --build. First boot pulls images and builds the app containers. -
Open the SPA.
Terminal window open http://localhost:5173 -
Sign in.
Magic-link emails land in Mailpit. Click the link there.
Bring it down
Section titled “Bring it down”bun run dev:stack:down # remove containers; volumes preservedTo also wipe Postgres / MinIO / Redis data:
docker compose down -vRun apps on the host instead of in containers
Section titled “Run apps on the host instead of in containers”Faster HMR, easier debugger attach, host-side breakpoints — useful when iterating on the api or worker:
docker compose up -d postgres redis mailpit miniobun installbun dev # api + frontend/appbun dev:worker # separate terminalbun dev:data-provider # separate terminalbun run dev:stackPer-app env files
Section titled “Per-app env files”The root .env is the single source of truth. scripts/sync-env.ts
materialises per-app .env files from it on every bun run dev:stack
and on every env-sync container run.
The script:
- Reads root
.env. - For each app under
apps/*/, parses that app’s.env.exampleto discover which keys it cares about. - Writes per-app
.envfiltered to only those keys. - Skips writes when content is unchanged (prevents Vite dev-server restart loops).
Adding a new env var an app needs: append it to that app’s
.env.example and to root .env.example. The script picks it up
automatically.
Service-to-service URLs in compose
Section titled “Service-to-service URLs in compose”The compose file overrides URLs that need a container-network
hostname (postgres, redis, minio, mailpit, data-provider)
on the relevant service’s environment: block. The root .env
versions are for host-side bun dev; the compose overrides take
precedence inside containers.
Default ports
Section titled “Default ports”| Service | Host port |
|---|---|
| Postgres | localhost:5433 (user/db scani / scani) |
| Redis | localhost:6380 |
| Mailpit SMTP | localhost:1026 |
| Mailpit UI | http://localhost:8026 |
| MinIO S3 API | http://localhost:9000 |
| MinIO console | http://localhost:9001 (minioadmin / minioadmin) |
| Data-provider | http://localhost:8082 |
| API | http://localhost:3011 |
| Frontend | http://localhost:5173 |
Common dev tasks
Section titled “Common dev tasks”# Rebuild just one imagedocker compose --profile full up -d --build api
# Tail logs across servicesdocker compose --profile full logs -f api worker data-provider
# Run migrations on demand (when Drizzle generates new ones)docker compose --profile migrate run --rm migrate
# Open a psql shelldocker compose exec postgres psql -U scani scani
# Open a redis-clidocker compose exec redis redis-cli