Skip to main content
Version: 2.2.1 Last Updated: 2026-04-24 Based on: Supabase Official Documentation This guide provides recommendations for configuring supabase/config.toml to support four environments:
  • Local - Local CLI development (Docker)
  • Dev - Persistent dev project (anuwknikgsijbameytzr); per-PR preview DBs created by Branching from PRs targeting GitHub prod
  • Staging (Persistent) - UAT and pre-prod validation
  • Production - Live application (aximlomrwfuhctxjhrsf); per-PR preview DBs created by Branching only for release PRs targeting GitHub production
Authoritative branch ↔ Vercel ↔ Supabase mapping: see VERCEL_SUPABASE_ENV_ALIGNMENT.md. When that document and this one disagree, the alignment doc wins.
2026-04 reconcile: the legacy production project (anuwknikgsijbameytzr) was repurposed as Dev. Two new clean projects were created from a single optimized baseline migration. See SUPABASE_RECONCILE_2026-04.md for the cutover record.
As of 2026-04-22 — source of truth and deploy path
  • Dev (anuwknikgsijbameytzr, “Encore OS”): Lovable + Supabase MCP apply migrations and deploy Edge Functions here first; this database is the live schema reference for day-to-day work.
  • Git repo (supabase/migrations/, supabase/functions/): canonical history for review and for production rollout.
  • Production (aximlomrwfuhctxjhrsf, “encore_os_prod”): Supabase Branching (GitHub integration) replays migrations for release PRs and, with Deploy to production ON, applies them on merge to production. CI still runs supabase db push --yes from .github/workflows/supabase-deploy-prod.yml on the same push as a reconcile with the repo. Break-glass: manual workflow_dispatch on that workflow with skip_db_push when migrations were already fully applied elsewhere (document in your ops log).
Do not point ad-hoc db push at Dev for schema promotion; treat Dev as MCP-driven. Use GitHub prod → optional .github/workflows/supabase-deploy-dev.yml backstop, and production → prod.

Bidirectional sync (Dev ↔ repo)

Two paths keep Dev and the repo aligned:
  • Repo → Dev (push): Author routine DDL declaratively under supabase/schemas/, then run npm run db:schemas:diff -- -f <slug> (wraps supabase stop + supabase db diff -f <slug>) to emit the migration in supabase/migrations/. Reserve supabase migration new for caveat migrations (DML, certain ALTER POLICY / view ownership / grant / comment cases — see DECLARATIVE_SCHEMA_GUIDE.md and MIGRATION_LANES.md). The migrations land on the Dev project when supabase-deploy-dev.yml runs on its branch trigger (see workflow table below).
  • Dev → repo (pull): Migrations applied directly to Dev by Lovable / Supabase MCP are captured back into supabase/migrations/ by supabase-sync-from-dev.yml. It runs daily at 06:00 UTC and on-demand via workflow_dispatch. The workflow runs supabase db diff --linked -f <slug> to produce a real timestamped migration, validates it with db reset + the RLS / FK / lane guards, refreshes the declarative tree under supabase/schemas/ via npm run db:schemas:export when bootstrapped (so the repo’s source of truth stays current), regenerates src/integrations/supabase/types.ts, and opens a review PR to dev. Developers can run the same flow locally with npm run db:pull-from-dev -- --name <slug>.

Promotion path

Release PRs (prodproduction): Require green Supabase Preview on encore_os_prod before merge. Order of operations and branch protection are documented in VERCEL_SUPABASE_ENV_ALIGNMENT.md section 2.3. CLI spike notes: BRANCHING_CI_SPIKE.md. Optional persistent staging (separate Supabase project) can sit between Dev and Prod when provisioned; there is no shared staging ref in config.toml until you add one.
This approach uses Supabase’s [remotes] configuration blocks to define environment-specific settings. This is the recommended approach per Supabase documentation.

Option 2: Environment Variable-Based (Alternative)

If you prefer to switch environments via environment variables:
Then use different .env files:
  • .env.localSUPABASE_PROJECT_ID=local-dev
  • .env.stagingSUPABASE_PROJECT_ID=<staging-ref>
  • .env.productionSUPABASE_PROJECT_ID=aximlomrwfuhctxjhrsf
Note: Option 1 is preferred because it provides better separation and allows environment-specific configurations.

Workflow by Environment

Local Development

Purpose: Feature development, migration testing, rapid iteration Setup:
Commands:
Configuration: Uses default [api], [db] settings in config.toml

Staging (Persistent UAT)

Purpose: User acceptance testing, integration testing, pre-production validation Setup:
Deploying to Staging:
Configuration: Uses [remotes.staging] block from config.toml Best Practices:
  • Keep staging schema in sync with production
  • Use synthetic test data (no PHI/PII)
  • Test all migrations in staging before production
  • Use staging for UAT sign-offs

Production

Purpose: Live application serving real users Canonical mapping: GitHub branch production, project ref aximlomrwfuhctxjhrsf — see VERCEL_SUPABASE_ENV_ALIGNMENT.md. Setup:
Deploying to Production:
Configuration: Uses [remotes.production] block from config.toml Best Practices:
  • NEVER deploy untested migrations to production
  • Always deploy to staging first
  • Use production for verified, tested changes only
  • Monitor deployments closely
  • Have rollback plan ready

Environment Switching Workflow

Alternative: Manual Linking


Configuration Options Reference

API Configuration

Database Configuration

Remote-Specific Overrides


Secrets Management

Local Development

Secrets are loaded from .env file in project root:
Reference in config.toml:

Remote Environments (Staging/Production)

Set secrets via CLI:
Important: Secrets are project-specific. You must set them separately for staging and production.

