Files
supabase/apps/docs/content/guides/local-development/cli-workflows.mdx

477 lines
16 KiB
Plaintext

---
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
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="homebrew"
queryGroup="install-method"
>
<TabPanel id="homebrew" label="Homebrew (macOS/Linux)">
```bash
brew install supabase/tap/supabase
```
The command is `supabase`. Updates: `brew upgrade supabase`.
</TabPanel>
<TabPanel id="npm" label="npm">
```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`.
</TabPanel>
</Tabs>
<Admonition type="note" label="Command syntax throughout this guide">
This guide uses `supabase` in all examples. If you installed via npm, substitute `npx supabase` everywhere.
</Admonition>
**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 <project-id>
```
Find your project ID in the Supabase Dashboard URL: `https://supabase.com/dashboard/project/<project-id>`.
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/<timestamp>_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.
<Admonition type="tip">
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.
</Admonition>
### 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
```
<Admonition type="caution">
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.
</Admonition>
**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.
<Admonition type="note" label="What about declarative schemas?">
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.
</Admonition>
## 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/<timestamp>_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
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="declarative"
queryGroup="schema-approach"
>
<TabPanel id="declarative" label="Declarative schemas">
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
</TabPanel>
<TabPanel id="imperative" label="Imperative migrations">
**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.
</TabPanel>
</Tabs>
### 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 <project-id>
# 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
```
<Admonition type="caution">
Never use `--include-seed` on a production database. Seed data is for development and testing.
</Admonition>
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 <name>` | 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 <name>` | 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).