--- 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).