DEV Community

Cover image for How to Use Supabase CLI: The Complete Developer Guide (2026)
Wanda
Wanda

Posted on • Originally published at apidog.com

How to Use Supabase CLI: The Complete Developer Guide (2026)

TL;DR

Supabase CLI lets you run a full Supabase stack locally using Docker: PostgreSQL, Auth, Storage, and Edge Functions. Install it with brew install supabase/tap/supabase, run supabase init and supabase start to spin up your environment, then use supabase db push and supabase functions deploy to ship to production. This is the fastest way to develop and test Supabase backends without relying on the cloud.

Try Apidog today

Introduction

73% of backend bugs are caught in production because developers skip local testing. With Supabase CLI, you can set up a production-like environment on your machine in under 5 minutes.

The real challenge: most teams either test in production (risky) or waste hours configuring inconsistent local setups. Supabase CLI solves this with a Docker-based stack that exactly mirrors production, ensuring what works locally will work after deployment.

💡 Tip: If you’re building APIs on Supabase, use Apidog to design, test, and document endpoints as you develop. Apidog connects directly to Supabase’s REST and GraphQL APIs for efficient local testing.

Test your Supabase APIs with Apidog - free

By the end of this guide, you’ll be able to:

  • Set up a complete local Supabase environment in minutes
  • Manage schema changes with version-controlled migrations
  • Build and test Edge Functions locally before deploying
  • Deploy to production with a single command

Why Local Supabase Development Breaks Without the CLI

If you’ve tried building a Supabase app without the CLI, you’ve likely hit these issues:

The “test in production” trap.

Making schema changes in the dashboard works—until a teammate pulls the repo and their local DB doesn’t match.

The environment mismatch.

Manually recreating schemas locally leads to subtle bugs, especially with Row Level Security (RLS) policies.

The “works on my machine” problem.

Edge Functions might behave differently in production if you don’t test with real environment variables locally.

Supabase CLI fixes these problems:

  • Migrations keep schema changes version-controlled and reproducible
  • The local Docker stack matches production (PostgreSQL version, RLS engine)
  • Local function serving tests Edge Functions with real environment variables

How Supabase CLI Works

The Local Stack

Running supabase start launches a Docker Compose stack with the following services:

Service Port Purpose
PostgreSQL 54322 Your database
PostgREST 54321 Auto-generated REST API
GoTrue 54321/auth Authentication service
Realtime 54321/realtime WebSocket subscriptions
Storage 54321/storage File storage
Studio 54323 Visual dashboard
Inbucket 54324 Email testing (catches all emails)
Edge Runtime 54321/functions Deno-based function runner

This setup mirrors the Supabase Cloud stack—locally.

Installation

macOS:

brew install supabase/tap/supabase
Enter fullscreen mode Exit fullscreen mode

Windows (Scoop):

scoop bucket add supabase https://github.com/supabase/scoop-bucket.git
scoop install supabase
Enter fullscreen mode Exit fullscreen mode

Linux / npm:

npm install -g supabase
Enter fullscreen mode Exit fullscreen mode

Verify installation:

supabase --version
# supabase 1.x.x
Enter fullscreen mode Exit fullscreen mode

Note: Docker Desktop must be running before you use supabase start. Otherwise, you’ll see errors about the Docker daemon.

Project Setup

mkdir my-project && cd my-project
supabase init
Enter fullscreen mode Exit fullscreen mode

This creates:

supabase/
├── config.toml       # Ports, auth settings, storage config
├── seed.sql          # Dev data loaded on every db reset
└── migrations/       # Schema version history
Enter fullscreen mode Exit fullscreen mode

Starting the Local Stack

supabase start
Enter fullscreen mode Exit fullscreen mode

First run downloads ~1GB of Docker images. Subsequent starts take ~10 seconds.

Example output:

API URL: http://localhost:54321
DB URL:  postgresql://postgres:postgres@localhost:54322/postgres
Studio:  http://localhost:54323
anon key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Enter fullscreen mode Exit fullscreen mode

Copy the anon key into your .env.local file for frontend access.

Database Management with Migrations

Track every schema change as a versioned SQL file in Git—no more “mystery changes.”

Creating Your First Migration

supabase migration new create_posts_table
# Creates: supabase/migrations/20260324120000_create_posts_table.sql
Enter fullscreen mode Exit fullscreen mode

Edit your migration file:

-- Create posts table with RLS from the start
CREATE TABLE posts (
  id          UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id     UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
  title       TEXT NOT NULL,
  content     TEXT,
  published   BOOLEAN DEFAULT false,
  created_at  TIMESTAMPTZ DEFAULT NOW(),
  updated_at  TIMESTAMPTZ DEFAULT NOW()
);

