# Deployment Guide — CourtMitra MissionOS > **Target:** Vercel (frontend) + Hugging Face Spaces (backend + worker) + Google OAuth (Clerk) + Neon (Postgres) --- ## Architecture Overview ``` ┌─────────────────────────┐ ┌──────────────────────────────────────────┐ │ Vercel (Next.js 15) │────────▶│ Hugging Face Spaces (Docker) │ │ - Clerk auth │ HTTPS │ - NestJS API + Inngest Worker │ │ - Hero page │ │ - Port 7860 │ │ - Profile page │ │ │ │ - Mission dashboard │ │ /api/* → API routes │ │ - Static assets │ │ /api/inngest → Worker functions │ └─────────────────────────┘ └────────────────────┬─────────────────────┘ │ │ postgres://... ▼ ┌────────────────────────────┐ │ Neon Postgres (Cloud) │ │ - 55 tables │ │ - pgvector + FTS │ │ - Legal chunks seeded │ └────────────────────────────┘ ``` --- ## Env Files (Where Secrets Live) > **Crucial for your partner:** There are TWO separate env files. One is gitignored (real secrets), the other is a committed template. | File | In Git? | Where | Purpose | |------|---------|-------|---------| | `apps/web/.env.local` | ❌ **NO** — gitignored | Your laptop / partner's laptop | **Frontend local dev** — Clerk keys + API URL. You MUST create this and fill in real keys. | | `apps/web/.env.local.example` | ✅ YES | In repo as template | Shows exactly what `.env.local` should contain. | | `.env` | ❌ **NO** — gitignored | Your laptop / partner's laptop | **Backend local dev** — DB, AI, Inngest, secrets. Copy from `.env.example` and fill in. | | `.env.example` | ✅ YES | In repo as template | Comprehensive backend env template. | **What your partner needs to do before `pnpm dev`:** 1. Create `apps/web/.env.local` (copy from `apps/web/.env.local.example`) 2. Paste in `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY` from Clerk dashboard 3. Create `.env` at repo root (copy from `.env.example`) 4. Fill in `DATABASE_URL`, `OPENROUTER_API_KEY`, etc. --- ## Prerequisites | Service | Purpose | Free Tier | |---------|---------|-----------| | [Vercel](https://vercel.com) | Frontend hosting | Hobby (unlimited) | | [Clerk](https://clerk.com) | Auth + Google OAuth | 10,000 MAU | | [Hugging Face Spaces](https://huggingface.co/spaces) | Backend API + Worker | CPU Basic (free) | | [Neon](https://neon.tech) | Postgres database | 500 MB, 200 hrs | | [Inngest](https://inngest.com) | Background workflows | 50k events/mo | | [OpenRouter](https://openrouter.ai) | LLM API | $5 free credits | --- ## Step 1: Clerk + Google OAuth (10 min) 1. Go to [dashboard.clerk.com](https://dashboard.clerk.com) → Create Application. 2. **Social Connections** → enable **Google**. - Development: toggle on (uses Clerk's shared OAuth credentials). - Production: add your own Google Client ID/Secret. 3. Copy keys from **API Keys** tab: - `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...` - `CLERK_SECRET_KEY=sk_test_...` 4. Add your Vercel domain to **URLs** → **Allowed Origins**: - `https://your-project.vercel.app` - `http://localhost:3000` (for local dev) --- ## Step 2: Neon Postgres (10 min) 1. [neon.tech](https://neon.tech) → New Project → copy **connection string**. 2. Run migrations from your local machine: ```bash export DATABASE_URL="postgres://courtmitra:password@host.neon.tech/courtmitra" export DIRECT_DATABASE_URL="$DATABASE_URL" pnpm db:migrate pnpm db:seed ``` 3. Verify: check that `users`, `missions`, `route_rules`, `legal_chunks` tables exist. --- ## Step 3: Frontend → Vercel (5 min) 1. Push repo to GitHub. 2. Vercel → **Add New Project** → import repo. 3. Set **Environment Variables**: | Variable | Value | Notes | |----------|-------|-------| | `NEXT_PUBLIC_API_BASE_URL` | `https://your-hf-space.hf.space/api` | Your HF Spaces URL | | `INTERNAL_API_BASE_URL` | `https://your-hf-space.hf.space/api` | Same as above | | `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` | `pk_test_...` | From Clerk dashboard | | `CLERK_SECRET_KEY` | `sk_test_...` | From Clerk dashboard | | `NEXT_PUBLIC_CLERK_SIGN_IN_URL` | `/en/sign-in` | Or `/hi/sign-in` | | `NEXT_PUBLIC_CLERK_SIGN_UP_URL` | `/en/sign-up` | Or `/hi/sign-up` | 4. Deploy. --- ## Step 4: Backend → Hugging Face Spaces (20 min) ### 4a. Create Space 1. [huggingface.co/spaces](https://huggingface.co/spaces) → **Create new Space**. 2. **Space name:** `courtmitra-api` 3. **SDK:** `Docker` 4. **Visibility:** Public 5. **Hardware:** Free CPU ### 4b. Link GitHub Repo In Space Settings → **Repository** → link your GitHub repo. The `Dockerfile.hf` at repo root builds the monorepo and runs API + Worker in one container. ### 4c. Set Environment Variables In Space → **Settings** → **Variables & Secrets**: #### Required (App won't start without these) ``` PORT=7860 API_PORT=7860 DATABASE_URL=postgres://courtmitra:password@host.neon.tech/courtmitra DIRECT_DATABASE_URL=postgres://courtmitra:password@host.neon.tech/courtmitra APP_BASE_URL=https://your-hf-space.hf.space API_BASE_URL=https://your-hf-space.hf.space WEBHOOK_BASE_URL=https://your-hf-space.hf.space JWT_SECRET=change-me-32-chars-minimum!! PII_HASH_SALT=change-me-salt TOKEN_ENCRYPTION_KEY=change-me-32-chars-minimum!! ``` #### Required for AI (Intake, route-plan, evidence parsing) ``` AI_PROVIDER=openrouter OPENROUTER_API_KEY=sk-or-v1-... AI_MODEL=google/gemini-2.0-flash-exp:free AI_VISION_MODEL=google/gemini-2.0-flash-exp:free ``` #### Clerk (for JWT verification) ``` CLERK_SECRET_KEY=sk_test_... # Optional: for networkless JWT verification (faster, no Clerk API call per request) CLERK_JWT_KEY=... # Optional: restrict which domains can use your API CLERK_AUTHORIZED_PARTIES=https://your-project.vercel.app,http://localhost:3000 ``` #### Optional (Email actions — Resend) ``` RESEND_API_KEY=re_... RESEND_FROM=noreply@yourdomain.com ``` #### Optional (Inngest background workflows) ``` INNGEST_EVENT_KEY=... INNGEST_SIGNING_KEY=... INNGEST_BASE_URL=https://api.inngest.com ``` #### Optional (Local dev helper — NOT for production) ``` DEMO_MODE=true ``` > ⚠️ **Never set `DEMO_MODE=true` in production.** It bypasses all auth. #### Optional (Object storage — for evidence files) ``` OBJECT_STORAGE_ENDPOINT=https://... OBJECT_STORAGE_BUCKET=courtmitra OBJECT_STORAGE_ACCESS_KEY=... OBJECT_STORAGE_SECRET_KEY=... ``` > If not set, evidence files are stored locally in `/app/.storage` (lost on Space restart). #### Optional (Other channels) ``` WHATSAPP_VERIFY_TOKEN=... WHATSAPP_APP_SECRET=... WHATSAPP_ACCESS_TOKEN=... WHATSAPP_PHONE_NUMBER_ID=... TELEGRAM_BOT_TOKEN=... ``` #### Optional (Phase 2 features — not needed for hackathon) ``` QDRANT_URL=https://... QDRANT_API_KEY=... QDRANT_COLLECTION=courtmitra_legal OPENAI_API_KEY=... BROWSERBASE_API_KEY=... BROWSERBASE_PROJECT_ID=... SUREPASS_API_KEY=... ENABLE_PORTAL_AUTOMATION=1 ``` ### 4d. Verify Once built, test: ```bash curl https://your-space.hf.space/api/admin/health # Expected: {"status":"ok","ts":"..."} curl https://your-space.hf.space/api/capabilities # Expected: JSON with adapter capabilities curl https://your-space.hf.space/api/inngest # Expected: Inngest registration JSON ``` --- ## Step 5: Wire Frontend → Backend 1. In Vercel dashboard, update: ``` NEXT_PUBLIC_API_BASE_URL=https://your-hf-space.hf.space/api ``` 2. Redeploy frontend. --- ## Complete Environment Variable Reference ### Vercel (Frontend) | Variable | Required | Description | |----------|----------|-------------| | `NEXT_PUBLIC_API_BASE_URL` | ✅ | HF Spaces API URL | | `INTERNAL_API_BASE_URL` | ✅ | Same as above (for SSR) | | `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` | ✅ | Clerk public key | | `CLERK_SECRET_KEY` | ✅ | Clerk secret key | | `NEXT_PUBLIC_CLERK_SIGN_IN_URL` | ✅ | `/en/sign-in` | | `NEXT_PUBLIC_CLERK_SIGN_UP_URL` | ✅ | `/en/sign-up` | ### Hugging Face Spaces (Backend) | Variable | Required | Description | |----------|----------|-------------| | `PORT` | ✅ | Must be `7860` | | `API_PORT` | ✅ | Must be `7860` | | `DATABASE_URL` | ✅ | Neon connection string | | `DIRECT_DATABASE_URL` | ✅ | Same as `DATABASE_URL` | | `APP_BASE_URL` | ✅ | HF Spaces URL | | `API_BASE_URL` | ✅ | Same | | `WEBHOOK_BASE_URL` | ✅ | Same | | `JWT_SECRET` | ✅ | Min 32 chars | | `PII_HASH_SALT` | ✅ | Any string | | `TOKEN_ENCRYPTION_KEY` | ✅ | Min 32 chars | | `AI_PROVIDER` | ✅ | `openrouter` or `opencode` | | `OPENROUTER_API_KEY` | ✅ | If `AI_PROVIDER=openrouter` | | `AI_MODEL` | ✅ | e.g. `google/gemini-2.0-flash-exp:free` | | `AI_VISION_MODEL` | ✅ | e.g. `google/gemini-2.0-flash-exp:free` | | `CLERK_SECRET_KEY` | ✅ | Same as Vercel | | `CLERK_JWT_KEY` | ⬜ | Optional, for faster JWT verification | | `RESEND_API_KEY` | ⬜ | For email actions | | `RESEND_FROM` | ⬜ | Sender email | | `INNGEST_EVENT_KEY` | ⬜ | For background workflows | | `INNGEST_SIGNING_KEY` | ⬜ | For background workflows | | `LOCAL_STORAGE_DIR` | ⬜ | Defaults to `/app/.storage` | | `DEMO_MODE` | ⬜ | `true` = bypass auth (dev only!) | --- ## Quick Smoke Test After deploying both services: 1. Open Vercel URL → click **Sign In** → choose **Google**. 2. After signing in, you should see your avatar in the top-right. 3. On the hero page, click **Get Started** → create a mission. 4. Visit `/{locale}/profile` → you should see: - Your Google name + email + avatar - Editable phone number - Language preference dropdown - Your mission listed under "My Missions" 5. The API calls should show `200 OK` in browser DevTools Network tab with `Authorization: Bearer ` header. --- ## For Your Partner: Quick Local Dev Setup **Before they can run `pnpm dev`, they MUST create two env files:** ### 1. Frontend env: `apps/web/.env.local` Create this file (it's gitignored, so it won't exist after clone): ```bash cp apps/web/.env.local.example apps/web/.env.local ``` Then paste in your Clerk keys: ``` NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_... # from Clerk dashboard CLERK_SECRET_KEY=sk_test_... # from Clerk dashboard NEXT_PUBLIC_CLERK_SIGN_IN_URL=/en/sign-in NEXT_PUBLIC_CLERK_SIGN_UP_URL=/en/sign-up NEXT_PUBLIC_API_BASE_URL=http://localhost:3001/api INTERNAL_API_BASE_URL=http://localhost:3001/api ``` ### 2. Backend env: `.env` (at repo root) ```bash cp .env.example .env ``` Fill in at minimum: ``` DATABASE_URL=postgres://courtmitra:courtmitra@localhost:5432/courtmitra DIRECT_DATABASE_URL=postgres://courtmitra:courtmitra@localhost:5432/courtmitra OPENROUTER_API_KEY=sk-or-v1-... AI_PROVIDER=openrouter AI_MODEL=google/gemini-2.0-flash-exp:free AI_VISION_MODEL=google/gemini-2.0-flash-exp:free CLERK_SECRET_KEY=sk_test_... JWT_SECRET=change-me-32-chars-minimum!! PII_HASH_SALT=change-me-salt TOKEN_ENCRYPTION_KEY=change-me-32-chars-minimum!! ``` ### 3. Run it ```bash # Terminal 1: start Postgres + Inngest pnpm infra:up # Terminal 2: migrate and seed pnpm db:migrate pnpm db:seed # Terminal 3: run all apps pnpm dev ``` - Web: http://localhost:3000 - API: http://localhost:3001/api - Inngest Dev: http://localhost:8288 **No Clerk keys yet?** Add `DEMO_MODE=true` to `.env` to bypass auth for local testing. --- ## Local Development ```bash # 1. Start local Postgres + Inngest pnpm infra:up # 2. Copy env and fill in keys cp .env.example .env # Edit .env with your keys # 3. Run everything pnpm dev ``` - Web: http://localhost:3000 - API: http://localhost:3001/api - Inngest Dev Server: http://localhost:8288 **For local dev without Clerk keys**, add to `.env`: ``` DEMO_MODE=true ``` --- ## Troubleshooting ### HF Space build fails - Check **Factory Logs** in HF dashboard. - Common: `pnpm` not found → Dockerfile installs via `corepack`. - Common: out of disk → free tier has ~50GB. Dockerfile strips `next`/`react` packages. ### API returns 401 Unauthorized - Make sure `CLERK_SECRET_KEY` is set in HF Space. - Make sure frontend sends `Authorization: Bearer ` header (check DevTools). - If testing with curl, you need a valid Clerk JWT token. ### API returns 500 - Check HF **Application Logs**. - Common: `DATABASE_URL` not set → can't connect to Neon. - Common: `OPENROUTER_API_KEY` not set → AI endpoints fail. - Common: `JWT_SECRET` too short → must be ≥32 characters. ### CORS errors from frontend - API has `app.enableCors({ origin: true })` which reflects the request origin. - If still blocked, add your Vercel domain explicitly in `apps/api/src/main.ts`. ### Clerk sign-in fails - Verify `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY` are set in Vercel. - In Clerk Dashboard → **Social Connections** → **Google** must be enabled. - Add your Vercel domain to Clerk Dashboard → **URLs** → **Allowed Origins**. ### Background workflows not running - HF free-tier Spaces **sleep after inactivity**. When the Space sleeps, Inngest can't invoke your worker. - **Fix:** Use a paid HF Space (Always On), or use Inngest's `serve` with a keep-alive ping. - Alternatively: trigger workflows manually via API for demo purposes. ### Database connection errors - Neon databases have a **connection limit**. If you see "too many clients", restart the HF Space. - Use Neon's **connection pooling** (add `?pgbouncer=true` to connection string). --- ## Known Limitations | Feature | Status | |---------|--------| | Google OAuth + user profiles | ✅ Works | | Mission CRUD (user-scoped) | ✅ Works | | Evidence upload | ✅ Works (if storage configured) | | Background AI workflows | ⚠️ Needs Inngest + always-on Space | | Email sending | ⚠️ Needs Resend key | | WhatsApp/Telegram | ❌ Needs Meta Business verification (days) | | Portal automation | ❌ Phase 2 only | --- ## Security Checklist Before going live: - [ ] `DEMO_MODE` is NOT set (or set to `false`) - [ ] `JWT_SECRET` is ≥32 random characters - [ ] `TOKEN_ENCRYPTION_KEY` is ≥32 random characters - [ ] `CLERK_JWT_KEY` is set (optional but recommended for performance) - [ ] Database uses SSL (Neon does this by default) - [ ] HF Space is set to **private** if you don't want public API access - [ ] Vercel domain is in Clerk's **Allowed Origins** --- *Good luck at the hackathon! 🚀*