Environments & Dev Setup
Environments & Dev Setup
How the Groundtruth Platform runs across local, development, and production environments. Start here if you're joining the team.
Quick Start by Role
Not everyone needs the full stack running. Pick your path:
| You are... | What you need | Setup time |
|---|---|---|
| Designer / Frontend | Node.js + Next.js dev server + Supabase (shared dev DB) | ~15 min |
| Full-stack dev | Above + Python 3.12 + engine running locally | ~30 min |
| Infra / DevOps | Above + Supabase branching + Railway environments | ~45 min |
All roles: follow the Local Development guide first, then return here for environment-specific setup.
How Production Works
The platform is three services connected by environment variables:
Internet
|
+---------+---------+
| |
+-----+------+ +------+------+
| Vercel | | Railway |
| Next.js +--->+ Python |
| Web + API | | Engine |
+-----+------+ +------+------+
| |
+---+ +----------+
| |
+-----+----+-----+
| Supabase |
| PostgreSQL + |
| Auth + Storage |
+-----------------+| Service | URL | What it does |
|---|---|---|
| Vercel (Web App) | groundtruth-platform.vercel.app | Next.js frontend, API routes, Stripe webhooks, SSE proxy |
| Railway (Engine) | groundtruth-platform-production.up.railway.app | Python CrewAI engine, engagement runs (10-60 min), LLM dispatch |
| Supabase (Database) | yyhsycogenoqrrsunkxf.supabase.co | PostgreSQL (20 tables), Auth (JWT + SSO), Storage (attachments) |
Supporting services: Stripe (billing), Upstash Redis (caching/rate limiting), Resend (email), Sentry (error tracking).
How a request flows
- Browser hits Vercel (Next.js API route)
- Middleware extracts tenant from JWT cookie or API key
- Route handler queries Supabase PostgreSQL via Prisma (RLS enforces tenant isolation)
- For engagement runs: Vercel calls Railway engine over HTTP, passing
X-Tenant-IDheader - Engine uses
DatabaseStorageAdapterto read/write PostgreSQL directly - Real-time updates flow back via SSE: Engine -> Next.js proxy -> Browser
Why two servers?
Vercel's serverless functions have a 60-second timeout. Engagement runs take 10-60 minutes. The Python engine runs as a persistent Railway service — no timeout.
Environment Matrix
Three environments, each with its own set of service instances:
| Component | Local Dev | Dev Branch | Production |
|---|---|---|---|
| Web App | localhost:3000 | Vercel Preview (auto) | groundtruth-platform.vercel.app |
| Engine | localhost:8000 | Shared dev Railway env | groundtruth-platform-production.up.railway.app |
| Database | Supabase branch (or shared dev) | Supabase branch | Production Supabase |
| Auth | Supabase (shared dev project) | Supabase branch | Production Supabase |
| Storage | Supabase Storage | Supabase branch | Production Supabase |
| Stripe | Test mode (sk_test_) | Test mode (sk_test_) | Live mode (sk_live_) |
| Redis | Optional (or Upstash dev) | Upstash dev instance | Upstash production |
| Console logging | Resend test | Resend production | |
| Sentry | Optional | Dev project | Production project |
Environment Variable Reference
Below is every env var and what value it should have per environment.
Web App (apps/web/.env.local)
| Variable | Local Dev | Dev Branch (Vercel) | Production (Vercel) |
|---|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | Branch/dev project URL | Branch project URL | https://yyhsycogenoqrrsunkxf.supabase.co |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Branch/dev anon key | Branch anon key | Production anon key |
SUPABASE_SERVICE_ROLE_KEY | Branch/dev service key | Branch service key | Production service key |
DATABASE_URL | Branch/dev direct URL | Branch direct URL | Production direct URL |
STRIPE_SECRET_KEY | sk_test_... | sk_test_... | sk_live_... |
STRIPE_WEBHOOK_SECRET | Stripe CLI whsec_... | Stripe test whsec_... | Stripe live whsec_... |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | pk_test_... | pk_test_... | pk_live_... |
RAILWAY_ENGINE_URL | http://localhost:8000 | Shared dev Railway URL | Production Railway URL |
UPSTASH_REDIS_REST_URL | Optional | Dev Upstash URL | Production Upstash URL |
UPSTASH_REDIS_REST_TOKEN | Optional | Dev Upstash token | Production Upstash token |
RESEND_API_KEY | Optional (logs to console) | re_... (test) | re_... (production) |
NEXT_PUBLIC_SENTRY_DSN | Optional | Dev DSN | Production DSN |
SENTRY_AUTH_TOKEN | Optional | Dev token | Production token |
Engine (packages/engine/.env)
| Variable | Local Dev | Dev Railway | Production Railway |
|---|---|---|---|
DATABASE_URL | Branch/dev direct URL | Branch pooler URL (port 6543) | Production pooler URL (port 6543) |
OPENAI_API_KEY | Your key | Shared dev key | Production key |
XAI_API_KEY | Your key | Shared dev key | Production key |
ANTHROPIC_API_KEY | Your key | Shared dev key | Production key |
GOOGLE_API_KEY | Your key | Shared dev key | Production key |
ALLOWED_ORIGINS | http://localhost:3000 | Vercel preview URL | https://groundtruth-platform.vercel.app |
SENTRY_DSN | Optional | Dev DSN | Production DSN |
ENVIRONMENT | development | development | production |
Important: Railway engine on dev uses the Supabase connection pooler URL (port 6543), not the direct connection. This prevents connection exhaustion.
Setting Up a Dev Branch
A "dev branch" means three things happening together:
- Git branch — your feature/bugfix branch
- Vercel Preview — automatic, deployed on every push (no setup needed)
- Supabase Branch — isolated database with its own schema, auth, and storage
Step 1: Create a Git Branch
git checkout -b feature/my-featureStep 2: Supabase Branching (Requires Pro Plan)
Supabase branching gives you an isolated database instance that starts with your production schema (all migrations applied) but no production data. This is ideal for testing migrations and schema changes safely.
Via Supabase Dashboard
- Go to your project at supabase.com
- Click the branch selector (top-left, next to project name)
- Click Create branch
- Name it to match your git branch (e.g.,
feature/my-feature) - Wait for provisioning (~1-2 minutes)
- The dashboard shows the branch's unique credentials:
- Project URL (different from production)
- Anon key (different from production)
- Service role key (different from production)
- Database URL (different from production)
Via Supabase CLI
# Install Supabase CLI if you haven't
brew install supabase/tap/supabase
# Link to your project (one-time)
supabase link --project-ref yyhsycogenoqrrsunkxf
# Create a branch
supabase branches create feature/my-feature
# List branches
supabase branches list
# Get branch credentials
supabase branches get feature/my-featureWhat branches include
- Fresh PostgreSQL instance with all migrations applied
- Separate Auth instance (its own JWT keys)
- Separate Storage instance (empty — no production files)
- No production data — you must seed:
npx prisma db seed
What branches do NOT include
- Production data (tables are empty after migrations)
- Edge Functions (Supabase Edge Functions, not our Railway engine)
- Custom domains
Step 3: Update Your Local .env.local
Point your local dev server at the branch database:
# apps/web/.env.local — update these four values from branch credentials
NEXT_PUBLIC_SUPABASE_URL=https://BRANCH_REF.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...branch-anon-key...
SUPABASE_SERVICE_ROLE_KEY=eyJ...branch-service-key...
DATABASE_URL=postgresql://postgres:PASSWORD@db.BRANCH_REF.supabase.co:5432/postgresStep 4: Seed the Branch Database
The branch has the schema but no data:
cd apps/web
# Apply any new migrations not yet in the branch
npx prisma migrate deploy
# Seed agents and templates
npx prisma db seedStep 5: Vercel Preview (Automatic)
When you push your branch, Vercel automatically creates a preview deployment. The preview URL looks like:
https://groundtruth-platform-<hash>-<team>.vercel.appFor the preview to use your Supabase branch, set branch-specific env vars in Vercel:
- Go to Vercel project Settings > Environment Variables
- For each Supabase variable (
NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_ANON_KEY, etc.), add an override scoped to your Preview environment - Or use Vercel's Git branch-based env var overrides
Tip: For quick local testing, just update
.env.local. Vercel preview env var overrides are only needed when you want the deployed preview to also use the branch database.
Step 6: Configure Auth Redirects
Add your local and preview URLs to Supabase Auth redirect allowlist (on the branch):
- In the Supabase branch dashboard, go to Authentication > URL Configuration
- Add:
http://localhost:3000/auth/callbackhttps://groundtruth-platform-<hash>.vercel.app/auth/callback
Step 7: Railway Engine (Shared Dev)
For most development work, the engine isn't needed (UI, API routes, billing all run on Vercel). When you do need it:
Option A: Run locally (recommended for engine work)
cd packages/engine
source venv/bin/activate
python api.py # Runs on localhost:8000Set RAILWAY_ENGINE_URL=http://localhost:8000 in .env.local.
Option B: Shared dev Railway environment (for team-wide testing)
- Create a second Railway environment named
development - Deploy the
developbranch to it - Set its
DATABASE_URLto the Supabase branch connection pooler - Share the URL with the team as
RAILWAY_ENGINE_URLfor dev
Git Workflow
feature/xyz ──push──> Vercel Preview ──> Supabase Branch
| (isolated DB)
|
├── PR review + preview testing
|
v
main ──auto-deploy──> Vercel Production ──> Supabase Production
(live data)Branch Naming
| Pattern | Purpose |
|---|---|
feature/short-description | New features |
fix/short-description | Bug fixes |
docs/short-description | Documentation |
refactor/short-description | Code restructuring |
Merge Checklist
Before merging to main:
-
npm run buildpasses locally (no type errors) - Tests pass:
npm test -- --run - If schema changed: migration file committed (
npx prisma migrate dev --name descriptive_name) - Supabase branch tested (seed + migrate + basic smoke test)
- Preview deployment loads and key flows work
- PR reviewed
After Merge
- Vercel auto-deploys to production
- If there are new Prisma migrations, they apply automatically via the Vercel build command (
npx prisma migrate deploy && next build— configured invercel.jsonor Vercel dashboard) - Delete the Supabase branch (dashboard or
supabase branches delete feature/xyz)
Working Without the Engine
If you're working on frontend, API routes, billing, or settings — you likely don't need the Python engine running. The web app works independently for:
- All dashboard pages and navigation
- Settings (billing, team, branding, API keys, webhooks, SSO)
- Engagement creation and editing
- Template management
- Client portal
- Analytics dashboard
You only need the engine for:
- Starting engagement runs (the "Run" button)
- Viewing live run progress (Mission Control SSE stream)
- Testing deliverable generation
Without the engine, the "Start Run" button will show an error, but everything else works normally.
Supabase Branch Lifecycle
Create branch ──> Develop + test ──> Merge migrations ──> Delete branch
|
Seed data
Test migrations
Validate schemaHandling Migration Drift
If production gets new migrations while your branch is active:
# Rebase your branch to pick up production migrations
supabase branches rebase feature/my-feature
# Or reset (destroys branch data, re-applies all migrations)
supabase branches reset feature/my-featureCost
Supabase branches are included in the Pro plan ($25/month). Each branch consumes compute while active. Delete branches you're not using.
Troubleshooting
"Cannot reach engine" on dev branch
Your RAILWAY_ENGINE_URL is probably still pointing at production or is unset. For local dev, set it to http://localhost:8000 and run the engine locally. If you don't need the engine, ignore this — the web app works fine without it.
Auth redirects failing on preview
The Supabase branch needs your preview URL in its redirect allowlist. Go to the branch's Authentication > URL Configuration and add the preview URL with /auth/callback.
Empty database after creating branch
Expected. Supabase branches apply migrations (schema) but don't copy production data. Run npx prisma db seed to populate agents and templates.
"Prisma Client out of date" after switching branches
Regenerate the client:
cd apps/web
npx prisma generateBranch database won't connect
Check that you're using the branch's credentials (URL, anon key, service key), not production's. Each branch has its own unique connection string.
Related Documentation
- Local Development — detailed setup for running locally
- Architecture Overview — system design and data flow
- Deployment Guide — deploying to production
- Engine Architecture — Python engine module breakdown
- Testing Guide — test framework and patterns