How to Run Supabase Locally with Docker: The Ultimate Development Guide
Why Use Supabase in a Local Docker Environment?
Developing against a live cloud instance can feel like walking on thin ice—any change might affect real users or incur unexpected costs. By spinning up a Supabase Local Docker stack, you get a sandbox that mirrors the production API while staying safely on your machine. The result is faster feedback loops, repeatable test data, and the freedom to tweak Postgres extensions without a gatekeeper.
Docker also isolates dependencies, meaning your team members can start the same environment with a single command, no matter whether they run macOS, Windows, or Linux. In short, local Docker turns the “cloud” into a controllable, version‑controlled service.
Prerequisites Before You Start
- Docker Engine (or Docker Desktop) version 20.10+ installed.
- Docker Compose (usually bundled with Docker Desktop).
- A recent
gitclient to clone the Supabase repo. - Basic familiarity with terminal commands and environment variables.
If any of these are missing, pause the guide and install them first. The rest of the steps assume you can run docker and docker-compose from your shell.
Setting Up the Docker Compose File
The heart of the setup is a docker-compose.yml that defines Supabase’s core services: Postgres, Kong (the API gateway), GoTrue (auth), Realtime, and the storage server. Supabase provides an official example, but you can trim it down if you only need a subset.
version: '3.8'services:
db:
image: supabase/postgres:15.1.0
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- "5432:5432"
kong:
image: supabase/kong:0.34.1
depends_on:
- db
environment:
KONG_DATABASE: "off"
KONG_DECLARATIVE_CONFIG: /home/kong/kong.yml
ports:
- "8000:8000"
auth:
image: supabase/gotrue:2.36.1
depends_on:
- db
environment:
GOTRUE_DB_DRIVER: postgres
GOTRUE_DB_DATABASE_URL: postgres://postgres:postgres@db:5432/postgres
ports:
- "9999:9999"
realtime:
image: supabase/realtime:2.20.2
depends_on:
- db
environment:
DB_HOST: db
DB_PORT: 5432
ports:
- "4000:4000"
storage:
image: supabase/storage:0.20.0
depends_on:
- db
environment:
POSTGREST_URL: http://kong:8000
ports:
- "5000:5000"
Save this snippet as docker-compose.yml in a dedicated folder. The version numbers reflect the latest stable releases at the time of writing, but you can always check Supabase’s Docker Hub pages for newer tags.
Launching Supabase with Docker Compose
Open a terminal, navigate to the folder containing the compose file, and run:
docker-compose up -dThe -d flag detaches the containers, letting you keep working in the same shell. Docker will pull the images the first time—expect a few minutes depending on your network speed. Afterward, verify that every service is healthy:
docker-compose psIf any container shows Exit or Restarting, inspect the logs with docker-compose logs <service> to pinpoint the issue. Common hiccups include port conflicts (especially if you already run a local Postgres) or insufficient memory allocation for Docker.
Connecting Your App to the Local Instance
Supabase’s JavaScript client reads configuration from an object, typically called supabaseUrl and supabaseKey. For a local Docker stack, point the URL to the Kong gateway:
const supabase = createClient('http://localhost:8000',
'anon-key-placeholder' // replace with actual JWT from GoTrue if needed
);
The default anon key is eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... (truncated for brevity). In production you’d swap it for a secret stored in environment variables. Once the client is instantiated, you can query tables, trigger auth flows, or listen to realtime updates exactly as you would with the cloud service.
Troubleshooting Common Hiccups
Port already in use. Docker will refuse to start a service if the host port is occupied. Either stop the conflicting process or change the host port mapping in docker-compose.yml (e.g., "5433:5432" for Postgres).
Database migrations not applied. Supabase relies on a set of migration scripts located in the supabase/migrations folder of the repo. To run them manually, exec into the db container and use psql:
docker exec -it <project>_db_1 bashpsql -U postgres -d postgres -f /docker-entrypoint-initdb.d/migrations.sql
Most of the time the official images handle migrations automatically, but custom schemas may need a manual step.
Realtime not receiving events. Ensure the realtime service can reach the db container on port 5432. Network misconfigurations inside Docker Desktop (especially on Windows) sometimes block inter‑container traffic. Restarting Docker or adding an explicit network section often resolves the issue.
Next Steps: Extending Your Local Setup
Now that the core stack runs, you can experiment with add‑ons such as pgbouncer for connection pooling, or swap the storage backend for a MinIO container to mimic S3. For CI pipelines, spin up the same compose file in a GitHub Actions job and run integration tests against it—this guarantees parity between local development and automated testing.
Finally, consider version‑controlling the docker-compose.yml and any custom SQL migrations. When teammates pull the repository, a single docker-compose up -d reproduces the exact same environment, cutting down onboarding time dramatically.
FAQ
Can I run Supabase locally without Docker?
Yes, you could install each service manually (Postgres, GoTrue, Kong, etc.), but Docker bundles the correct versions and configures networking automatically, making it the most pragmatic choice for most developers.
Do I need an internet connection after the images are pulled?
No. Once Docker has cached the images, you can start and stop the stack entirely offline. Only actions that fetch external resources—like pulling new image tags—require connectivity.
Is the local anon key safe for production testing?
The default anon key is deliberately insecure; it grants full read/write access to any table. Use it only in isolated development environments. For staging or production‑like testing, generate a fresh JWT secret and configure it via environment variables.
How do I reset the database to a clean state?
Stop the containers, remove the db volume, and bring the stack up again:
docker-compose down -vdocker-compose up -d
This wipes all data, giving you a fresh Postgres instance identical to the initial migration state.