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.shhelpers, monthlyself-hosted/v*snapshots, and anupdate.shmerge workflow. - Client SDKs you already know: The same
supabase-jspatterns work against a self-hostedSUPABASE_PUBLIC_URLand 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/AAAArecords 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.exampleplaceholder secrets on a public IP - Publish only ports
80and443when using the HTTPS proxy overrides; keep the API gateway on the Docker network or localhost as documented - Set
DASHBOARD_PASSWORDto a strong value that includes at least one letter (official Studio basic-auth rule) - Keep
.envmode600and out of Git - Update
SUPABASE_PUBLIC_URL,API_EXTERNAL_URL, andSITE_URLto 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.
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_PASSWORDSECRET_KEY_BASE,VAULT_ENC_KEY, and related encryption keys written by the scriptsSUPABASE_PUBLISHABLE_KEYandSUPABASE_SECRET_KEYDASHBOARD_USERNAME/DASHBOARD_PASSWORD(password must include a letter)
View the credentials you will hand to apps anytime with:
sh run.sh secrets
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:
- Open
https://supabase.example.comand sign in with Studio basic auth. - Create a
public.notestable withid,body, anduser_idcolumns. - Enable Row Level Security and add policies scoped to
auth.uid(). - Call REST with the publishable key.
- Confirm Realtime subscriptions over WSS through the same public hostname.
- Upload a small file through Storage and download it again.
- Invoke the sample Edge Function if present:
curl https://supabase.example.com/functions/v1/hello.
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.
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.shanddocker compose ps. Low RAM is a common cause on 2 GB droplets. - Studio basic auth fails: Reset
DASHBOARD_PASSWORDto 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_URLandAPI_EXTERNAL_URL. OAuth callback URLs must matchAPI_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/ConnectionandX-Forwarded-*headers. container supabase-vector exitedon rootless Docker: SetDOCKER_SOCKET_LOCATIONin.envto 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_URLandSITE_URLarehttps://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