feat: sandbox mode by jgoux · Pull Request #4805 · supabase/cli
Sandbox Mode - Fully Native Local Development Stack
Summary
Adds supabase start --sandbox to run a minimal Supabase stack using 100% native binaries - no Docker required. Enables local development in environments where containers aren't available (cloud IDEs, CI/CD, corporate restrictions).
Motivation
The Docker-based stack requires a full container runtime, which isn't always available:
- Cloud IDEs (Codespaces, Gitpod) often have limited Docker support
- CI/CD pipelines may restrict container-in-container execution
- Corporate environments may have policies against Docker
- Lightweight VMs and some platforms don't support containers
Sandbox mode runs PostgreSQL, GoTrue, PostgREST, and a Go proxy as native processes, orchestrated by process-compose.
Architecture
flowchart TB
subgraph CLI["supabase start --sandbox"]
START[Start Command]
end
subgraph SERVER["Background Server Process"]
PC[process-compose<br/>embedded library]
HTTP[HTTP Server<br/>port N]
end
subgraph NATIVE["Native Processes"]
PG[(PostgreSQL<br/>native binary<br/>port random)]
PROXY[API Proxy<br/>supabase _proxy<br/>port random]
GOTRUE[GoTrue<br/>port random]
POSTGREST[PostgREST<br/>port random]
end
START -->|spawns detached| SERVER
PC --> PG
PC --> PROXY
PC --> GOTRUE
PC --> POSTGREST
PROXY -->|/auth/v1/*| GOTRUE
PROXY -->|/rest/v1/*| POSTGREST
GOTRUE --> PG
POSTGREST --> PG
Startup Sequence
sequenceDiagram
participant User
participant CLI as supabase start
participant Server as _sandbox-server
participant PC as process-compose
participant Services as postgres/proxy/gotrue/postgrest
User->>CLI: supabase start --sandbox
CLI->>CLI: Allocate ports
CLI->>CLI: Download/extract binaries (if needed)
CLI->>CLI: Initialize PostgreSQL (if first run)
CLI->>CLI: Generate configs
CLI->>Server: Spawn detached process
Server->>PC: Initialize runner
Server->>PC: Start HTTP server
PC->>Services: Start all services
CLI->>Server: Poll /processes (HTTP)
Server-->>CLI: Services status
Note over CLI: Wait until healthy
CLI-->>User: "All services running"
Note over CLI: CLI exits
Note over Server: Server keeps running
Shutdown Sequence
sequenceDiagram
participant User
participant CLI as supabase stop
participant Server as _sandbox-server
participant PC as process-compose
participant Services as postgres/proxy/gotrue/postgrest
User->>CLI: supabase stop
CLI->>CLI: Load state.json
CLI->>Server: POST /project/stop (HTTP)
Server->>PC: ShutDownProject()
PC->>Services: SIGTERM (reverse order)
Services-->>PC: Stopped
Server-->>CLI: OK
alt HTTP API unavailable
CLI->>CLI: Kill server PID (fallback)
end
CLI->>CLI: Cleanup sandbox directory
Note over CLI: pgdata persists for next start
CLI-->>User: "Stopped"
⚠️ Required Local Binaries (macOS)
Until the GoTrue and PostgreSQL binaries are published to GitHub releases for macOS, you must manually place the following files at the root of the supabase/cli directory:
| File | Description |
|---|---|
auth-v2.186.0-darwin-arm64 |
GoTrue binary built for macOS arm64 |
supabase-postgres-17.6-darwin-arm64.zip |
PostgreSQL bundle for macOS arm64 |
The CLI will automatically detect these local files and use them instead of attempting to download from GitHub releases. See the Building Binaries Locally section for instructions on how to build these.
Usage
# Start sandbox mode (services run in background) supabase start --sandbox # Check status supabase status # Stop sandbox (data persists) supabase stop
Files Changed
| File | Description |
|---|---|
cmd/start.go |
Added --sandbox flag |
cmd/sandbox_server.go |
Hidden _sandbox-server command for background process-compose server |
cmd/proxy.go |
Hidden _proxy command for API reverse proxy |
internal/sandbox/sandbox.go |
Main entry point, PostgreSQL initialization with password setup |
internal/sandbox/server.go |
Process-compose server lifecycle and health polling |
internal/sandbox/runner.go |
Process-compose config generation and runner spawning |
internal/sandbox/proxy.go |
Pure Go reverse proxy with CORS and auth transformation |
internal/sandbox/binary.go |
Versioned binary download, extraction, and macOS code signing |
internal/sandbox/ports.go |
Dynamic port allocation |
internal/sandbox/context.go |
Project-specific context and state persistence |
internal/sandbox/status.go |
Sandbox-specific status display |
internal/sandbox/stop.go |
Graceful shutdown via HTTP API with PID fallback |
internal/sandbox/templates/process-compose.yaml.tmpl |
Process orchestration config |
internal/status/status.go |
Integration with sandbox status |
internal/stop/stop.go |
Integration with sandbox stop |
How It Works
Start (supabase start --sandbox)
- Allocates ports for all services:
- API and Postgres ports: Uses values from
config.toml, falls back to random if in use - Internal services (GoTrue, PostgREST, proxy, etc.): Always uses random ports
- API and Postgres ports: Uses values from
- Downloads and extracts binaries if needed:
- PostgreSQL: Extracts from
supabase-postgres-<version>-<os>-<arch>.zip - GoTrue: Downloads from GitHub releases (or uses local build)
- PostgREST: Downloads from GitHub releases
- PostgreSQL: Extracts from
- Initializes PostgreSQL data directory (first run only)
- Generates process-compose.yaml config with:
- PostgreSQL with native binary
- GoTrue migration and runtime with full
config.tomlauth settings - PostgREST with JWT/JWKS verification
- API proxy (
supabase _proxy) with auth transformation
- Spawns a detached
_sandbox-serverprocess - Saves state (server PID + ports) to
state.json - Waits for all services to be healthy via process-compose API
- Exits (services continue running in background)
Status (supabase status)
- Detects sandbox mode via
state.jsonfile and PID check - Checks service health via HTTP probes and
pg_isready - Displays status table with ports
Stop (supabase stop)
- Loads state from
state.json - Connects to process-compose HTTP server
- Calls
ShutDownProject()for graceful, dependency-ordered shutdown - Falls back to killing server PID if HTTP API unavailable
- Cleans up sandbox config files (state.json, process-compose.yaml, logs/)
- Preserves pgdata directory for data persistence
Directory Structure
Binary Cache (versioned, shared across projects)
~/.supabase/
└── bin/
├── gotrue/
│ └── 2.186.0/
│ └── gotrue
├── postgrest/
│ └── 14.4/
│ └── postgrest
└── postgres/
└── 17.6/
├── bin/ # postgres, initdb, psql, pg_isready
├── lib/ # shared libraries
└── share/ # config templates, migrations
Project State
supabase/.temp/
├── sandbox/ # Created on start, removed on stop
│ ├── state.json # Server PID + allocated ports
│ ├── process-compose.yaml # Process orchestration config
│ └── logs/
│ ├── server.log # Background server logs
│ └── process-compose.log # Process-compose logs
├── pgdata/ # PERSISTS between start/stop
│ ├── PG_VERSION
│ ├── postgresql.conf
│ ├── pg_hba.conf
│ └── base/
└── sandbox-postgres-version # Tracks installed postgres version
State File Format
{
"pid": 12345,
"ports": {
"api": 54321,
"postgres": 54322,
"gotrue": 54323,
"postgrest": 54324,
"postgrest_admin": 54325,
"process_compose": 54326
}
}API Proxy
The sandbox includes a pure Go reverse proxy (supabase _proxy) that replaces nginx:
- Routes:
/auth/v1/*→ GoTrue (with auth transformation for protected endpoints)/auth/v1/verify,/auth/v1/callback,/auth/v1/authorize→ GoTrue (no auth transformation)/rest/v1/*→ PostgREST (with auth transformation)/rest-admin/v1/*→ PostgREST admin server (no auth transformation)
- Auth transformation: Maps
apikeyheader to JWT Authorization (anon key → anon JWT, service role key → service role JWT) - CORS: Full CORS support with preflight handling (
Access-Control-Allow-Origin: *) - Health check:
/healthendpoint for process-compose readiness probes
Building Binaries Locally
GoTrue (darwin/arm64)
git clone https://github.com/supabase/auth.git cd auth go build -o auth . cp auth /path/to/supabase/cli/auth-v2.186.0-darwin-arm64
PostgreSQL Bundle
Place the postgres archive next to the CLI binary:
supabase-postgres-17.6-darwin-arm64.zip
The CLI will extract and install it to ~/.supabase/bin/postgres/17.6/.
Breaking Changes
None. The --sandbox flag is opt-in and doesn't affect the default Docker-based workflow.