Self-Hosting Supabase: A Complete Guide to Running an Open-Source Backend

Self-Hosting Supabase: A Complete Guide to Running an Open-Source Backend

Supabase is an open-source Firebase alternative built around Postgres, Auth, Storage, Realtime, and Edge Functions. This guide walks through a production-minded Docker Compose deployment on Ubuntu 24.04 LTS using the official self-hosted stack, secure key generation, Envoy as the API gateway, HTTPS with Caddy or Nginx, backups, and updates.

 Self-hosted Supabase overview

Caption: Self-hosted Supabase puts Postgres, Auth, REST, Realtime, Storage, Edge Functions, and Studio behind an API gateway on infrastructure you control.

Introduction

Supabase is an open-source platform that combines PostgreSQL with Auth, auto-generated REST APIs, Realtime subscriptions, object Storage, Edge Functions, and a Studio dashboard. Teams use it as a Firebase-style backend while keeping data in a real relational database. The managed cloud product is convenient; the self-hosted Docker stack is for operators who need data residency, predictable costs, or full control of secrets and networking.

Self-hosting Supabase makes sense when application data should stay on your VPS or private cloud, when you want Postgres extensions and Row Level Security under your own backup policy, or when a side project has outgrown a single Postgres container but is not ready for a multi-vendor backend. Agencies can host client backends next to Laravel or Node apps. Homelab operators can run Studio locally and expose only HTTPS.

This is not the supabase start local development CLI alone, and it is not a drop-in clone of every managed feature on day one. Logs and analytics are optional. The default API gateway is Envoy (Kong remains available as an override). Self-hosted Auth keys and URL settings must be correct before you point a production frontend at the stack.

This guide installs Supabase on Ubuntu 24.04 LTS with the official Docker Compose project from the supabase/supabase repository, pins a self-hosted/v* release, generates secrets with the shipped scripts, starts the stack with run.sh, and terminates TLS with the official Caddy or Nginx Compose overrides. The example uses https://supabase.example.com. Replace domains, email addresses, and passwords before production use.

Why Choose Supabase?

  • Postgres first: Tables, indexes, extensions, foreign keys, and SQL policies live in a database you can dump, replicate, and inspect with normal Postgres tools.
  • Batteries included: Auth, PostgREST, Realtime, Storage, Edge Runtime, postgres-meta, Supavisor, and Studio ship as one Compose project.
  • Official self-host path: Supabase publishes a maintained docker/ directory, setup.sh / run.sh helpers, monthly self-hosted/v* snapshots, and an update.sh merge workflow.
  • Client SDKs you already know: The same supabase-js patterns work against a self-hosted SUPABASE_PUBLIC_URL and publishable key.
  • Row Level Security: Authorization can live next to the data instead of only in application middleware.
  • Optional modules: Enable logs analytics, Caddy/Nginx HTTPS, or Kong with sh run.sh config add … instead of rewriting Compose from scratch.

Treat the stack as a multi-service production system. A leaked SUPABASE_SECRET_KEY or weak DASHBOARD_PASSWORD is equivalent to full backend compromise. Plan disk for Postgres and Storage before you invite users.

Prerequisites

Hardware Recommendations (from the official self-hosting guide):

  • Minimum: 2 CPU cores, 4 GB RAM, 40 GB SSD
  • Recommended: 4+ CPU cores, 8 GB+ RAM, 80 GB+ SSD
  • Extra headroom if you enable Logflare / Vector analytics
  • Off-server backup storage for database dumps and Storage files

Software and Accounts:

  • Ubuntu 24.04 LTS with sudo access
  • Domain DNS A/AAAA records for the public Supabase URL
  • Git, OpenSSL, jq, curl, ufw
  • Docker Engine with the Compose v2 plugin
  • A password manager for Studio credentials, API keys, and Postgres passwords
  • Optional SMTP provider for Auth emails (required for real signup flows)

Security Notes:

  • Never start with .env.example placeholder secrets on a public IP
  • Publish only ports 80 and 443 when using the HTTPS proxy overrides; keep the API gateway on the Docker network or localhost as documented
  • Set DASHBOARD_PASSWORD to a strong value that includes at least one letter (official Studio basic-auth rule)
  • Keep .env mode 600 and out of Git
  • Update SUPABASE_PUBLIC_URL, API_EXTERNAL_URL, and SITE_URL to HTTPS before OAuth or production clients

Patch the host and open only what you need:

sudo apt update
sudo apt upgrade -y
sudo apt install -y ca-certificates curl gnupg openssl jq git ufw

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status

Do not expose Postgres (5432 / 6543) to the whole internet unless you have a deliberate firewall allowlist and understand the risk.

Installation Guide

This deployment follows the official manual installation path so you can pin self-hosted/v0.8.0 and review secrets before the first start. On a greenfield Linux VPS you can instead use the quick start script curl -fsSL https://supabase.link/setup.sh | sh, then cd supabase-project && sh run.sh start. Inspect that script before piping it to sh.

1. Install Docker Engine

curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker "$USER"
newgrp docker

docker --version
docker compose version

If docker compose version fails, fix Docker before continuing. The self-hosted project expects Compose v2.

