[{"data":1,"prerenderedAt":29},["ShallowReactive",2],{"$fOskX7UzDeV1ygj26tUDjWKxrO6dm01nGxzHZozFCi74":3},{"slug":4,"title":5,"description":6,"date":7,"tags":8,"draft":13,"readingMinutes":14,"html":15,"related":16},"supabase-cli","Supabase CLI: Migrations, Types and the Docker Catch","The Supabase CLI in late 2026: how to install it, why local dev needs a container runtime, and the migration commands that keep production in sync.","2026-10-30",[9,10,11,12],"supabase","cli","postgres","migrations",false,4,"\u003Cp>The Supabase CLI does two jobs, and people usually only know about one. It runs the whole Supabase stack on your machine (Postgres, Auth, Storage, Studio, a fake mail inbox), and it moves your database schema between that local copy and the real project as migration files in Git. The second job is the one that matters once a project has users. The first is the one with the catch: it needs a container runtime.\u003C/p>\n\u003Cp>Everything here is checked against Supabase&#39;s \u003Ca href=\"https://supabase.com/docs/guides/local-development/cli/getting-started\">CLI getting started guide\u003C/a> and \u003Ca href=\"https://supabase.com/docs/guides/deployment/database-migrations\">database migrations guide\u003C/a> on October 30, 2026.\u003C/p>\n\u003Ch2>Installing it\u003C/h2>\n\u003Cp>I&#39;d install it per project, not globally:\u003C/p>\n\u003Cpre>\u003Ccode class=\"language-bash\">npm install supabase --save-dev\nnpx supabase --version\n\u003C/code>\u003C/pre>\n\u003Cp>That pins the version in \u003Ccode>package.json\u003C/code>, so everyone on the project and the CI runner use the same one. It needs Node 20 or newer; the docs say older versions like 16 &quot;fail to start the CLI&quot;, which is a fun one to debug on an old CI image. If you want it on your PATH instead, it&#39;s \u003Ccode>brew install supabase/tap/supabase\u003C/code> on macOS or Linux, Scoop on Windows, or a \u003Ccode>.deb\u003C/code>/\u003Ccode>.rpm\u003C/code> from the GitHub releases.\u003C/p>\n\u003Cp>Updating is the same package manager again (\u003Ccode>npm update supabase --save-dev\u003C/code>), and the docs ask you to stop the local containers first. Read the warning next to that before you copy the command: \u003Ccode>supabase stop --no-backup\u003C/code> deletes your local schema and data changes.\u003C/p>\n\u003Ch2>The Docker catch\u003C/h2>\n\u003Cp>\u003Ccode>supabase start\u003C/code> runs every service as a container, so you need Docker Desktop, OrbStack (what Supabase recommends on macOS), Rancher Desktop, Podman or colima. On a laptop with 8 GB of RAM the full stack is noticeable, and the first start pulls a lot of images.\u003C/p>\n\u003Cp>There is now an experimental native runtime that runs each service as a plain process, for CI runners and sandboxes without a container engine, on Linux and Apple silicon Macs. Experimental is the word in the docs, so I wouldn&#39;t build a team workflow on it yet.\u003C/p>\n\u003Cp>You can also skip local development altogether and still use the CLI. Linking, pulling, pushing migrations, generating types and deploying Edge Functions all work against the hosted project. Plenty of small teams should do exactly that: one staging project on Supabase instead of a Docker setup nobody maintains.\u003C/p>\n\u003Cp>When it is running, these are the addresses it prints:\u003C/p>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>What\u003C/th>\n\u003Cth>Local address\u003C/th>\n\u003C/tr>\n\u003C/thead>\n\u003Ctbody>\u003Ctr>\n\u003Ctd>API (REST, GraphQL, Edge Functions)\u003C/td>\n\u003Ctd>\u003Ccode>http://127.0.0.1:54321\u003C/code>\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Postgres\u003C/td>\n\u003Ctd>\u003Ccode>postgresql://postgres:postgres@127.0.0.1:54322/postgres\u003C/code>\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Studio\u003C/td>\n\u003Ctd>\u003Ccode>http://127.0.0.1:54323\u003C/code>\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Mailpit (every auth email lands here)\u003C/td>\n\u003Ctd>\u003Ccode>http://127.0.0.1:54324\u003C/code>\u003C/td>\n\u003C/tr>\n\u003C/tbody>\u003C/table>\n\u003Cp>Mailpit alone is a reason to run it locally. Testing sign-up and password reset emails against real inboxes is how people hit the hosted project&#39;s email limits on day one.\u003C/p>\n\u003Cimg src=\"/blog/supabase-cli/supabase-cli-command-map.webp\" alt=\"Metro-map style diagram of the Supabase CLI: the migrations folder, local database and hosted project as stations, linked by migration new, db diff, db reset, db pull, db push and gen types\" width=\"1600\" height=\"900\" loading=\"lazy\">\u003Ch2>Migrations: the part worth learning properly\u003C/h2>\n\u003Cp>The loop the docs describe, and the one I&#39;d stick to:\u003C/p>\n\u003Cpre>\u003Ccode class=\"language-bash\">npx supabase login\nnpx supabase link                       # pick the project\nnpx supabase db pull                    # first time: capture the remote schema\nnpx supabase migration new add_invoices # write SQL in the new file\nnpx supabase db reset                   # rebuild local from all migrations + seed\nnpx supabase db push                    # apply what production hasn&#39;t run yet\n\u003C/code>\u003C/pre>\n\u003Cp>\u003Ccode>db push\u003C/code> reads the history table \u003Ccode>supabase_migrations.schema_migrations\u003C/code> on the remote database and runs only the files that aren&#39;t in it, in order. That&#39;s the whole mechanism, and it explains every error you&#39;ll get.\u003C/p>\n\u003Cp>If you prefer clicking in Studio, \u003Ccode>supabase db diff -f add_invoices\u003C/code> turns the changes you made locally into a migration file. It works. I still read every generated file before committing, because a diff tool can&#39;t tell a rename from a drop and a create.\u003C/p>\n\u003Cp>\u003Cstrong>The rule that saves you:\u003C/strong> never change the production schema in the dashboard once you use migrations. The docs are blunt about it: a remote change &quot;bypasses the migration history and will cause \u003Ccode>db push\u003C/code> to fail with sync errors.&quot; Someone adds a column in the table editor on a Friday, and on Monday nobody can deploy.\u003C/p>\n\u003Cp>When the history and reality do drift, \u003Ccode>supabase migration list\u003C/code> shows local against remote, and \u003Ccode>supabase migration repair --status applied &lt;timestamp&gt;\u003C/code> (or \u003Ccode>reverted\u003C/code>) fixes the bookkeeping. Note what repair doesn&#39;t do: it &quot;updates the tracking table only&quot;. It never runs or undoes any SQL. Use it to tell the CLI the truth, not to fix the schema.\u003C/p>\n\u003Cp>And only one person or one pipeline should run \u003Ccode>db push\u003C/code>. Two laptops pushing at once is a conflict the docs warn about, and it&#39;s an easy one to avoid: push from CI on merge to main and nowhere else.\u003C/p>\n\u003Ch2>Types, functions and secrets\u003C/h2>\n\u003Cp>Two commands I&#39;d put in \u003Ccode>package.json\u003C/code> scripts on day one:\u003C/p>\n\u003Cpre>\u003Ccode class=\"language-bash\">npx supabase gen types typescript --linked &gt; src/types/database.ts\nnpx supabase functions deploy send-invoice\n\u003C/code>\u003C/pre>\n\u003Cp>Generated types make \u003Ccode>supabase-js\u003C/code> queries fail at compile time when a column is renamed, which is far better than finding out from a user. Run it after every migration. Edge Function secrets go in with \u003Ccode>npx supabase secrets set STRIPE_SECRET_KEY=...\u003C/code>, never in the function&#39;s code.\u003C/p>\n\u003Ch2>Where it gets annoying\u003C/h2>\n\u003Cp>Seeds. \u003Ccode>db reset\u003C/code> runs \u003Ccode>supabase/seed.sql\u003C/code>, which is great for test users and terrible the day someone puts real-looking customer data in it and commits it. Keep seeds fake.\u003C/p>\n\u003Cp>Auth and storage config live partly in \u003Ccode>supabase/config.toml\u003C/code> and partly in the dashboard, and not every setting syncs. I treat the dashboard as the source of truth for auth providers and redirect URLs, and write them down in the README. Boring, but it&#39;s the thing new developers on a project ask about first.\u003C/p>\n\u003Cp>If your Supabase project grew without any of this and you want migrations, types and a clean deploy path set up properly, that&#39;s a job I take on. The \u003Ca href=\"/blog/supabase-pricing\">Supabase pricing breakdown\u003C/a> and \u003Ca href=\"/blog/supabase-rls\">RLS guide\u003C/a> cover the other two things I look at first, and you can \u003Ca href=\"https://calendly.com/director-norsky/meeting\">book a free call\u003C/a> to talk it through.\u003C/p>\n",[17,21,25],{"slug":18,"title":19,"description":20},"supabase-realtime","Supabase Realtime: What It Costs and Where It Breaks","Supabase Realtime in late 2026: how messages are counted and billed, the per-plan limits, the errors you hit first, and when to switch from Postgres Changes to Broadcast.",{"slug":22,"title":23,"description":24},"supabase-rls","Supabase RLS: What It Actually Protects","Supabase row level security explained for 2026: grants vs policies, the publishable key, why UPDATE needs a SELECT policy, views that leak, and fast policies.",{"slug":26,"title":27,"description":28},"self-host-supabase","Self-Host Supabase: What You Take On","How to self-host Supabase with Docker in 2026: server size, the one-command setup, what you lose (PITR, branching, backups) and when it's worth it.",1794181975338]