---
id: 'cli-workflows'
title: 'CLI workflows'
description: 'End-to-end workflows for local development with the Supabase CLI.'
subtitle: 'End-to-end workflows for local development with the Supabase CLI.'
---
This guide walks through two common starting points for local development with the Supabase CLI, and shows how they converge into the same daily workflow. By the end, you'll have a `./supabase` directory in your repo that anyone can clone to recreate the full project — locally or on a fresh remote instance.
**Scenario A**: You have an existing project on the Supabase platform and want to move to a proper local development workflow.
**Scenario B**: You're starting from scratch locally and will eventually push to a remote instance.
Both lead to the same place: database schema and migrations tracked in version control, with seed data for local development.
## What the local development stack is
Running `supabase start` launches the full Supabase stack on your machine via Docker: Postgres, Auth (GoTrue), Storage, Realtime, Edge Functions, PostgREST, Studio, and more.
This local stack is **for development only**. It is not hardened for production use and must never be exposed to external traffic. It has no TLS, no rate limiting, and default credentials. Use it to develop and test — deploy to the [Supabase Platform](https://supabase.com) or a proper self-hosted setup for anything beyond that.
All local configuration lives in `./supabase/config.toml`, created by `supabase init`.
## Installing the CLI
```bash
brew install supabase/tap/supabase
```
The command is `supabase`. Updates: `brew upgrade supabase`.
```bash
npm install supabase --save-dev
```
The command is `npx supabase`. Unlike many npm packages, `npx supabase` does **not** auto-download the package on the fly — you must install it first. Pin the version in `package.json` so your entire team uses the same CLI version. Updates: `npm update supabase`.
This guide uses `supabase` in all examples. If you installed via npm, substitute `npx supabase` everywhere.
**Prerequisites**: [Docker Desktop](https://www.docker.com/products/docker-desktop/), [OrbStack](https://orbstack.dev/), [Colima](https://github.com/abiosoft/colima), or another Docker-compatible container runtime. At least 7 GB of RAM allocated to Docker is recommended.
## The `./supabase` directory
After `supabase init`, your project contains a `./supabase` directory. Here's what goes in it and what to commit:
| Path | Purpose | Commit? |
|---|---|---|
| `config.toml` | Local stack configuration (ports, auth settings, etc.) | Yes |
| `migrations/` | Timestamped SQL migration files, applied in order | Yes |
| `seed.sql` | Dev/test data, applied after migrations on `start` and `db reset` | Yes |
| `schemas/` | Declarative schema files (if using that approach) | Yes |
| `.temp/`, `.branches/` | CLI internal state | No (gitignored) |
The `config.toml` is safe to commit — it contains no secrets by default. If you add sensitive values (OAuth credentials, API keys), use the `env()` function to reference environment variables instead of hardcoding them. See [Managing config and secrets](/docs/guides/local-development/managing-config).
## Scenario A: Existing platform project to local development
You've built a project on the Supabase platform — tables created via the Dashboard, SQL editor, or client libraries. Now you want a local dev setup with everything in version control.
### Step 1: Initialize
In your project root:
```bash
supabase init
```
This creates `./supabase/config.toml`. If you already have a project directory with application code, run this at the root — the `supabase/` directory will sit alongside your app code.
### Step 2: Authenticate
```bash
supabase login
```
Opens a browser to generate an access token. The token is stored locally and used for all subsequent CLI commands that interact with the platform.
### Step 3: Link to your remote project
```bash
supabase link --project-ref
```
Find your project ID in the Supabase Dashboard URL: `https://supabase.com/dashboard/project/`.
This tells the CLI which remote project to connect to for `db pull`, `db push`, and other remote operations. You'll be prompted for the database password — this is the password set when you created the project.
### Step 4: Pull the remote schema
```bash
supabase db pull
```
This connects to your remote database, dumps the entire schema, and saves it as a migration file:
```
supabase/migrations/_remote_schema.sql
```
This initial migration is your baseline — it represents the current state of your database. All future changes build on top of it.
If you also use Supabase Auth or Storage and have customized their schemas, pull them separately:
```bash
supabase db pull --schema auth -f pull-auth-schema
supabase db pull --schema storage -f pull-storage-schema
```
These schemas are managed by Supabase and typically don't need to be pulled unless you've made custom modifications.
### Step 5: Create seed data
You have two options:
**Option A: Dump existing data from remote** (then clean it up):
```bash
supabase db dump --data-only --linked > supabase/seed.sql
```
Review and clean up the dump before committing. Remove production user data, secrets, personal information, and anything sensitive. Keep only representative test data that a developer needs to work with the project.
**Option B: Write seed data by hand** (recommended for most projects):
Create `supabase/seed.sql` with INSERT statements that set up a useful local development state — a few test users, sample data, etc. This is often better than dumping production data because you control exactly what's in it.
### Step 6: Verify
```bash
supabase start
supabase db reset
```
`db reset` destroys the local database and recreates it from scratch: applies all migrations in order, then runs `seed.sql`. If this succeeds, your setup is reproducible — anyone who clones the repo can do the same.
### Step 7: Commit
```bash
git add supabase/
git commit -m "add supabase local development setup"
```
Your project now has a fully reproducible local development environment.
For an existing project, the pulled migration already serves as your schema baseline. You don't need to also create a `schemas/` directory — that would mean maintaining two representations of the same schema. If you want to adopt declarative schemas later, see [Declarative database schemas](/docs/guides/local-development/declarative-database-schemas). For day-to-day changes going forward, see [The daily workflow](#the-daily-workflow) below.
## Scenario B: Starting fresh locally
No remote project yet. You're building from scratch and want to do it right from the start.
### Step 1: Initialize
```bash
supabase init
```
### Step 2: Start the local stack
```bash
supabase start
```
On first run, Docker images are pulled — this takes a few minutes. Subsequent starts are fast.
Once running, the CLI outputs local service URLs and credentials:
```
API URL: http://127.0.0.1:54321
GraphQL URL: http://127.0.0.1:54321/graphql/v1
S3 Storage URL: http://127.0.0.1:54321/storage/v1/s3
DB URL: postgresql://postgres:postgres@127.0.0.1:54322/postgres
Studio URL: http://127.0.0.1:54323
...
anon key: eyJ...
service_role key: eyJ...
```
Use the Studio URL to access a local instance of the Supabase Dashboard.
### Step 3: Create your schema
Two approaches — pick one:
**Option A: Declarative schema** (recommended for new projects)
Create a schema file that declares the state you want your database to be in:
```sql title="supabase/schemas/schema.sql"
create table public.todos (
id bigint generated by default as identity primary key,
created_at timestamptz default now() not null,
title text not null,
is_complete boolean default false not null,
user_id uuid references auth.users (id) default auth.uid() not null
);
alter table public.todos enable row level security;
create policy "Users can read their own todos"
on public.todos for select
using (auth.uid() = user_id);
create policy "Users can create their own todos"
on public.todos for insert
with check (auth.uid() = user_id);
```
Then generate a migration from it:
```bash
supabase db diff -f initial-schema
```
This compares your declared schema against the current (empty) database and generates a migration file in `supabase/migrations/`.
**Option B: Write the migration directly**
```bash
supabase migration new initial-schema
```
This creates an empty file at `supabase/migrations/_initial-schema.sql`. Write your SQL in it, then apply:
```bash
supabase db reset
```
### Step 4: Add seed data
Create `supabase/seed.sql`:
```sql title="supabase/seed.sql"
-- Create a test user (Supabase Auth)
-- Note: in local dev, email confirmation is disabled by default
insert into auth.users (id, email, raw_user_meta_data)
values ('d0e3c8f0-1234-5678-9abc-def012345678', 'test@example.com', '{}');
-- Seed application data
insert into public.todos (title, user_id)
values
('Buy groceries', 'd0e3c8f0-1234-5678-9abc-def012345678'),
('Write documentation', 'd0e3c8f0-1234-5678-9abc-def012345678');
```
### Step 5: Verify
```bash
supabase db reset
```
Drops everything, applies migrations, runs seed. If this passes, your project is reproducible.
### Step 6: Commit
```bash
git add supabase/
git commit -m "add supabase local development setup"
```
## The daily workflow
Both scenarios converge here. You have a working `./supabase` directory in your repo. Here's how day-to-day development works.
### Making schema changes
1. Edit your schema file(s) in `supabase/schemas/` (add a table, a column, a policy, etc.)
2. Generate a migration:
```bash
supabase db diff -f add-due-date-to-todos
```
3. Review the generated migration file — see [Cleaning up generated migrations](#cleaning-up-generated-migrations)
4. Verify the full chain:
```bash
supabase db reset
```
5. Commit the schema file **and** the migration together
**If you made changes through the local Studio UI:**
```bash
supabase db diff -f add-due-date-to-todos
```
This captures your UI changes as a migration file.
**If you prefer to write SQL directly:**
```bash
supabase migration new add-due-date-to-todos
```
Write the SQL in the generated file. Then verify:
```bash
supabase db reset
```
Commit the migration.
### Staying in sync with your team
When someone else pushes new migrations:
```bash
git pull
supabase db reset
```
`db reset` replays all migrations from scratch, so you'll always match the current state of the repo.
## Pushing to a remote project
When you're ready to deploy your schema to a remote Supabase instance:
```bash
# Authenticate (if not already)
supabase login
# Link to the remote project (if not already)
supabase link --project-ref
# Preview what will be applied
supabase db push --dry-run
# Apply migrations
supabase db push
```
`db push` applies only migrations that haven't been applied to the remote yet. It tracks this via the `supabase_migrations.schema_migrations` table created automatically on the remote database.
To also seed a fresh remote instance (dev/staging environments only):
```bash
supabase db push --include-seed
```
Never use `--include-seed` on a production database. Seed data is for development and testing.
For multi-environment setups with CI/CD (feature branches → staging → production), see [Managing Environments](/docs/guides/deployment/managing-environments).
## Key commands at a glance
| Command | What it does |
|---|---|
| `supabase init` | Creates `./supabase/config.toml` |
| `supabase start` | Starts the local stack, applies migrations + seed |
| `supabase stop` | Stops the local stack (data persists until `db reset`) |
| `supabase db reset` | Destroys local DB, reapplies all migrations + seed from scratch |
| `supabase db diff -f ` | Generates a migration by diffing current DB state against a shadow database |
| `supabase db pull` | Pulls remote schema into a new local migration file |
| `supabase db push` | Applies unapplied local migrations to the remote database |
| `supabase db dump` | Exports remote DB schema (or `--data-only` for data) via `pg_dump` |
| `supabase migration new ` | Creates an empty migration file |
| `supabase migration list` | Compares local migrations against remote migration history |
| `supabase link --project-ref` | Connects local project to a remote Supabase project |
| `supabase login` | Authenticates with the Supabase platform |
## Cleaning up generated migrations
When `supabase db diff` generates a migration, it may include statements that are technically correct but noisy. Review every generated migration before committing.
### Grants
You may see lines like:
```sql
GRANT ALL ON TABLE public.todos TO anon;
GRANT ALL ON TABLE public.todos TO authenticated;
GRANT ALL ON TABLE public.todos TO service_role;
```
These appear because the diff tool treats permissions as part of the schema state. For tables in the `public` schema, these grants are applied by default and the lines are redundant. They're harmless — but if you want clean migrations, you can remove them. Be consistent across your team about whether you keep or remove them.
### Revoke/re-grant patterns
Sometimes a diff produces:
```sql
REVOKE ALL ON TABLE public.todos FROM anon;
GRANT ALL ON TABLE public.todos TO anon;
```
This is the diff tool being overly cautious. If you haven't changed permissions, these lines can be safely removed.
### Extension statements
`CREATE EXTENSION IF NOT EXISTS ...` may appear. Keep these if the extension is required by your migration. Remove them if the extension is already created by a previous migration or is part of the default Supabase setup.
### Known limitations of `db diff`
The diff is generated by the [migra](https://github.com/djrobstep/migra) tool. It has limitations:
- **DML is not captured**: INSERT, UPDATE, DELETE statements are not tracked. If your change involves data, add those statements to the migration manually.
- **RLS policy renames** show up as a drop + create (not a rename). The result is functionally identical but worth knowing.
- **Some view properties** (like `security_invoker`) may not diff correctly.
When in doubt, review the generated SQL and adjust it manually. Treat `db diff` output as a draft, not a final migration.
## Troubleshooting
**`db reset` fails with a migration error**
The output will show which migration file failed and the SQL error. Fix the migration file, then run `db reset` again.
**`db push` says migrations are already applied**
The remote database already has those migrations in its history. Run `supabase migration list` to compare local vs. remote state. If they're out of sync, use `supabase migration repair` to correct the remote history.
**Schema drift: remote was changed outside of migrations**
If someone modified the remote database directly (via Dashboard, SQL editor, etc.), run `supabase db pull` to capture those changes as a new migration file. Then `supabase db reset` locally to verify everything still works.
**Docker issues on `supabase start`**
Ensure Docker is running and has at least 7 GB of RAM allocated. If containers fail health checks, try:
```bash
supabase stop
supabase start
```
If problems persist, `supabase stop --no-backup` for a clean restart (this removes local database data).