2. Clone the Official Docker Configuration

Pin a published self-hosted tag so upgrades stay intentional. Check GitHub tags for a newer self-hosted/v* release when you install.

sudo mkdir -p /opt
sudo chown "$USER":"$USER" /opt
cd /opt

git clone --depth 1 --branch self-hosted/v0.8.0 https://github.com/supabase/supabase
mkdir supabase-project
cp -rf supabase/docker/. supabase-project
cd supabase-project
cp .env.example .env
printf 'ref=self-hosted/v0.8.0\n' > .supabase-version
chmod 600 .env

docker compose pull

The tree should keep the upstream supabase clone separate from supabase-project, which becomes your live config, .env, and volume mounts. .supabase-version records the base so update.sh can upgrade later.

 Supabase Docker Compose stack

Caption: Traffic hits HTTPS on the reverse proxy, then Envoy routes Auth, REST, Realtime, Storage, Functions, and Studio while Postgres and volumes hold durable state.

3. Generate Keys and Secrets

Official docs warn that placeholder passwords must never be used. Generate secrets, then add the new API keys and asymmetric JWT signing material:

cd /opt/supabase-project
sh utils/generate-keys.sh
sh utils/add-new-auth-keys.sh

Review .env before starting. Confirm at least:

  • POSTGRES_PASSWORD
  • SECRET_KEY_BASE, VAULT_ENC_KEY, and related encryption keys written by the scripts
  • SUPABASE_PUBLISHABLE_KEY and SUPABASE_SECRET_KEY
  • DASHBOARD_USERNAME / DASHBOARD_PASSWORD (password must include a letter)

View the credentials you will hand to apps anytime with:

sh run.sh secrets

 Supabase security configuration map

Caption: Secure a self-hosted install by generating keys, setting Studio basic auth, fixing public URLs, enabling HTTPS, and keeping .env private.

4. Configure Public URLs

Edit .env for your domain. For a first HTTP bring-up on port 8000:

SUPABASE_PUBLIC_URL=http://supabase.example.com:8000
API_EXTERNAL_URL=http://supabase.example.com:8000/auth/v1
SITE_URL=http://app.example.com

For production HTTPS (after the proxy step below), switch to:

SUPABASE_PUBLIC_URL=https://supabase.example.com
API_EXTERNAL_URL=https://supabase.example.com/auth/v1
SITE_URL=https://app.example.com
PROXY_DOMAIN=supabase.example.com
CERTBOT_EMAIL=you@example.com

SITE_URL is your frontend origin for Auth redirects, not the Supabase hostname. API_EXTERNAL_URL must match the Auth callback base your OAuth providers expect.

5. Start the Stack

cd /opt/supabase-project
sh run.sh start
docker compose ps

sh run.sh start is equivalent to docker compose up -d --wait. Within about a minute, services should show Up … (healthy). If something stays in created or exits, run:

sh tests/test-container-logs.sh
sh run.sh logs storage

Studio is available through the API gateway on port 8000 by default, for example http://supabase.example.com:8000, protected by HTTP basic authentication.

6. Add HTTPS with Caddy or Nginx

Production Auth and browsers need TLS. Official overrides terminate TLS in front of the gateway:

cd /opt/supabase-project
# Prefer Caddy for automatic Let's Encrypt:
sh run.sh config add caddy
# Or Nginx + Certbot image:
# sh run.sh config add nginx

# Ensure .env URLs use https:// as shown above, then:
sh run.sh start
curl -I https://supabase.example.com/auth/v1/

A 401 from /auth/v1/ confirms TLS reached Auth. Follow docker logs supabase-caddy or docker logs supabase-nginx if certificates fail. Confirm DNS points at the VPS and ports 80/443 are open.

Configuration

After first start, treat .env and Compose overrides as the control plane. Useful run.sh commands:

sh run.sh help
sh run.sh secrets
sh run.sh logs auth
sh run.sh restart studio
sh run.sh recreate
sh run.sh config add logs
sh run.sh config remove logs

config add / config remove edit COMPOSE_FILE so optional files such as docker-compose.caddy.yml, docker-compose.nginx.yml, or docker-compose.logs.yml layer on the base stack. Enabling analytics starts Logflare and Vector and increases memory use.

SMTP is required for real email confirmation flows. Set production values in .env:

SMTP_ADMIN_EMAIL=admin@example.com
SMTP_HOST=smtp.example.com
SMTP_PORT=465
SMTP_USER=your-smtp-user
SMTP_PASS=your-smtp-password
SMTP_SENDER_NAME=Your App

Then recreate services so containers pick up the new environment:

sh run.sh recreate

Connect application code with the public URL and publishable key from sh run.sh secrets. Keep SUPABASE_SECRET_KEY on servers only. Session-mode Postgres through Supavisor uses port 5432 with username postgres.[POOLER_TENANT_ID]; transaction mode uses 6543. Prefer letters and numbers in POSTGRES_PASSWORD to avoid URL-encoding pain in connection strings.