-- Enable Row Level Security
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- Anyone can read published posts
CREATE POLICY "Anyone can read published posts"
  ON posts FOR SELECT
  USING (published = true);

-- Users manage their own posts
CREATE POLICY "Users manage own posts"
  ON posts FOR ALL
  USING (auth.uid() = user_id);

-- Auto-update updated_at on every change
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = NOW();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER posts_updated_at
  BEFORE UPDATE ON posts
  FOR EACH ROW EXECUTE FUNCTION update_updated_at();
Enter fullscreen mode Exit fullscreen mode

Apply migrations:

supabase migration up
Enter fullscreen mode Exit fullscreen mode

Generating TypeScript Types

After each schema change, regenerate types:

supabase gen types typescript --local > src/types/database.ts
Enter fullscreen mode Exit fullscreen mode

Example usage:

import { Database } from '@/types/database'

type Post = Database['public']['Tables']['posts']['Row']
type NewPost = Database['public']['Tables']['posts']['Insert']

const createPost = async (post: NewPost) => {
  const { data, error } = await supabase
    .from('posts')
    .insert(post)
    .select()
    .single()
  return data
}
Enter fullscreen mode Exit fullscreen mode

Seeding Development Data

Edit supabase/seed.sql:

-- Test users (bypasses auth for local dev)
INSERT INTO auth.users (id, email) VALUES
  ('00000000-0000-0000-0000-000000000001', 'alice@example.com'),
  ('00000000-0000-0000-0000-000000000002', 'bob@example.com');

-- Test posts
INSERT INTO posts (user_id, title, content, published) VALUES
  ('00000000-0000-0000-0000-000000000001', 'Getting started with Supabase', 'Here is what I learned...', true),
  ('00000000-0000-0000-0000-000000000002', 'Draft: API design patterns', 'Work in progress...', false);
Enter fullscreen mode Exit fullscreen mode

Reset and reseed:

supabase db reset
Enter fullscreen mode Exit fullscreen mode

This drops everything, runs migrations, and loads seed data.

Testing Supabase APIs with Apidog

Once Supabase is running locally, your REST API is at http://localhost:54321. Supabase auto-generates endpoints for every table via PostgREST.

Manual testing with curl is tedious, especially for RLS. Apidog streamlines this:

  • Save requests as reusable collections
  • Test endpoints as different users by switching environments
  • Add assertions (e.g., “response contains at least 1 post”) and run as a test suite
  • Share API docs with your team automatically

Setup Apidog with Local Supabase:

  1. Create a new project in Apidog
  2. Set base URL: http://localhost:54321
  3. Add environment variable: anon_key = your-local-anon-key
  4. Add Authorization header: Bearer {{anon_key}}

Test the posts endpoint:

GET http://localhost:54321/rest/v1/posts?published=eq.true
Authorization: Bearer {{anon_key}}
apikey: {{anon_key}}
Enter fullscreen mode Exit fullscreen mode

Save this request in Apidog, add an assertion, and rerun every time you tweak RLS policies.

Start testing your Supabase APIs with Apidog

Edge Functions: Build and Test Locally

Edge Functions run on Deno at the edge. They’re ideal for webhooks, background jobs, and server-side API endpoints.

Create a Function

supabase functions new send-welcome-email
Enter fullscreen mode Exit fullscreen mode

Creates: supabase/functions/send-welcome-email/index.ts

import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'

serve(async (req) => {
  const { user_id } = await req.json()

  // Service role bypasses RLS - use carefully
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
  )

  const { data: profile } = await supabase
    .from('profiles')
    .select('email, full_name')
    .eq('id', user_id)
    .single()

  // Your email sending logic here
  console.log(`Sending welcome email to ${profile?.email}`)

  return new Response(
    JSON.stringify({ success: true }),
    { headers: { 'Content-Type': 'application/json' } }
  )
})
Enter fullscreen mode Exit fullscreen mode

Test Locally with Hot Reload

supabase functions serve
Enter fullscreen mode Exit fullscreen mode

The server watches for file changes. Test it:

curl -X POST http://localhost:54321/functions/v1/send-welcome-email \
  -H "Authorization: Bearer YOUR_ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"user_id": "00000000-0000-0000-0000-000000000001"}'
Enter fullscreen mode Exit fullscreen mode

Deploy to Production

# Deploy one function
supabase functions deploy send-welcome-email

# Deploy all functions
supabase functions deploy
Enter fullscreen mode Exit fullscreen mode

Advanced Techniques and Proven Approaches

Secrets Management

