Local development
How to run each Metrica service locally: the backend API, the dashboard, the admin console, the marketing landing and the CV worker.
Port map
Section titled “Port map”Metrica shares this machine with mintveo, which already owns 5432 and 8000.
Metrica therefore uses the +1 ports so the two stacks never collide.
| Service | Port | Notes |
|---|---|---|
| Docker Postgres | 5433 |
container maps 5433:5432 (mintveo owns host 5432) |
| Backend API | 8001 |
mintveo’s backend owns 8000 |
Dashboard (frontend/) |
5183 |
3000/3001 taken by other local tools |
Admin console (admin/) |
5185 |
set in admin/vite.config.ts |
Landing (landing/) |
5190 |
set in landing/vite.config.ts |
Prerequisites
Section titled “Prerequisites”- Docker (for Postgres)
uv— Python env + runner forbackend/andworker/bun— package manager / runner for the JS apps
uv run and bunx use each project’s local environment automatically — you do
not need to source .venv/bin/activate.
1. Database — Docker Postgres on 5433
Section titled “1. Database — Docker Postgres on 5433”The backend expects Postgres on localhost:5433. Bring it up first:
docker compose -f backend/docker-compose.yml up -dThis starts postgres:16 with:
| Setting | Value |
|---|---|
| user / password | postgres / postgres |
| database | retailpulse |
| host port | 5433 (maps to container 5432) |
| volume | retailpulse_pgdata (data persists across restarts) |
Stop it with docker compose -f backend/docker-compose.yml down (add -v to
wipe the data volume).
2. Backend API (port 8001)
Section titled “2. Backend API (port 8001)”Set up env once:
cd backendcp .env.example .envuv syncKey vars in backend/.env:
| Var | Local value |
|---|---|
DATABASE_URL |
postgresql+asyncpg://postgres:postgres@localhost:5433/retailpulse |
SUPABASE_URL / SUPABASE_SECRET_KEY |
blank locally until auth is wired up |
SUPERADMIN_EMAILS |
comma-separated superadmin emails |
WORKER_API_KEY |
shared secret the worker sends as X-Worker-Key — generate with openssl rand -hex 32, must match worker/.env.local |
Run migrations, then start the server:
cd backenduv run alembic upgrade head./scripts/backend.shscripts/backend.sh runs:
uv run uvicorn app.main:app --port 8001 --host 127.0.0.1API docs are then at http://127.0.0.1:8001/docs.
3. Dashboard — frontend/ (port 5183)
Section titled “3. Dashboard — frontend/ (port 5183)”cd frontendcp .env.example .env.localbun install./scripts/frontend.sh # → bunx vite dev --port 5183 --host 127.0.0.1Open http://127.0.0.1:5183/dashboard. Vite auto-loads frontend/.env.local.
VITE_* vars are exposed to the browser, so only put publishable/anon keys
there:
| Var | Local value |
|---|---|
VITE_SUPABASE_URL |
https://<project-ref>.supabase.co |
VITE_SUPABASE_PUBLISHABLE_KEY |
Supabase anon / publishable key |
VITE_API_URL |
http://localhost:8001 |
VITE_POSTHOG_KEY, VITE_SENTRY_DSN, … |
optional analytics / error monitoring |
4. Admin console — admin/ (port 5185)
Section titled “4. Admin console — admin/ (port 5185)”cd admincp .env.example .env.localbun install./scripts/admin.sh # → bunx vite dev --host 127.0.0.1Open http://127.0.0.1:5185/. Env vars:
| Var | Local value |
|---|---|
VITE_SUPABASE_URL |
https://<project-ref>.supabase.co |
VITE_SUPABASE_PUBLISHABLE_KEY |
Supabase anon / publishable key |
VITE_API_URL |
http://localhost:8001 |
VITE_FRONTEND_URL |
http://localhost:5183 (used for invite / deep links) |
5. Landing — landing/ (port 5190)
Section titled “5. Landing — landing/ (port 5190)”./scripts/landing.sh # → bunx vite dev --host 127.0.0.1Open http://127.0.0.1:5190/.
6. CV worker
Section titled “6. CV worker”The worker counts people crossing a virtual line (YOLO + ByteTrack) and POSTs batched events to the backend. It runs against a recorded file or a live RTSP camera.
cd workeruv syncStandalone (no API/DB), straight from the worker dir:
uv run python run.py ../data/samples/people-walking.mp4uv run python run.py ../data/samples/subway.mp4 --save subway_annotated.mp4Wired to the local backend via the helper script:
# Recorded clip (bounded — exits at end of file)./scripts/worker.sh data/samples/people-walking.mp4
# Live RTSP — continuous (flushes events ~every 30 frames)./scripts/worker.sh "rtsp://user:pass@CAM_HOST:554/cam/realmonitor?channel=3&subtype=0"
# Live RTSP — bounded sanity check (stop after N frames)./scripts/worker.sh "rtsp://user:pass@CAM_HOST:554/cam/realmonitor?channel=3&subtype=0" --max-frames 300scripts/worker.sh loads worker/.env.local (for WORKER_API_KEY and optional
overrides) and defaults to:
| Var | Default |
|---|---|
API_URL |
http://localhost:8001 |
STORE_ID |
the seeded dev store UUID |
CAMERA_ID |
the seeded dev camera UUID |
Override any of them inline: STORE_ID=… CAMERA_ID=… API_URL=… ./scripts/worker.sh <SOURCE>.
Related
Section titled “Related”- Deployment — how each app ships to prod
- Operations — migrations, monitoring, incident runbooks
- Testing — running the test suites