Default gateway note for 2026 installs: Envoy is the default api-gw service. Kong is opt-in with sh run.sh config add kong. The default Envoy listener is HTTP on port 8000; terminate TLS with the Caddy/Nginx overrides rather than expecting gateway-native HTTPS.

Usage

Run this checklist after HTTPS is healthy:

  1. Open https://supabase.example.com and sign in with Studio basic auth.
  2. Create a public.notes table with id, body, and user_id columns.
  3. Enable Row Level Security and add policies scoped to auth.uid().
  4. Call REST with the publishable key.
  5. Confirm Realtime subscriptions over WSS through the same public hostname.
  6. Upload a small file through Storage and download it again.
  7. Invoke the sample Edge Function if present: curl https://supabase.example.com/functions/v1/hello.

 Supabase first-run checklist

Caption: A practical first-run path moves from Studio login to a table with RLS, a REST call, and a Realtime subscription.

Example REST probe (replace the key):

curl -sS "https://supabase.example.com/rest/v1/notes?select=*" \
  -H "apikey: YOUR_SUPABASE_PUBLISHABLE_KEY" \
  -H "Authorization: Bearer YOUR_SUPABASE_PUBLISHABLE_KEY"

Add Edge Functions under volumes/functions/<name>/index.ts, then:

sh run.sh restart functions

Use sh run.sh recreate functions when environment variables change. Client libraries should set supabaseUrl to SUPABASE_PUBLIC_URL and use the publishable key in browsers.

Screenshots and Visuals

The visuals in this guide are original architecture diagrams, not scraped Studio UI. They highlight the gateway boundary, secret generation, first-run workflow, and backup path you must operate yourself when you leave the managed Supabase cloud.

 Supabase backup and update workflow

Caption: Useful backups capture Postgres dumps, Storage volumes, Compose files, and .env, then verify health after update.sh and run a restore drill.

Troubleshooting

  • Services never become healthy: Run sh tests/test-container-logs.sh and docker compose ps. Low RAM is a common cause on 2 GB droplets.
  • Studio basic auth fails: Reset DASHBOARD_PASSWORD to a value that includes a letter; numbers-only passwords are rejected by the official guidance.
  • Auth redirects break after HTTPS: Recreate the stack after changing SUPABASE_PUBLIC_URL and API_EXTERNAL_URL. OAuth callback URLs must match API_EXTERNAL_URL + /callback.
  • Realtime WebSocket failures: Confirm the reverse proxy upgrades WebSockets. Official Caddy/Nginx overrides include the needed behavior; custom proxies must forward Upgrade / Connection and X-Forwarded-* headers.
  • container supabase-vector exited on rootless Docker: Set DOCKER_SOCKET_LOCATION in .env to your user socket, for example /run/user/1000/docker.sock.
  • Gateway entrypoint errors after clone on Windows tooling: Normalize docker/ files to LF line endings; CRLF checkouts break scripts.
  • Storage failures on macOS Docker Desktop: Prefer a named volume instead of a bind mount for Storage when developing on macOS; Linux VPS bind mounts are the usual production path.
  • Mixed content in the browser: Ensure SUPABASE_PUBLIC_URL and SITE_URL are https:// and clear caches after the change.
  • Analytics missing in Studio: Enable with sh run.sh config add logs && sh run.sh start.

Scaling, Securing, and Next Steps

Back up Postgres data, Storage files, and .env before every upgrade. A minimal maintenance sketch:

cd /opt/supabase-project
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p backups

# Logical dump through the running db container (adjust service name if needed)
docker compose exec -T db pg_dump -U postgres postgres | gzip > "backups/postgres-${stamp}.sql.gz"

tar --one-file-system -czf "backups/storage-${stamp}.tar.gz" volumes/storage
tar --one-file-system -czf "backups/config-${stamp}.tar.gz" \
  .env .supabase-version docker-compose.yml docker-compose.*.yml volumes/api volumes/proxy 2>/dev/null || true

rsync -avz backups/ backup-user@backup.example.net:/srv/backups/supabase/

Test a restore on a spare host before you need it. To change the database password after the first boot, use the official helper and recreate:

sh utils/db-passwd.sh
sh run.sh recreate

For upgrades, read docker/CHANGELOG.md and the self-hosted updating guide, take a fresh backup, then run update.sh as documented for your install. Pin self-hosted/v* tags rather than casually tracking master. After an update, verify Studio login, curl -I https://supabase.example.com/auth/v1/, a REST select, and a Storage download.

The outcome of this guide is a private Supabase backend on Ubuntu with generated secrets, an Envoy-fronted API surface, HTTPS via the official proxy overrides, Studio access under basic auth, and a backup path you can restore. From here, point one application at the publishable key, write RLS policies before opening signups, configure SMTP, document your restore drill, and schedule upgrades against the monthly self-hosted snapshots.

Need this done on your server?

I deploy and harden Laravel, CodeCanyon, and open-source apps on cPanel or VPS, and offer monthly Server Watch retainers. Hire for deploy · Care plan

References

Share:

Get new posts in your inbox

No spam. One short email per new article — practical PHP, Laravel, devops, and AI-assisted workflows.

Comments

Powered by GitHub Discussions via Giscus. A free GitHub account is required.