Self-Hosting with Docker
Learn how to configure and deploy Supabase with Docker.
Docker is the easiest way to get started with self-hosted Supabase. It should take you less than 30 minutes to get up and running.
Contents#
- Before you begin
- System requirements
- Installing Supabase
- Configuring and securing Supabase
- Starting and stopping
- Accessing Supabase services
- Updating
- Uninstalling
- Advanced topics
Before you begin#
This guide assumes you're comfortable with:
- Linux server administration basics
- Docker and Docker Compose
- Networking fundamentals (ports, DNS, firewalls)
If you're new to these topics, consider starting with the managed Supabase platform for free.
You need the following installed on your system:
- Git
- Docker:
- Linux server/VPS: Install Docker Engine and Docker Compose
- Linux desktop: Install Docker Desktop
- macOS: Install Docker Desktop
- Windows: Install Docker Desktop
- OpenSSL and Node.js 16+ (when switching to the new API keys and new auth)
System requirements#
Minimum requirements for running all Supabase components, suitable for development and small to medium production workloads:
| Resource | Minimum | Recommended |
|---|---|---|
| RAM | 4 GB | 8 GB+ |
| CPU | 2 cores | 4 cores+ |
| Disk | 50 GB SSD | 80 GB+ SSD |
If you don't need specific services, such as Logflare (Analytics), Realtime, Storage, imgproxy, or Edge Runtime (Functions), you can remove the corresponding sections and dependencies from docker-compose.yml to reduce resource requirements.
Installing Supabase#
Follow these steps to start Supabase on your machine:
1# Get the code2git clone --depth 1 https://github.com/supabase/supabase34# Make your new supabase project directory5mkdir supabase-project67# Tree should look like this8# .9# ├── supabase10# └── supabase-project1112# Copy the compose files over to your project13cp -rf supabase/docker/* supabase-project1415# Copy the fake env vars16cp supabase/docker/.env.example supabase-project/.env1718# Switch to your project directory19cd supabase-project2021# Pull the latest images22docker compose pullIf you are using rootless Docker, edit .env and set DOCKER_SOCKET_LOCATION to your docker socket location. For example: /run/user/1000/docker.sock. Otherwise, you will see an error like container supabase-vector exited (0).
Configuring and securing Supabase#
While we provided example placeholder passwords and keys in the .env.example file, you should NEVER start your self-hosted Supabase using these defaults.
Review the configuration steps below and ensure you set all secrets properly before starting the services.
Quick setup#
To generate secure passwords and secrets, run:
1sh utils/generate-keys.shAs the next step, use the following script to add the new API keys and asymmetric key pair:
1sh utils/add-new-auth-keys.shReview the output of both scripts and check the .env file before proceeding to configure Supabase URLs.
For a description of all secrets refer to the related section in the "Advanced topics" below. If you'd like to learn more about how the new API keys and asymmetric JWT signing work in a self-hosted Supabase setup, make sure to read the detailed how-to guide.
Configure Supabase URLs#
Review and change URL configuration variables:
SUPABASE_PUBLIC_URL: base URL for accessing Supabase from the Internet (Dashboard, API, Storage, etc.), e.g.,http://example.com:8000API_EXTERNAL_URL: used by the Auth service to configure callback URLs, e.g.,http://example.com:8000SITE_URL: default redirect URL for Auth, e.g.,http://example.com:3000
What your-domain means in the docs
Throughout the self-hosting guides, <your-domain> stands for the host where your Supabase instance is reachable: your domain name, your server's IP, or localhost, depending on your setup.
- Default setup: Kong listens on port
8000, so the full URL ishttp://<your-domain>:8000. - Behind a reverse proxy: the proxy terminates TLS on port
443, so the URL becomeshttps://<your-domain>.
Where to find your credentials#
The generated secrets and password are written to the .env file. The ones you are most likely to need when connecting your application to self-hosted Supabase are:
POSTGRES_PASSWORD: database password used in Postgres connection strings and bypsqlSUPABASE_PUBLISHABLE_KEY: publishable API key for client-side use (e.g., in your frontend)SUPABASE_SECRET_KEY: secret API key for server-side use. Never expose this in client codeSUPABASE_PUBLIC_URL: base URL you pass assupabaseUrlto the client libraries
If you are still using the legacy API keys, look for ANON_KEY and SERVICE_ROLE_KEY instead of the new publishable and secret API keys above.
Studio authentication#
Access to Studio (Dashboard) is protected with HTTP basic authentication.
A secure password MUST be set before starting Supabase. The password must include at least one letter - do not use numbers only or any special characters.
In the .env file, edit DASHBOARD_PASSWORD to change the password, and optionally DASHBOARD_USERNAME to change the username.
Starting and stopping#
You can start Supabase by using the following command in the same directory as your docker-compose.yml file:
1docker compose up -dCheck the status of the services:
1docker compose psAfter a minute or less, all services should have a status Up [...] (healthy). If you see a status such as created but not Up, run the test script to determine what the problem might be:
1sh tests/test-container-logs.shThen try inspecting the Docker logs for a specific container, e.g.,
1docker compose logs analyticsTo stop Supabase, use:
1docker compose downAccessing Supabase Studio (Dashboard)#
By default, you can access the dashboard through the API gateway on port 8000.
For example: http://<your-domain>:8000, or http://<your-ip>:8000 (or localhost:8000 if you are running Docker Compose locally).
You will be prompted for a username and password. See the Studio authentication section for details.
Accessing Postgres#
The self-hosted Supabase stack provides the Supavisor connection pooler for accessing Postgres and managing database connections.
You can connect to the Postgres database via Supavisor using the methods described below. Use your domain name, your server IP, or localhost depending on whether you are running self-hosted Supabase on a VPS, or locally.
The default POOLER_TENANT_ID is your-tenant-id (can be changed in .env), and the password is the one you set previously in Configure database password.
For session-based connections (equivalent to a direct Postgres connection):
1psql 'postgres://postgres.[POOLER_TENANT_ID]:[POSTGRES_PASSWORD]@[your-domain]:5432/postgres'For pooled transactional connections:
1psql 'postgres://postgres.[POOLER_TENANT_ID]:[POSTGRES_PASSWORD]@[your-domain]:6543/postgres'When using psql with command-line parameters instead of a connection string to connect to Supavisor, the -U parameter should also be postgres.[POOLER_TENANT_ID], and not just postgres.
If you need to configure Postgres to be directly accessible from the Internet, read Exposing your Postgres database.
To change the database password, read Changing database password.
Accessing Edge Functions#
Edge Functions live in volumes/functions. The default setup includes a hello function you can invoke with curl:
1curl http://<your-domain>:8000/functions/v1/helloAdd new functions at volumes/functions/<FUNCTION_NAME>/index.ts, then restart the service to pick them up:
1docker compose restart functions --no-depsSee the self-hosted Edge Functions guide for more details.
Accessing APIs#
Each of the APIs is available through the same API gateway:
- REST:
http://<your-domain>:8000/rest/v1/ - Auth:
http://<your-domain>:8000/auth/v1/ - Storage:
http://<your-domain>:8000/storage/v1/ - Realtime:
http://<your-domain>:8000/realtime/v1/
Configuring HTTPS#
By default, Supabase is accessible over HTTP. For production deployments, especially when using OAuth providers, you need HTTPS with a valid TLS certificate. The recommended approach is to place a reverse proxy (such as Caddy or Nginx) in front of Kong.
See the Configure HTTPS guide for detailed setup instructions.
Updating#
We publish stable releases of the Docker Compose setup approximately once a month. The versions in each release are tested together, so they may lag behind the latest images on Docker Hub. To update, apply the latest changes from the repository and restart the services. If you want to run different versions of individual services, you can change the image tags in the Docker Compose file, but compatibility is not guaranteed. All Supabase images are available on Docker Hub.
To follow the changes and updates, refer to the self-hosted Supabase changelog.
You need to restart services to pick up the changes, which may result in downtime for your applications and users.
Example: You'd like to update or rollback the Studio image. Follow the steps below:
- Check the supabase/studio images on Supabase Docker Hub
- Find the latest version (tag) number. It looks something like
2025.11.26-sha-8f096b5 - Update the
imagefield in thedocker-compose.ymlfile. It should look like this:image: supabase/studio:2025.11.26-sha-8f096b5 - Run
docker compose pull, followed bydocker compose down && docker compose up -dto restart Supabase.
Uninstalling#
Be careful — the following destroys all data, including the database and storage volumes!
To uninstall, stop Supabase (while in the same directory as your docker-compose.yml file).
Stop the containers and remove volumes:
1docker compose down -vOptionally, ensure removal of all Postgres data:
1rm -rf volumes/db/dataand all Storage data:
1rm -rf volumes/storageAdvanced topics#
Everything beyond this point in the guide helps you understand how the system works and how you can modify it to suit your needs.
Architecture#
Supabase is built from open source tools, each chosen or developed for production use.
If the tools and communities already exist, with an MIT, Apache 2, PostgreSQL, or equivalent open source license, we will use and support that tool. If the tool doesn't exist, we build and open source it ourselves.
- Studio - A dashboard for managing your self-hosted Supabase project
- Kong - Kong API gateway
- Auth - JWT-based authentication API for user sign-ups, logins, and session management
- PostgREST - Web server that turns your Postgres database directly into a RESTful API
- Realtime - Elixir server that listens to Postgres database changes and broadcasts them to subscribed clients
- Storage - RESTful API for managing files in S3, with Postgres handling permissions
- imgproxy - Fast and secure image processing server
- postgres-meta - RESTful API for managing Postgres (fetch tables, add roles, run queries)
- Postgres - Object-relational database with over 30 years of active development
- Edge Runtime - Web server based on Deno runtime for running JavaScript, TypeScript, and WASM services
- Logflare - Log management and event analytics platform
- Vector - High-performance observability data pipeline for logs
- Supavisor - Supabase's Postgres connection pooler
Multiple services require specific configuration within the Postgres database. Refer to the documentation describing the default roles to learn more.
You can find all the default extensions inside the schema migration scripts repo. These scripts are mounted at /docker-entrypoint-initdb.d to run automatically when starting the Postgres container.
Setting database password#
The generate-keys.sh script creates a secure random database password. If you want to use your own, you can change POSTGRES_PASSWORD in the .env file before starting Supabase for the first time.
Follow the password guidelines for choosing a secure password. For easier configuration, use only letters and numbers to avoid URL encoding issues in connection strings.
Changing database password#
To change the database password after the initial setup, run:
1sh utils/db-passwd.shThe script generates a new password, updates all database roles, and modifies your .env file. After running it, restart the services with docker compose up -d --force-recreate.
Configuring legacy API keys#
Use the key generator below to obtain and configure the following secure keys in .env:
JWT_SECRET: Used by Auth, PostgREST, and other services to sign and verify JWTs.ANON_KEY: Client-side API key with limited permissions (anonrole). Use this in your frontend applications.SERVICE_ROLE_KEY: Server-side API key with full database access (service_rolerole). Never expose this in client code.
- Copy the generated value and update
JWT_SECRETin the.envfile. Do not share this secret publicly or commit it to version control. - Copy the generated value and update
ANON_KEYin the.envfile. - Copy the generated value and update
SERVICE_ROLE_KEYin the.envfile.
The generated keys expire in 5 years. You can verify them at jwt.io using the saved value of JWT_SECRET.
Configuring secrets#
The generate-keys.sh script sets the following secrets automatically. You can also configure them manually in the .env file if needed:
SECRET_KEY_BASE: encryption key for securing Realtime and Supavisor communications. (Must be at least 64 characters; generate withopenssl rand -base64 48)VAULT_ENC_KEY: encryption key used by Supavisor for storing encrypted configuration. (Must be exactly 32 characters; generate withopenssl rand -hex 16)PG_META_CRYPTO_KEY: encryption key for securing connection strings used by Studio against postgres-meta. (Must be at least 32 characters; generate withopenssl rand -base64 24)LOGFLARE_PUBLIC_ACCESS_TOKEN: API token for log ingestion and querying. Used by Vector and Studio to send and query logs. (Must be at least 32 characters; generate withopenssl rand -base64 24)LOGFLARE_PRIVATE_ACCESS_TOKEN: API token for Logflare management operations. Used by Studio for administrative tasks. Never expose client-side. (Must be at least 32 characters; generate withopenssl rand -base64 24)S3_PROTOCOL_ACCESS_KEY_ID: Access key ID (username-like) for accessing the S3 protocol endpoint in Storage. (Generate withopenssl rand -hex 16)S3_PROTOCOL_ACCESS_KEY_SECRET: Secret key (password-like) used with S3_PROTOCOL_ACCESS_KEY_ID. (Generate withopenssl rand -hex 32)
MINIO_ROOT_PASSWORD: Root administrator password for the MinIO server. (Must be 8+ characters; generate withopenssl rand -hex 16)
Configuring Supabase services#
Each service has a number of configuration options you can find in the related documentation.
Configuration options are generally added to the .env file and referenced in docker-compose.yml service definitions, e.g.,
1services:2 rest:3 image: postgrest/postgrest4 environment:5 PGRST_JWT_SECRET: ${JWT_SECRET}Configuring social login (OAuth) providers#
See the Configure Social Login (OAuth) Providers guide for setup instructions.
Configuring phone login, SMS, and MFA#
See the Configure Phone Login & MFA guide for SMS provider setup, OTP settings, and multi-factor authentication configuration.
Configuring an email server#
You will need to use a production-ready SMTP server for sending emails. You can configure the SMTP server by updating the following environment variables in the .env file:
.env
1SMTP_ADMIN_EMAIL=2SMTP_HOST=3SMTP_PORT=4SMTP_USER=5SMTP_PASS=6SMTP_SENDER_NAME=We recommend using AWS SES. It's affordable and reliable. Restart all services to pick up the new configuration.
Configuring S3 Storage#
By default, all files are stored locally on the server. You can connect Storage to an S3-compatible backend (AWS S3, RustFS, MinIO, Cloudflare R2), enable the S3 protocol endpoint for tools like rclone, or both. These are independent features.
See the Configure S3 Storage guide for detailed setup instructions.
Configuring Supabase AI Assistant#
Configuring the Supabase AI Assistant is optional. By adding your own OPENAI_API_KEY to .env you can enable AI services, which help with writing SQL queries, statements, and policies.
Accessing Postgres through Supavisor#
By default, Postgres connections go through the Supavisor connection pooler for efficient connection management. Two ports are available:
POSTGRES_PORT(default: 5432) - Session mode, behaves like a direct Postgres connectionPOOLER_PROXY_PORT_TRANSACTION(default: 6543) - Transaction mode, uses connection pooling
For more information on configuring and using Supavisor, see the Supavisor documentation.
Exposing your Postgres database#
By default, Postgres is only accessible through Supavisor. If you need direct access to the database (bypassing the connection pooler), you need to disable Supavisor and expose the Postgres port.
Exposing Postgres directly bypasses connection pooling and exposes your database to the network. Configure firewall rules or network policies to restrict access to trusted IPs only.
Edit docker-compose.yml:
- Disable Supavisor - Comment out or remove the entire
supavisorservice section - Expose Postgres port - Add the port mapping to the
dbservice, it should look like the example below:
docker-compose.yml
1db:2 ports:3 - ${POSTGRES_PORT}:${POSTGRES_PORT}4 container_name: supabase-dbAfter restarting, you can connect to the database directly using a standard Postgres connection string:
1postgres://postgres:[POSTGRES_PASSWORD]@[your-server-ip]:5432/[POSTGRES_DB]Setting log_min_messages in Postgres#
By default, the database's log_min_messages configuration is set to fatal in docker-compose.yml to prevent redundant logs generated by Realtime. You can configure log_min_messages using any of the Postgres Severity Levels.
Using file backend in Storage on macOS#
By default, the Storage backend uses local files via a bind mount. On macOS, Docker Desktop bind mounts have known limitations (missing xattr support, permission issues) that can prevent Storage from working correctly. Change the bind mount to a named Docker volume instead.
Managing your secrets#
Many components inside Supabase rely on secrets and passwords being kept securely. By default, all secrets are in the .env file, but we strongly recommend using a secrets manager when deploying to production.
Some suggested systems include:
- Doppler
- Infisical
- Key Vault by Azure (Microsoft)
- Secrets Manager by AWS
- Secrets Manager by GCP
- Vault by HashiCorp
Demo#
- The VPS instance is a DigitalOcean droplet. (For server requirements refer to System requirements)
- To access Studio, use the IPv4 IP address of your Droplet.
- If you're unable to use Studio, run
docker compose psto see if all services are up and healthy.