Execute Supabase production deployment checklist and rollback procedures. Use when deploying Supabase integrations to production, preparing for launch, or implementing go-live procedures. Trigger...
Actionable 14-step checklist for taking a Supabase project to production, based on Supabase's official production guide. Each step below carries its verification checklist inline; the full SQL, TypeScript, and CLI commands for every step live in references/step-commands.md.
@supabase/supabase-js v2+ installednpx supabase --version)Work top to bottom. Every checkbox must be satisfied before go-live. Each step names the commands to run; copy them from references/step-commands.md.
RLS is the single most critical production requirement. Without it, any client with your anon key can read/write every row. Start with the audit query — it must return zero rows before going live:
-- Find tables WITHOUT RLS enabled (must return zero rows before launch)
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public' AND rowsecurity = false;
Then ALTER TABLE ... ENABLE ROW LEVEL SECURITY and add per-command policies —
full CREATE POLICY patterns in step-commands.md.
USING (true) without intent (public read tables only)The anon key is safe for client-side code. The service_role key bypasses RLS
entirely and must never leave server-side environments. See the two-client setup
in step-commands.md.
NEXT_PUBLIC_ prefix)grep -r "service_role" dist/)Supabase uses Supavisor for pooling. Serverless functions (Vercel, Netlify, Cloudflare Workers) MUST use the pooled connection string (port 6543) to avoid exhausting the database connection limit — direct connections (port 5432) are for migrations and admin tasks only. Connection strings and client config in step-commands.md.
transaction for serverless, session for long-lived connectionsSupabase provides automatic daily backups on Pro plan. Point-in-time recovery (PITR) enables granular restores.
supabase/migrations/ directory)npx supabase db push tested against a fresh project to verify migrations replay cleanlyRestrict database access to known IP addresses. This prevents unauthorized direct database connections even if credentials leak.
A custom domain replaces the default *.supabase.co URLs with your brand domain
for API and auth endpoints.
Default Supabase auth emails show generic branding. Customize them so users see your domain and brand.
Supabase enforces rate limits that vary by plan. Hitting these in production causes 429 errors.
| Resource | Free | Pro | Team |
|---|---|---|---|
| API requests | 500/min | 1,000/min | 5,000/min |
| Auth emails | 4/hour | 30/hour | 100/hour |
| Realtime connections | 200 concurrent | 500 concurrent | 2,000 concurrent |
| Edge Function invocations | 500K/month | 2M/month | 5M/month |
| Storage bandwidth | 2GB/month | 250GB/month | Custom |
| Database size | 500MB | 8GB | 50GB |
Supabase provides built-in monitoring. Review these before launch to establish baselines, and deploy a health check endpoint (full route handler in step-commands.md).
Edge Functions run on Deno Deploy. Set environment variables via the Supabase CLI or Dashboard, not hardcoded. Secret commands and a webhook function template in step-commands.md.
npx supabase functions deploy)npx supabase secrets set (not hardcoded)SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY available automatically (no need to set)npx supabase functions serve locally before deployingStorage buckets need explicit policies, similar to RLS on tables. Without policies, buckets are inaccessible (default deny). Inspection queries and example policies in step-commands.md.
file_size_limit in bucket config)allowed_mime_types)auth.uid() to prevent overwritesMissing indexes are the leading cause of slow queries after launch. Add indexes on foreign keys, filter columns, and sort columns. Diagnostic queries (missing-index, slow-query, table-bloat) and index DDL in step-commands.md.
pg_stat_statements enabled for ongoing query monitoringstatement_timeout set for authenticated role to prevent runaway queriesnpx supabase db pushAll schema changes must go through migration files, never manual Dashboard edits in production. Migration commands in step-commands.md.
supabase/migrations/ directory (version controlled)npx supabase db push tested against a fresh projectnpx supabase migration list)Run the final linked-project verification commands in step-commands.md, then confirm:
supabase-load-scale)npx supabase db pushCommon go-live failures and their fixes. Full catalog (with HTTP status codes,
alert thresholds, and a Supabase error-code switch handler) in
references/errors.md.
| Issue | Cause | Solution |
|---|---|---|
403 Forbidden on all API calls |
RLS enabled but no policies created | Add SELECT/INSERT/UPDATE/DELETE policies for each role |
429 Too Many Requests |
Plan rate limit exceeded | Upgrade plan or implement client-side backoff with retry |
| Connection timeout under load | Using direct connection in serverless | Switch to pooled connection string (port 6543) |
| Auth emails not delivered | Default SMTP rate-limited | Configure custom SMTP provider (SendGrid, Resend, Postmark) |
PGRST301 permission denied |
Service role key used where anon expected | Check client initialization — use anon key for client-side |
| Storage upload fails | Missing bucket policy or size limit exceeded | Add INSERT policy and check file_size_limit on bucket |
| Slow queries after launch | Missing indexes on filter/join columns | Run Performance Advisor and add indexes per Step 12 |
| Migration conflicts | Manual Dashboard edits diverged from migration files | Run npx supabase db diff to capture drift, then commit |
Lean patterns below; complete, copy-paste versions (Next.js client setup, health check endpoint, full RLS policy set, storage policies, Edge Functions, rollback) in references/examples.md.
ALTER TABLE public.posts ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Public read published" ON public.posts
FOR SELECT USING (status = 'published');
CREATE POLICY "Authors manage own" ON public.posts
FOR ALL USING (auth.uid() = author_id)
WITH CHECK (auth.uid() = author_id);
npx supabase migration new rollback_bad_change # Create reversal SQL
npx supabase db push # Apply rollback
# For data: Dashboard > Database > Backups > PITR
# For app: vercel rollback / netlify deploy --prod
supabase-upgrade-migrationsupabase-load-scalesupabase-monitoringsupabase-edge-functions