Secrets requiring manual setup

Some edge functions stay dormant until their secret is set in the project vault. They are deploy-gated by design — the function code ships, but it returns 503 (or otherwise no-ops) until the secret exists. Set these per environment with supabase secrets set (or via the dashboard → Edge Functions → Secrets):

Migration Workflow

CI/CD Integration

For automated deployments, use GitHub Actions with environment-specific secrets:

Best Practices

1. Always Test Locally First

  • Use supabase db reset to test migrations from scratch
  • Verify migrations are idempotent (can be run multiple times safely)

2. Staging Before Production

  • NEVER deploy directly to production
  • Always validate in staging first
  • Use staging for UAT sign-offs

3. Environment Isolation

  • Keep production and staging completely separate
  • Never use production data in staging
  • Use synthetic test data in staging

4. Configuration as Code

  • Keep config.toml in version control
  • Use [remotes] blocks for environment-specific settings
  • Document any manual configuration changes

5. Secrets Management

  • Never commit secrets to git
  • Use .env files for local development (gitignored)
  • Use supabase secrets set for remote environments
  • Rotate secrets regularly

6. Migration Safety

  • Always review migrations before deploying
  • Test rollback procedures
  • Use transactions where possible
  • Avoid breaking changes when possible

Troubleshooting

Issue: Wrong Project Linked

Symptom: supabase db push deploys to wrong environment Solution:

Issue: Config Not Applied

Symptom: Remote-specific config not taking effect Solution:
  • Verify project_id in [remotes.*] matches actual project ID
  • Use supabase config push to sync config to remote
  • Check that you’re linked to correct project

Issue: Secrets Not Available

Symptom: Edge functions can’t access secrets Solution:

Migration from Current Setup

Current State

  • config.toml has single project_id = "<STAGING_PROJECT_REF>" (staging)
  • Manual switching between environments
  1. Backup current config:
  2. Update config.toml:
    • Add default local configuration
    • Add [remotes.production] block
    • Add [remotes.staging] block
    • Keep current staging project_id
  3. Test local development:
  4. Test staging link:
  5. Test production link:
  6. Update documentation:
    • Update docs/development/ENVIRONMENT_CONFIG.md with new workflow
    • Document switching process for team

Database connections for scripts (pooler, not direct)

Supabase’s direct Postgres hostname (db.<project-ref>.supabase.co) is often IPv6-only. Many local networks and CI runners only resolve IPv4, which surfaces as ENOTFOUND or connection timeouts when using pg, psql, or pg_dump. Use the Session pooler for any script that opens a raw Postgres connection from a laptop or CI:
  • Dashboard → Project SettingsDatabaseConnection stringSession mode (Supavisor, port 5432).
  • URI shape: postgresql://postgres.<PROJECT_REF>:<PASSWORD>@aws-0-<REGION>.pooler.supabase.com:5432/postgres
This repo’s seed tooling reads:
  • SUPABASE_DEV_DB_URL — dev (anuwknikgsijbameytzr), pooler URI.
  • SUPABASE_PROD_DB_URL — prod (aximlomrwfuhctxjhrsf), pooler URI.
Keep these in .env.local only (gitignored). See .env.local.example.

Applying the global system-defaults bundle to prod

  1. Regenerate supabase/seeds/system-defaults/00_global_system_defaults.sql (npm run seed:extract-system-defaults or --source rest).
  2. Prefer recording the load in migration history: in Cursor, use the Supabase MCP apply_migration tool against aximlomrwfuhctxjhrsf with migration name seed_global_system_defaults_bundle and query set to the file contents (same SQL as the SQL Editor path).
  3. Fallback: paste the file into the prod project SQL Editor and run.
  4. Fresh production project (empty / first-time): run the manual GitHub Actions workflow Bootstrap Supabase (production) (see workflow table below). It runs supabase db push --include-all, then supabase db query --linked --file on 00_global_system_defaults.sql, then optional npm run check:prod-seed-parity when SUPABASE_PROD_DB_URL is configured as a secret.

Migration lanes (schema vs system data)

New migrations must follow the lane rules in MIGRATION_LANES.md so CI can block dev-only URLs, JWTs in SQL, and non-idempotent catalog inserts after the strict cutoff timestamp.

Ongoing parity check

npm run check:prod-seed-parity compares prod catalog row counts to dev (if SUPABASE_DEV_DB_URL is set) or to documented minimum baselines. It skips when SUPABASE_PROD_DB_URL is unset (e.g. CI without secrets). Set that variable in CI to enforce the gate in automation.

GitHub Actions (Supabase deploy + drift)

Workflows live under .github/workflows/: Repository secrets (Actions → Secrets and variables → Actions): db diff --linked requires Docker on the runner; if the drift job logs a CLI/Docker failure, fix runner setup or run the diff locally.

References


Summary

Recommended Approach: Use [remotes] configuration blocks in config.toml to define environment-specific settings. This provides: ✅ Clear separation between environments
✅ Environment-specific configuration overrides
✅ Version-controlled configuration
✅ Easy switching via supabase link
✅ Support for different settings per environment
Workflow:
  1. Local: supabase start (uses default config), then supabase db reset when testing migrations.
  2. Dev (Encore OS): anuwknikgsijbameytzr — primary schema changes via Lovable + MCP; optional GitHub prod branch workflow mirrors repo → Dev.
  3. Production (encore_os_prod): aximlomrwfuhctxjhrsf — promote via GitHub production branch (CI) or manual supabase link + db push + functions deploy following SUPABASE_RECONCILE_2026-04.md.
This approach aligns with Supabase best practices and provides a scalable, maintainable multi-environment setup.