Blog

Local Supabase without Docker: inz dev --embedded-pg

Run your backend locally with no Docker: one binary with Postgres built in, and your frontend keeps using supabase-js. How inz dev compares to supabase start.

You can run your backend locally without Docker by using inz dev --embedded-pg. It starts the instancez API and a Postgres 16 that ships inside the inz binary, so you don’t run containers or install a database. Your frontend keeps using @supabase/supabase-js, pointed at http://localhost:8080.

This post covers what supabase start needs, what inz dev does instead, and the local loop for schema changes, resets and running more than one project. We ran the local commands and scripts here before publishing.

What does supabase start need?

Supabase’s local stack runs in containers. From the Supabase local development docs: “A container manager compatible with Docker APIs is a prerequisite for supabase start.” They list OrbStack, Docker Desktop, Rancher Desktop and Podman.

A few other details from the CLI getting-started guide and the supabase start reference:

  • “The first run takes time while the CLI downloads the Docker images.”
  • “It is recommended to have at least 7GB of RAM to start all services.”
  • The API listens on 54321, Postgres on 54322 and Studio on 54323. With the default ports, supabase start runs one local project per machine.
  • You can skip services you don’t need with -x, for example -x gotrue,imgproxy.

Supabase also documents an experimental native runtime that runs each service as a process instead of a container, on Linux and Apple silicon macOS. The standard path is Docker.

None of this is a problem if Docker is already part of your day. If it isn’t, it’s a lot to set up just to develop against a backend.

How do I run a backend locally without Docker?

Install the CLI and start a project:

curl -fsSL https://get.instancez.ai | sh
mkdir my-app && cd my-app
inz init
inz dev --embedded-pg

Here’s what happens on that first inz dev --embedded-pg:

  1. It downloads a Postgres 16 binary once (about 30 MB) and keeps the data in ./pgdata/.
  2. It provisions the Postgres roles instancez needs and applies the schema from instancez.yaml.
  3. It generates a publishable key (for browsers, mapped to anon) and a secret key (for servers, mapped to service_role), prints both and saves them to .development.env.
  4. It serves the API on http://localhost:8080 and a dashboard on http://localhost:8080/dashboard.

That’s one process. The same binary handles REST, auth, storage, SQL functions and JavaScript functions. It ran in seconds on our machines, with no Postgres or Docker installed for it.

inz init scaffolds a todos table, a profiles table, an avatars bucket and an example code function, so you have something to query right away.

Does supabase-js work against it?

Yes, unchanged. With the keys from .development.env in your environment:

import { createClient } from '@supabase/supabase-js'

const supabase = createClient('http://localhost:8080', process.env.INSTANCEZ_PUBLISHABLE_KEY)

const { data: { user } } = await supabase.auth.signUp({
  email: '[email protected]',
  password: 'local-only-password',
})
await supabase.from('todos').insert({ user_id: user.id, title: 'works offline' })

const { data } = await supabase.from('todos').select('title, status')
console.log(data)
set -a; . ./.development.env; set +a
node try.mjs
# [ { status: 'pending', title: 'works offline' } ]

The status column filled in its pending default. The scaffolded todos table has a user_id = auth.uid() policy, so the same select without a session returns an empty list:

curl 'http://localhost:8080/rest/v1/todos?select=*' \
  -H "apikey: $INSTANCEZ_PUBLISHABLE_KEY"
# []

If you’re coming from a local Supabase setup, point your app’s env vars at http://localhost:8080 and the publishable key instead of 54321 and the anon key. The switch guide shows the full before and after.

How do schema changes work locally?

With supabase start, schema lives in supabase/migrations. You write SQL or generate it with supabase db diff, then supabase db reset rebuilds the local database from those files.

With instancez, you edit instancez.yaml while inz dev runs. We added a table to the scaffold:

  tags:
    rls_enabled: true
    fields:
      - name: id
        type: bigserial
        primary_key: true
      - name: label
        type: text
        required: true
    rls:
      - operations: [select]
        using: "true"

