# Run Supabase Locally in a Cloud Sandbox

> Create a default Sandboxer sandbox, install the Supabase CLI from its .deb release, then run supabase init and supabase start. Docker is preinstalled. The first start took about 100 seconds. Host port 54323 (Studio) and 54321 (API) with sbx host for private HTTPS URLs. A memory-restoring stop and resume keeps the stack running.

Source: https://sandboxer.com/guides/run-supabase-locally-in-a-cloud-sandbox. Updated 2026-10-27.

**Short answer:** Create a default Sandboxer sandbox, install the Supabase CLI from its .deb release, then run supabase init and supabase start. Docker is preinstalled. The first start took about 100 seconds. Host port 54323 (Studio) and 54321 (API) with sbx host for private HTTPS URLs. A memory-restoring stop and resume keeps the stack running.

This is the Supabase CLI's **local** stack: Postgres, the API gateway, Auth, Storage, Realtime and Studio in Docker containers on one machine. It is not a Supabase cloud project. You don't need a Supabase account, and the keys it prints are shared local development defaults.

## At a glance

| Step | What we saw (single runs, October 27, 2026, `default` size) |
| - | - |
| Install | Supabase CLI 2.122.0 from the `.deb` release; Docker 29.9.0 and Node 22 were already there |
| `supabase start`, first run | About 100 seconds, most of it pulling images; 12 containers running |
| `supabase stop` + `supabase start` | 11.7 s to stop, 25.8 s to start again with images cached; data kept |
| Memory after start | About 2.1 GB used of 8 GB |
| Studio on a hosted URL | `401` without the token, `302` then `200` with it |
| REST call through the hosted URL | `200`, median 0.22 s over 20 calls from our test machine; 3.6 ms inside the sandbox |
| `sbx stop --wait` + `sbx resume` | 2.2 s and 1.4 s; `memoryRestored: true`, same container PIDs, same rows |

## Which size

We used `default` (4 vCPU, 8 GB RAM, 50 GB disk, $0.036/hour). Supabase's [local development guide](https://supabase.com/docs/guides/local-development/cli/getting-started) gives no minimum RAM, CPU or disk. The stack runs 12 containers and used about 2.1 GB of RAM once up, so `default` leaves headroom for your app and a test run next to it. We didn't try `small` (4 GB). Moving to a smaller size isn't supported yet, so pick the size you plan to stay on.

## Install the CLI and start the stack

