This guide explains how to set up and run BetterFit locally with Supabase and the iOS simulator.
- Xcode 17.0+
- macOS with Docker support (for Supabase containers)
miseinstalled (see mise.toml)- iOS 26.2 simulator runtime
mise installInstalls Supabase CLI, SwiftLint, and other build tools.
mise run supabase:startStarts PostgreSQL, Auth, Storage, and Studio in Docker containers.
Output includes:
Studio: http://127.0.0.1:54323
API: http://127.0.0.1:54321
Mailpit: http://127.0.0.1:54324
mise run supabase:resetCreates all persistence tables (workouts, templates, plans, etc.) with Row Level Security.
mise run supabase:configureThis single command:
- Populates
.envwith credentials fromsupabase status - Regenerates Xcode project with XcodeGen
- Injects environment variables from
.envinto Xcode scheme
What gets set:
SUPABASE_URL=http://127.0.0.1:54321
SUPABASE_ANON_KEY=sb_publishable_ACJWl...
SUPABASE_SECRET_KEY=sb_secret_N7UND0U...Why this approach?
- Credentials stay in
.env(git-ignored), never committed to version control - No hardcoded values in
project.ymlor scheme files - Each developer's credentials are local to their machine
- Script automatically updates scheme when credentials change
# Generate Xcode project and open it
mise run ios:open
# Or just build for simulator
mise run ios:build:devNote: ios:open and ios:gen automatically inject credentials from .env into the scheme after generation.
The runtime flow:
- Xcode scheme provides
SUPABASE_URLandSUPABASE_ANON_KEYas environment variables - App reads via
ProcessInfo.processInfo.environment AppConfiguration→EnvironmentLoaderloads credentials- App initializes
AuthServiceandBetterFitwith Supabase client
In the simulator:
- Tap "Sign in with Email" → enter any email/password (6+ chars for new accounts)
- Tap "Continue as Guest" → offline mode
- Tap "Sign in with Apple" → simulated approval
Supabase running locally
↓
scripts/setup_local_env.sh (reads supabase status)
↓
.env file (git-ignored, never committed)
↓
scripts/update_scheme_env.sh (injects into Xcode scheme XML)
↓
BetterFit.xcscheme (local file, credentials injected post-generation)
↓
ProcessInfo.processInfo.environment (runtime)
↓
AppConfiguration.swift (via EnvironmentLoader)
↓
AuthService, BetterFit initialization
Key principle: Credentials never committed to version control
project.ymlhas no hardcoded values (only declares scheme structure).envis git-ignored- Scheme files have credentials injected locally via script
- Each developer runs
mise run supabase:configureto set up their environment
In mise.toml:
[tasks."supabase:configure"]
run = """
bash scripts/setup_local_env.sh # Populate .env
cd Apps/iOS && xcodegen generate # Generate Xcode project
bash ../../scripts/update_scheme_env.sh # Inject .env into scheme
"""In Sources/BetterFit/Services/AppConfiguration.swift:
public init() {
let rawSupabaseURL = EnvironmentLoader.get("SUPABASE_URL")
let supabaseAnonKey = EnvironmentLoader.get("SUPABASE_ANON_KEY")
// ... validation
}mise run supabase:statusOpen http://127.0.0.1:54323 in browser
- Browse tables, rows, functions
- Manage auth users and tokens
- View storage buckets
Open http://127.0.0.1:54324 in browser
- View all emails sent by Supabase auth
- Useful for testing confirmation emails
mise run supabase:resetRuns all migrations from supabase/migrations/ and seeds from supabase/seed.sql (if present).
mise run supabase:migrateApplies only new migrations without resetting data.
mise run supabase:stopStops all containers but preserves data.
mise run supabase:stop
rm -rf supabase/.temp supabase/.branches # Remove local state
mise run supabase:start
mise run supabase:envmise run supabase:status
# If not running:
mise run supabase:start# Create from template
cp .env.example .env
# Or auto-populate
mise run supabase:env- Verify
.envexists and has credentials - Regenerate Xcode project:
mise run ios:gen - Clean build folder:
Cmd+Shift+Kin Xcode - Rebuild:
mise run ios:build:dev
- Check
AppConfiguration.primaryWarningfor validation errors - Verify
SUPABASE_URL=http://127.0.0.1:54321(not https) - Confirm Supabase is running:
mise run supabase:status - In simulator, network can reach localhost via special host alias
- Check Mailpit http://127.0.0.1:54324 for error emails
- Verify email format (must be valid address)
- Check
supabase/config.tomlemail settings
- Database: PostgreSQL 15 on port 54322
- Auth: GoTrue (Supabase auth service) on port 54321
- Storage: S3-compatible storage on port 54321/storage/v1/s3
- Studio: Web UI on port 54323
- Mailpit: Email capture/testing on port 54324
- AuthService: Uses Supabase Swift client
- Persistence: SupabasePersistenceService for cloud sync (authenticated users)
- Local Storage: LocalPersistenceService using UserDefaults (guest mode)
- Configuration: AppConfiguration validates env vars at startup
- Fallback: Guest mode available if Supabase not configured
Tables created by migrations in supabase/migrations/:
| Table | Purpose |
|---|---|
workouts |
Completed/in-progress workout sessions |
workout_templates |
Reusable workout templates |
training_plans |
Multi-week training programs |
user_profiles |
User profile + social data |
body_map_recovery |
Muscle recovery tracking |
streak_data |
Workout streak tracking |
All tables use Row Level Security (RLS) - users can only access their own data.
See docs/api.md for API integration details.
- Read docs/auth.md for authentication setup details
- Check docs/api.md for data persistence examples
- Review AGENTS.md for code organization practices