On save, the log printed config reloaded successfully tables=3, and GET /rest/v1/tags answered right away. There was no migration file and no restart. instancez diffs the YAML against the database and applies the difference. Locally, inz dev also applies drops, logging a warning for each one. Production (inz serve) refuses drops unless you opt in. More in migrations without migration files.

Code functions reload the same way. Edit a file in functions/ and the next call runs the new code.

How do I reset the local database?

Data survives restarts. Stop inz dev with Ctrl+C, start it again, and your rows are still in ./pgdata/. We checked: the works offline row came back after a restart.

To start from an empty database:

inz dev --embedded-pg --reset-pg

That wipes ./pgdata/ and applies the current schema to a fresh Postgres. After a reset, our row was gone and the tables were back, empty. Because the schema is the YAML, a reset never needs a migration replay. It’s the closest thing to supabase db reset, and supabase stop --no-backup covers the same ground on the Supabase side.

Can I run two projects at once?

Yes. Each project keeps its own ./pgdata/, so you only need a different API port. Set it in the second project’s instancez.yaml:

server:
  port: 8081

We ran two scaffolded projects side by side this way, each with its own embedded Postgres, keys and data. Both answered at the same time.

Do code functions need anything extra?

Node.js 22 or later on your PATH. Functions are JavaScript ESM handlers that run in Node.js worker processes. inz init only scaffolds the example function when Node 22+ is installed, and projects without a functions: block don’t need Node at all.

The scaffold’s todos function is called the same way you’d call a Supabase Edge Function:

const { data, error } = await supabase.functions.invoke('todos', {
  body: { title: 'from a function' },
})
// data.todo: { id: 1, title: 'from a function', status: 'pending', ... }

Inside the handler, ctx.supabase carries the caller’s JWT, so RLS applies to its queries as that user. The functions docs cover the handler shape, secrets and logging.

Supabase Edge Functions run on Deno, so existing ones need porting to Node before they run here.

Can I use my own Postgres instead?

Yes. Drop the flag and give inz dev a superuser connection string for any Postgres 14 or later:

export INSTANCEZ_DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres
inz dev

inz doctor checks the config, the connection string and the role layout before you start. The embedded Postgres exists only on inz dev; it’s for development, not production.

How do the two local setups compare?

inz dev --embedded-pgsupabase start
NeedsThe inz binary (Node 22+ only for code functions)A Docker-compatible container runtime
What runsOne process plus an embedded Postgres 16The Supabase services as containers
First runDownloads Postgres 16 once (about 30 MB)Downloads the Docker images
API URLhttp://localhost:8080http://localhost:54321
Schemainstancez.yaml, re-applied on savesupabase/migrations SQL files
Reset--reset-pgsupabase db reset
Several projectsOne server.port per projectOne per machine with default ports
Client@supabase/supabase-js@supabase/supabase-js
FunctionsNode.jsDeno

The trade-off is breadth. Locally and in production, instancez has no Realtime or websockets, no phone/SMS login, and OAuth for Google, GitHub and Apple only. If your app depends on supabase.channel(), that code won’t have a backend to talk to. The compare page has the full list.

How do I go from local to production?

The file you developed against is the file you deploy. On your own server, with your own Postgres:

inz validate
inz serve --migrate

Or push it to instancez Cloud with inz cloud deploy. The self-hosting guide covers VMs, Docker and Kubernetes, the self-hosted Supabase comparison explains the one-binary trade-off, and one YAML file for your whole backend goes through everything that file can hold.

FAQ

Is the embedded Postgres a real Postgres? Yes, it’s Postgres 16 running from the binary inz downloads. Your RLS policies are real Postgres policies, and data lives in ./pgdata/.

What can I check without starting a server? inz validate checks the YAML’s structure and references, and inz vet --fail-on medium flags open policies, disabled RLS and hardcoded secrets. Both read the file only, so they fit in CI.

Where are my keys? In .development.env, generated on first run. They’re also printed at startup and shown in the dashboard. The .gitignore that inz init writes keeps .development.env, .production.env and pgdata/ out of git.