Supabase lists Homebrew, Linux packages and `npx supabase` as install methods. We used the Linux `.deb` from the [CLI releases](https://github.com/supabase/cli/releases):

```bash
sbx new --name supabase-dev --type default
id=sb_ydztcc1fj6    # the id that sbx new printed

sbx exec $id 'curl -fsSLo /tmp/supabase.deb https://github.com/supabase/cli/releases/download/v2.122.0/supabase_2.122.0_linux_amd64.deb && sudo dpkg -i /tmp/supabase.deb && supabase --version'
```

```text
Unpacking supabase (2.122.0) ...
Setting up supabase (2.122.0) ...
2.122.0
```

The first `supabase start` pulls about a dozen images and runs longer than a foreground `sbx exec` waits (ours ended with exit `124`). Run it detached and poll a result file:

```bash
sbx exec $id 'mkdir -p ~/myapp && cd ~/myapp && supabase init'
sbx exec $id --detach 'cd ~/myapp && s=$(date +%s.%N); supabase start > start.log 2>&1; echo exit=$? > start.result; echo elapsed=$(python3 -c "print(round($(date +%s.%N)-$s,1))") >> start.result'

sbx exec $id 'cat ~/myapp/start.result'
```

```text
exit=0
elapsed=101.0
```

Part of the images had already been downloaded by our timed-out first attempt, so a completely fresh pull can take longer. The tail of `start.log`:

```text
Started supabase local development setup.
│ Studio  │ http://127.0.0.1:54323     │
│ Mailpit │ http://127.0.0.1:54324     │
│ Project URL    │ http://127.0.0.1:54321              │
│ REST           │ http://127.0.0.1:54321/rest/v1      │
│ URL │ postgresql://postgres:postgres@127.0.0.1:54322/postgres │
…
Local dev security notice
All services bind to 0.0.0.0 (network-accessible, not just localhost)
API keys and JWT secrets are shared defaults. Do not use in production
Studio, pgMeta (/pg/*), and analytics have no authentication
```

`supabase status -o env` prints the same values as shell variables, including `ANON_KEY`, `PUBLISHABLE_KEY` and `SERVICE_ROLE_KEY`. They are demo defaults, but don't paste the service role key into logs or chat anyway.

## Create a table and call it over REST

The sandbox has no `psql`, so run it inside the database container. The container is named `supabase_db_<project id>`, and the project id defaults to the folder name.

```sql
-- todos.sql
create table public.todos (
  id bigint generated always as identity primary key,
  task text not null,
  done boolean not null default false
);
alter table public.todos enable row level security;
create policy "anyone can read todos" on public.todos
  for select to anon using (true);
insert into public.todos (task) values ('write the guide'), ('delete the sandbox');
```

```bash
sbx scp todos.sql $id:/home/user/myapp/todos.sql
sbx exec $id 'cd ~/myapp && docker exec -i supabase_db_myapp psql -U postgres -d postgres < todos.sql'
sbx exec $id 'cd ~/myapp && eval $(supabase status -o env 2>/dev/null | grep ^ANON_KEY=); curl -s "http://127.0.0.1:54321/rest/v1/todos?select=id,task,done" -H "apikey: $ANON_KEY"'
```

```text
CREATE TABLE
ALTER TABLE
CREATE POLICY
INSERT 0 2
[{"id":1,"task":"write the guide","done":false},
 {"id":2,"task":"delete the sandbox","done":false}]
```

PostgREST serves the new table at once. An insert with the anon key was refused by the policy: `401` with `new row violates row-level security policy for table "todos"`.

## Host Studio and the API on private URLs

The CLI binds every service to `0.0.0.0`, so `sbx host` reaches them without `--localhost`. Keep both private (the default):

```bash
sbx host $id 54323 --title studio
sbx host $id 54321 --title api
```

```text
{"ok":true,"type":"port.hosted","sandboxId":"sb_ydztcc1fj6","port":54323,"url":"https://vivid-able-fig-54323.box.sandboxer.com?_token=<token>","access":"private",…}
{"ok":true,"type":"port.hosted","sandboxId":"sb_ydztcc1fj6","port":54321,"url":"https://vivid-able-fig-54321.box.sandboxer.com?_token=<token>","access":"private",…}
```

Open the Studio link in a browser. What `curl` returned:

```bash
curl -s -o /dev/null -w '%{http_code}\n' https://vivid-able-fig-54323.box.sandboxer.com/
# 401   (no token)

curl -sL -c jar -b jar -o /dev/null -w '%{http_code} %{url_effective}\n' "https://vivid-able-fig-54323.box.sandboxer.com?_token=<token>"
# 200 https://vivid-able-fig-54323.box.sandboxer.com/project/default
```

For the API, trade the token for the `__sbx_auth` cookie once, then call PostgREST with the anon key as usual:

```bash
API=https://vivid-able-fig-54321.box.sandboxer.com
curl -s -o /dev/null -c api.jar "$API/?_token=<token>"        # 302, sets the cookie
curl -s -b api.jar "$API/rest/v1/todos?select=id,task,done" -H "apikey: $ANON_KEY" -w '\nHTTP %{http_code} in %{time_total}s\n'
```

```text
[{"id":1,"task":"write the guide","done":false},
 {"id":2,"task":"delete the sandbox","done":false}]
HTTP 200 in 0.240160s
```

Over 20 calls from our test machine to the sandbox (region `eu-central`), the median was 0.22 s. Inside the sandbox the same call took 3.6 ms, so nearly all of that is the network path.

**Don't make these ports public.** Studio has no login, as the CLI's notice says. In this CLI version the local gateway also answered anon reads with no `apikey` header and with `apikey: nope` (both `200`). On a private URL the `_token` is the only thing keeping strangers out. Treat the full URL and the cookie like a password.

## Stop and resume: the stack keeps running

Before stopping, the sandbox had been up 325 seconds and `supabase_db_myapp` had PID 13272.

```bash
sbx stop $id --wait     # 2.2 s
sbx resume $id          # 1.4 s
sbx info $id            # "memoryRestored": true
sbx exec $id 'cat /proc/uptime; docker inspect -f "{{.Name}} {{.State.Pid}}" $(docker ps -q) | sort | head -3; docker ps -q | wc -l'
```

```text
342.25 1001.76
/supabase_analytics_myapp 13537
/supabase_auth_myapp 14153
/supabase_db_myapp 13272
12
```

Uptime kept counting and the PIDs are unchanged: memory was restored, so nothing restarted. The REST call through the hosted URL returned the same two rows, and Studio answered `200`, with no new `sbx host` call. Routes and tokens are kept across stop and resume.

## If the sandbox boots from disk instead

Resume boots from disk, keeping files but not processes, when you resize on resume or no machine with the same CPU type is free. Docker is an enabled systemd service and the database lives in Docker volumes on disk, so what comes back depends on each container's restart policy. The CLI sets them for you:

```bash
sbx exec $id 'docker inspect -f "{{.Name}} restart={{.HostConfig.RestartPolicy.Name}}" $(docker ps -q) | sort'
```

```text
/supabase_analytics_myapp restart=unless-stopped
/supabase_auth_myapp restart=unless-stopped
/supabase_db_myapp restart=unless-stopped
/supabase_edge_runtime_myapp restart=no
…   (the other 8 containers: unless-stopped)
```

We didn't resize, so we tested the same mechanism by restarting the Docker daemon (`sudo systemctl restart docker`). Eleven containers came back healthy and the rows were still there. `supabase_edge_runtime_myapp` stayed `Exited (15)`, and `supabase start` replied `supabase start is already running.` without starting it. `docker start supabase_edge_runtime_myapp` brought it back. Do the same after a cold boot if you use Edge Functions.

## Clean up

`supabase stop` keeps your data in Docker volumes (`supabase stop --no-backup` deletes it). Deleting the sandbox removes everything:

```bash
sbx delete $id --yes
```

After the delete, the hosted API URL returned `404`.

## FAQ

### Is this the same as a Supabase cloud project?

No. `supabase start` runs the open-source services in Docker inside your sandbox, with no Supabase account and shared demo keys. Use it for development and tests; it doesn't replace a hosted project.

### Can my app outside the sandbox use the hosted API URL?

Yes, if it can send the `__sbx_auth` cookie: open the `?_token=` link once, keep the cookie, and pass the anon key in the `apikey` header. We tested this with `curl`. Code running inside the sandbox can call `http://127.0.0.1:54321` directly.

### Why did the first start time out?

It pulls about a dozen images, which ran past the time a foreground `sbx exec` waits, and the command ended with exit `124`. Start it with `sbx exec $id --detach` and poll a result file, as shown above. With images cached, a restart took 25.8 seconds.

### Do I lose my tables when the sandbox stops?

No. A memory-restoring resume left all 12 containers running with the same PIDs. A boot from disk keeps the Docker volumes, and 11 of the 12 containers restart on their own.

### Can I connect to Postgres directly?

Port 54322 is the database. From your laptop, forward it over SSH with `sbx forward $id --remote 54322` rather than a hosted URL, which serves HTTP. We didn't run this step; see [SSH](/docs/ssh#forward-ports).

## Related

- [Hosting](/docs/hosting)
- [Run Docker Compose in a sandbox with a URL per service](/guides/run-docker-compose-in-a-sandbox-with-a-url-per-service)
- [Test text-to-SQL queries against a real Postgres](/guides/test-text-to-sql-queries-against-a-real-postgres)
- [Pause and resume a sandbox with its memory](/guides/pause-and-resume-a-sandbox-with-memory)