Never hardcode API keys. Use CLI secrets:

# Set production secrets
supabase secrets set RESEND_API_KEY=re_xxx STRIPE_KEY=sk_live_xxx

# List all secrets
supabase secrets list

# Remove a secret
supabase secrets unset STRIPE_KEY
Enter fullscreen mode Exit fullscreen mode

Access in functions:

const resendKey = Deno.env.get('RESEND_API_KEY')
// Never: const resendKey = 're_xxx'
Enter fullscreen mode Exit fullscreen mode

Database Branching

Isolate big schema changes:

supabase branches create feature-payments
supabase branches switch feature-payments

# Make changes, test, then merge
supabase branches merge feature-payments
Enter fullscreen mode Exit fullscreen mode

Common Mistakes to Avoid

  • Editing the database directly in Studio: Always use migrations.
  • Committing .env files: Use supabase secrets set for production and add .env* to .gitignore.
  • Skipping supabase db reset after pulling: Reset to apply new migrations locally.
  • Not regenerating types after schema changes: Run type generation after every migration.
  • Deploying functions without local testing: Always run supabase functions serve and test.
  • Using service role key in frontend code: Only use in Edge Functions/server-side code.

Performance Tips

# Exclude services you don't need
supabase start --exclude-studio --exclude-inbucket

# Monitor resource usage
docker stats
Enter fullscreen mode Exit fullscreen mode

Alternatives and Comparisons

Feature Supabase CLI Firebase CLI PlanetScale CLI
Local database Full PostgreSQL Emulator only Cloud only
Migrations SQL files in Git No native support Branching
Edge Functions Deno runtime Cloud Functions Not included
Auth locally Full GoTrue Emulator Not included
Open source Fully open Proprietary Proprietary
Type generation Built-in Manual Manual

Firebase's emulator is fast for prototyping but lacks a real PostgreSQL DB. PlanetScale excels at branching but is cloud-only. Supabase CLI is best if you want open-source, PostgreSQL-native local development.

Real-World Use Cases

SaaS application with multi-tenant data:

A fintech startup manages 47 migrations across dev/staging/prod. RLS policies are tested locally with different roles. Result: zero schema-related production incidents in six months.

E-commerce order processing:

An e-commerce team uses Edge Functions for Stripe webhook processing, testing payloads locally with supabase functions serve and real Stripe test events. Deployment time drops from 2 hours to 15 minutes.

Mobile app backend:

A React Native team generates TypeScript types after every migration and shares via internal npm package. Frontend and backend always in sync; no more “what fields does this endpoint return?”

Wrapping Up

You can now:

  • Set up a complete local Supabase environment in minutes
  • Use migrations to version-control schema changes
  • Test Edge Functions locally with hot reload
  • Generate TypeScript types from your schema
  • Deploy with supabase db push and supabase functions deploy
  • Test APIs with Apidog before shipping

This workflow helps teams ship faster, catch bugs earlier, and avoid schema drift.

Next steps:

  1. Install: brew install supabase/tap/supabase
  2. Run supabase init in your project
  3. Create your first migration
  4. Set up Apidog to test endpoints
  5. Deploy to production confidently

Test your Supabase APIs with Apidog - free

FAQ

Do I need Docker to use Supabase CLI?

Yes. Docker Desktop must be running before supabase start. The CLI uses Docker Compose to run the stack locally. If Docker isn’t running, you’ll get a “Cannot connect to Docker daemon” error.

How do I sync my local database with production?

Use supabase db pull to generate migrations from your remote schema, then supabase db push to apply local migrations to production. Run supabase db reset locally after pulling.

Can I use Supabase CLI without a Supabase Cloud account?

Yes. You can use the CLI completely locally. Only use supabase login and supabase link when ready to deploy.

How do I handle migration conflicts in a team?

Pull the latest Git changes and run supabase db reset before creating new migrations. Use descriptive migration names and coordinate on breaking changes.

What’s the difference between supabase db push and supabase migration up?

supabase migration up applies pending migrations to your local DB. supabase db push applies them to your remote (production) project. Always test locally first.

Can I use Supabase CLI with an existing project?

Yes. Run supabase link --project-ref YOUR_PROJECT_ID to link, then supabase db pull to generate migrations from your current remote schema.

How do I test RLS policies locally?

Use Supabase Studio at http://localhost:54323 to switch user roles, or test via API with different JWT tokens. Apidog makes this easy: create multiple environments with different tokens and run requests as different users.

Is Supabase CLI free?

Yes. The CLI is free and open source. Local development costs nothing. You only pay for Supabase Cloud resources when deploying to production.

Top comments (0)