Environment setup
Configure local, device, and production environments for Claire.
Claire runs across three environments. The difference between them is entirely which API and Supabase origins the clients point at.
| Environment | API server | Supabase | When to use |
|---|---|---|---|
| local-sim | localhost:3001 | localhost:8000 | Simulator on your Mac |
| local-device | <lan-ip>:3001 | <lan-ip>:8000 | Physical phone on the same WiFi |
| production | Your deployed API origin | Supabase or a self-hosted gateway | TestFlight and App Store |
Supabase#
The Supabase stack runs locally in Docker (docker/supabase/). That is enough for the iOS simulator at localhost:8000. A deployed server or a physical device needs Supabase to have a reachable public URL.
Provision a small VPS
Any Ubuntu 22.04 host works. A Hetzner CAX11 (2 GB ARM) is around €4/month and is more than enough.
Copy the stack across
scp -r docker/supabase/ root@YOUR_VPS_IP:/opt/claire-supabase/ scp -r supabase/migrations/ root@YOUR_VPS_IP:/opt/claire-supabase/migrations/ ssh root@YOUR_VPS_IP cd /opt/claire-supabase && docker compose -f docker-compose.supabase.yml up -dApply migrations
psql postgresql://postgres:postgres@localhost:5432/postgres \ -f /opt/claire-supabase/migrations/20250806092049_initial_schema.sqlRepeat for the remaining migration files in order. Supabase is then reachable at
http://YOUR_VPS_IP:8000.
A tunnel exposes your local Supabase publicly without provisioning anything. It only works while your machine is online.
ngrok http 8000Point your deployment at the resulting URL. Supabase keys live in docker/supabase/.env, or under Project Settings → API in the dashboard.
railway variables set \
SUPABASE_URL="https://xxxx.ngrok-free.app" \
SUPABASE_ANON_KEY="<your-supabase-anon-key>" \
SUPABASE_SERVICE_KEY="<your-supabase-service-role-key>"Migrating to hosted Supabase#
Dump the local data, apply the schema to the target, then restore.
docker exec supabase-db pg_dump -U postgres -d postgres \
--data-only --no-owner \
-t messages -t chats -t contacts -t users -t sessions \
> /tmp/claire_data.sql
CLOUD_DB="postgresql://postgres:YOUR_PW@db.YOUR_REF.supabase.co:5432/postgres"
psql "$CLOUD_DB" -f supabase/migrations/20250806092049_initial_schema.sql
psql "$CLOUD_DB" < /tmp/claire_data.sqlExpo environment switching#
.env.example Template — copy to .env.local (committed)
.env.local Your device overrides — never commit (gitignored)
.env.production Production keys — never commit (gitignored)
eas.json EAS build profilesExpo loads these in ascending priority: .env, then .env.local, then .env.development or .env.production.
Simulator on a Mac#
mobile/.env already points at localhost, so there is nothing to configure.
bunx expo run:iosPhysical device on WiFi#
Find your machine’s LAN address with ipconfig getifaddr en0 — it changes — and write it into mobile/.env.local.
EXPO_PUBLIC_API_URL=http://<your-lan-ip>:3001
EXPO_PUBLIC_SUPABASE_URL=http://<your-lan-ip>:8000
EXPO_PUBLIC_SUPABASE_ANON_KEY=...
EXPO_PUBLIC_ENV=developmentThe server already listens on 0.0.0.0:3001, so it needs no change.
bunx expo run:ios --deviceProduction build#
The production profile in eas.json sets EXPO_PUBLIC_API_URL; the Supabase values come from .env.production.
eas build --profile preview --platform ios
eas build --profile production --platform iosQuick reference#
# What env vars does the app actually see?
cd mobile && bunx expo config --type introspect | grep EXPO_PUBLIC
# Run the API against your local .env
cd server && bun run --watch src/index.ts
# Tail deployment logs
railway logs --lines 50