Supabase, the open-source Firebase alternative, is surprisingly easy to self-host with Docker, letting you ditch vendor lock-in and gain complete control over your data and infrastructure.
Here’s a look at Supabase running in a typical self-hosted setup. This is a docker-compose.yml file defining the services:
version: '3.8'
services:
db:
image: supabase/postgres:14.5.0.77
container_name: supabase_db
ports:
- "5432:5432"
volumes:
- supabase_db_data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: your_postgres_password # Change this!
POSTGRES_USER: supabase_user
POSTGRES_DB: supabase_db
networks:
- supabase_network
studio:
image: supabase/studio:20230801.80.ga96f15c
container_name: supabase_studio
ports:
- "3000:3000"
depends_on:
- db
environment:
POSTGRES_PASSWORD: your_postgres_password # Change this!
POSTGRES_USER: supabase_user
POSTGRES_DB: supabase_db
POSTGRES_HOST: db
POSTGRES_PORT: 5432
JWT_SECRET: your_jwt_secret # Change this!
DEFAULT_MAX_ROWS_PER_PAGE: 1000
networks:
- supabase_network
gotrue:
image: supabase/gotrue:v2.15.1
container_name: supabase_gotrue
ports:
- "8000:8000"
depends_on:
- db
environment:
DB_HOST: db
DB_PORT: 5432
DB_USER: supabase_user
DB_PASSWORD: your_postgres_password # Change this!
DB_NAME: supabase_db
JWT_SECRET: your_jwt_secret # Change this!
SITE_URL: http://localhost:3000 # Adjust if needed
API_URL: http://localhost:8000 # Adjust if needed
LOG_LEVEL: info
networks:
- supabase_network
realtime:
image: supabase/realtime:v0.34.2
container_name: supabase_realtime
ports:
- "4000:4000"
depends_on:
- db
environment:
DB_HOST: db
DB_PORT: 5432
DB_USER: supabase_user
DB_PASSWORD: your_postgres_password # Change this!
DB_NAME: supabase_db
JWT_SECRET: your_jwt_secret # Change this!
PORT: 4000
networks:
- supabase_network
storage:
image: supabase/storage:0.2.16
container_name: supabase_storage
ports:
- "5001:5001"
depends_on:
- db
environment:
DB_HOST: db
DB_PORT: 5432
DB_USER: supabase_user
DB_PASSWORD: your_postgres_password # Change this!
DB_NAME: supabase_db
JWT_SECRET: your_jwt_secret # Change this!
LOG_LEVEL: info
PRIVATE_KEY_PATH: /etc/storage/private.key # Ensure this file exists or is mounted
PUBLIC_KEY_PATH: /etc/storage/public.key # Ensure this file exists or is mounted
volumes:
- ./storage/keys:/etc/storage # Mount keys here
networks:
- supabase_network
volumes:
supabase_db_data:
networks:
supabase_network:
driver: bridge
This docker-compose.yml file defines the core Supabase services: db (PostgreSQL), studio (the dashboard), gotrue (authentication), realtime (websockets), and storage (file storage). Each service has its own Docker image, environment variables for configuration (like database credentials and JWT secrets), and network setup.
The db service uses the official PostgreSQL image, exposing port 5432 and persisting data in a Docker volume named supabase_db_data. The studio service runs on port 3000 and depends on the database being available. gotrue handles authentication on port 8000, realtime manages WebSocket connections on port 4000, and storage serves files on port 5001.
Key Configuration Points:
- Passwords and Secrets: Crucially, you must change
your_postgres_passwordandyour_jwt_secretto strong, unique values. These are used for database access and signing JWTs for authentication. - Database Connection: All services connect to the
dbservice using the hostnamedband the specified port, user, password, and database name. - URLs:
SITE_URLandAPI_URLingotrueshould reflect how your Supabase instance will be accessed. For local development,http://localhost:3000andhttp://localhost:8000are common. - Storage Keys: The
storageservice requires public and private keys for secure file operations. You’ll need to generate these (e.g., usingopenssl) and mount them into the container.
To get this running, first create a directory for your storage keys (e.g., mkdir storage && mkdir storage/keys), generate the keys (you can use openssl genrsa -out storage/keys/private.key 2048 and then openssl rsa -pubout -in storage/keys/private.key -out storage/keys/public.key), and then run:
docker-compose up -d
This will download the necessary Docker images and start all the Supabase services in detached mode. You can then access the Supabase Studio at http://localhost:3000 to manage your database and project.
The real magic of Supabase lies in its auto-generated APIs. Once your database tables are set up (either manually through the Studio or via migrations), Supabase automatically provides RESTful endpoints and a GraphQL API for them. This means you can interact with your data directly from your frontend application without writing any backend API code.
For example, if you have a todos table with id and task columns, you can fetch all todos with a simple GET request to /rest/v1/todos using your Supabase project’s API URL. Authentication is handled via JWTs passed in the Authorization: Bearer <your_jwt> header.
The realtime service enables live subscriptions. You can subscribe to changes in specific tables or rows. For instance, in your client-side code, you might use a Supabase SDK to subscribe to INSERT events on the todos table. When a new todo is added by any user, all subscribed clients will receive the update instantly via WebSockets.
One of the most powerful, yet often overlooked, aspects of Supabase’s self-hosted setup is how it leverages PostgreSQL’s Row Level Security (RLS). While the Studio provides a UI for managing RLS policies, understanding that these policies are applied directly at the database level is key. This means that even if an attacker gains access to your API endpoints, they cannot bypass your defined security rules, as the enforcement happens within PostgreSQL itself. You define policies like CREATE POLICY "Allow logged-in users to read their own posts" ON posts FOR SELECT USING (auth.uid() = user_id);, and Supabase’s gotrue service ensures that the auth.uid() function correctly returns the authenticated user’s ID for enforcement.
The next step after getting Supabase running locally is often setting up a robust deployment strategy for production, including SSL termination and domain configuration.