Why Self-Host Supabase?
Supabase has become one of the fastest-growing backend platforms on the planet — and for good reason. It ships Postgres, Auth, Realtime, Storage, and Edge Functions as a single integrated stack. Supabase recently doubled its valuation to $10B, which tells you exactly how much momentum is behind the project right now.
The managed cloud is excellent. But there are real reasons to run your own instance:
- Data residency: Your Postgres data stays in your jurisdiction, your data centre, your rules.
- Cost at scale: Cloud pricing scales with rows, bandwidth, and users. A VPS doesn't.
- Compliance: HIPAA, GDPR, SOC 2 — easier to demonstrate when the hardware is yours.
- Customisation: Postgres extensions, custom auth flows, tweaked docker-compose configs — it's all open.
This guide walks you through the full self-host Supabase Docker setup: from a blank server to a production-hardened instance with HTTPS, secrets locked down, and Storage working. We also have a companion deep-dive — Self-Host Supabase Docker: Run Your Own Firebase Alternative With Full Control — which covers advanced customisation if you want to go further after this guide.
Prerequisites
Get these in place before you start. The setup will fail in hard-to-diagnose ways if any of these are missing.
Server Requirements
| Resource | Minimum | Recommended |
|---|---|---|
| RAM | 4 GB | 8 GB+ |
| CPU | 2 cores | 4 cores+ |
| Disk | 40 GB SSD | 80 GB+ SSD |
A $20/month VPS (2 vCPU, 4 GB RAM) handles small-to-medium workloads fine. Scale the disk generously — Postgres and Storage objects live there.
Software
- Ubuntu 22.04 or 24.04 (this guide uses Ubuntu)
- Docker Engine 24+ with Docker Compose v2
- Git
- A domain name with an A record pointing to your server (required for HTTPS)
- Port 80 and 443 open in your firewall; port 8000 for the API gateway
Verify Your Stack
docker --version # Docker version 24+
docker compose version # Docker Compose version v2+
git --version
Step 1 — Clone the Supabase Repository
Supabase ships its entire Docker configuration inside its main monorepo. You only need the docker/ subdirectory.
Option A: Quick Start Script (Linux Only)
If you're on a supported Linux distro (Debian/Ubuntu or RHEL/CentOS/Fedora), the official setup script handles everything — Docker install, sparse clone, secret generation, and URL prompts:
curl -fsSL https://supabase.link/setup.sh | sh
The script generates all JWT secrets, API keys, and a random dashboard password automatically, then prompts you for your domain URLs. If you go this route, skip to Step 3.
Option B: Manual Clone (All Platforms)
For more control — or on macOS/Windows — clone manually:
# Clone only the docker/ directory (sparse clone is faster)
git clone --depth 1 https://github.com/supabase/supabase
cd supabase/docker
# Copy the example env file
cp .env.example .env
Step 2 — Configure Secrets and Environment Variables
This is the step most guides rush. Every secret in the .env file defaults to an example value that is publicly known. You must change them before starting the stack — not after, not "later for production". Now.
Generate Secure Values
You need a JWT_SECRET (at least 32 characters, random), an ANON_KEY, and a SERVICE_ROLE_KEY. The anon and service role keys are JWTs signed with your JWT_SECRET.
# Generate a strong JWT secret
openssl rand -base64 48
# You can also use the Supabase JWT generator at:
# https://supabase.com/docs/guides/self-hosting/docker#generate-api-keys
# Paste your JWT_SECRET there to get pre-signed ANON_KEY and SERVICE_ROLE_KEY values
Edit the .env File
Open .env and update all the mandatory secrets. The key fields are:
############
# Secrets — CHANGE EVERY ONE OF THESE
############
POSTGRES_PASSWORD=your-strong-postgres-password
JWT_SECRET=your-long-random-jwt-secret-min-32-chars
ANON_KEY=eyJhb... # paste the generated anon JWT here
SERVICE_ROLE_KEY=eyJhb... # paste the generated service role JWT here
DASHBOARD_USERNAME=admin
DASHBOARD_PASSWORD=your-strong-dashboard-password
############
# API URLs — update to match your domain
############
SITE_URL=https://app.yourdomain.com
API_EXTERNAL_URL=https://api.yourdomain.com
SUPABASE_PUBLIC_URL=https://api.yourdomain.com
############
# SMTP — required for auth emails
############
[email protected]
SMTP_HOST=smtp.yourprovider.com
SMTP_PORT=587
SMTP_USER=your-smtp-username
SMTP_PASS=your-smtp-password
SMTP_SENDER_NAME=Your App Name
Critical:
ANON_KEYandSERVICE_ROLE_KEYmust be JWTs signed with the exactJWT_SECRETyou set. If these three values don't match, every API call will return 401s. Use the Supabase key generator to produce them correctly.
Postgres Configuration (Optional Tuning)
For production workloads, tune Postgres memory settings. Edit or create volumes/db/postgresql.conf overrides:
# volumes/db/postgresql.conf additions (tune to your server's RAM)
shared_buffers = 256MB # ~25% of total RAM
effective_cache_size = 768MB # ~75% of total RAM
work_mem = 16MB
maintenance_work_mem = 64MB
max_connections = 100
Step 3 — Start the Stack
With secrets configured, start all services:
# Pull images first (saves time — images are large)
docker compose pull
# Start all services detached
docker compose up -d
# Watch the logs to confirm everything comes up healthy
docker compose logs -f
The first boot takes 2–3 minutes as Postgres initialises and migrations run. You'll know it's ready when supabase-studio logs show it's listening on port 3000.
Check Service Health
# All containers should show "Up" or "Up (healthy)"
docker compose ps
# Hit the API gateway health endpoint
curl http://localhost:8000/rest/v1/ \
-H "apikey: YOUR_ANON_KEY" \
-H "Authorization: Bearer YOUR_ANON_KEY"
A 200 OK response confirms PostgREST is live and your JWT keys are correctly wired up.
Access Studio
By default, Supabase Studio runs on port 3000. Navigate to http://your-server-ip:3000 and log in with the DASHBOARD_USERNAME and DASHBOARD_PASSWORD you set in .env.
Step 4 — HTTPS with Nginx and Let's Encrypt
Plain HTTP is fine for local development. For any public-facing instance — especially if you're using OAuth providers or sending auth emails — HTTPS is required. Supabase's own docs recommend placing Nginx or Caddy in front of the Kong API gateway.
Install Nginx and Certbot
sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx
Nginx Site Config
Create /etc/nginx/sites-available/supabase. This proxies all traffic to Kong (port 8000) and Studio (port 3000) under separate subdomains — replace the domains with your own:
# API gateway
server {
listen 80;
server_name api.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
client_max_body_size 50M;
}
}
# Studio dashboard
server {
listen 80;
server_name studio.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Enable the site
sudo ln -s /etc/nginx/sites-available/supabase /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
# Issue certificates for both subdomains
sudo certbot --nginx -d api.yourdomain.com -d studio.yourdomain.com
Once Certbot runs, update API_EXTERNAL_URL, SUPABASE_PUBLIC_URL, and SITE_URL in your .env to use the https:// URLs, then restart the stack:
docker compose down
docker compose up -d
Step 5 — Post-Install Configuration
The stack is running and secured. Here's what to do next to make it production-ready.
Edge Functions
Edge Functions live in volumes/functions/. Create a subdirectory per function:
mkdir -p volumes/functions/hello-world
cat > volumes/functions/hello-world/index.ts << 'EOF'
Deno.serve(async (req) => {
return new Response(
JSON.stringify({ message: "Hello from self-hosted Edge Functions!" }),
{ headers: { "Content-Type": "application/json" } }
)
})
EOF
# Restart the functions service to pick up the new function
docker compose restart functions
Invoke it via the API gateway: POST https://api.yourdomain.com/functions/v1/hello-world
Row Level Security
Always enable RLS on tables that users can access. Without it, your anon key is effectively a root key to the entire table. In Studio, go to Table Editor → Your Table → RLS and enable it, then create policies for SELECT, INSERT, UPDATE, and DELETE.
Postgres Backups
Data in volumes/db/data is in a Docker volume. For automated backups, use pg_dump on a cron schedule:
# Run a dump from inside the Postgres container
docker exec supabase-db pg_dump \
-U postgres \
-d postgres \
--no-password \
| gzip > /backups/supabase-$(date +%Y%m%d-%H%M%S).sql.gz
# Add to crontab for nightly backups at 2am
# 0 2 * * * /path/to/backup-script.sh
Keeping Supabase Updated
Pull the latest images and restart. Your data is in named volumes — it survives image updates:
cd supabase/docker
git pull
docker compose pull
docker compose up -d
Step 6 — Troubleshooting Common Issues
These are the problems you'll actually encounter. Most have fast fixes once you know what to look for.
Problem: All API Calls Return 401
Your ANON_KEY or SERVICE_ROLE_KEY does not match the JWT_SECRET. This is the single most common self-hosting mistake.
Fix: Go to the Supabase key generator, paste your JWT_SECRET, copy the two generated keys, update .env, and restart the stack. The keys must be re-generated — you cannot manually edit a JWT and keep it valid.
Problem: Studio Shows "Unable to Connect"
Studio can't reach the API gateway. Check that Kong is running and healthy:
docker compose ps supabase-kong
docker compose logs supabase-kong --tail 50
# Manually verify the gateway is reachable on the host
curl -I http://localhost:8000
If Kong fails to start, it's usually a bad ANON_KEY or SERVICE_ROLE_KEY format — see above.
Problem: Auth Emails Not Sending
Check your SMTP_* variables in .env. Most cloud providers (AWS, GCP, DigitalOcean) block outbound port 25 by default — use port 587 or 465 with an SMTP relay (Resend, Postmark, SendGrid). Check the GoTrue auth container logs for SMTP errors:
docker compose logs supabase-auth --tail 100 | grep -i smtp
Problem: Realtime Connections Failing
Realtime validates JWTs on every WebSocket connection. If the JWT_SECRET is inconsistent across services, Realtime will reject connections silently. Confirm all services share the same secret:
grep JWT_SECRET .env
docker compose logs supabase-realtime --tail 50
Also confirm your Nginx config includes proxy_read_timeout 86400; for the /realtime/ location — WebSocket connections are long-lived and will be dropped without it.
Problem: Postgres Container Crashes on Restart
Usually a permissions issue on the volume mount. Fix with:
sudo chown -R 999:999 volumes/db/data
docker compose up -d supabase-db
Tip: Don't Expose Postgres Directly to the Internet
Port 5432 should never be open to the public. If you need direct Postgres access from a remote machine, use an SSH tunnel:
# Tunnel remote Postgres to local port 5433
ssh -L 5433:localhost:5432 user@your-server-ip -N
Then connect your client to localhost:5433. No firewall rule needed, no Postgres exposed.
Tip: Disable Sign-Ups for Private Deployments
If this is an internal app, you don't want open registration. In Studio, go to Authentication → Settings → Disable Sign-ups and toggle it off. You can still create users manually from the dashboard or via the service role key.
What You've Got
At this point your self-hosted Supabase setup gives you:
- A fully managed Postgres database with logical replication enabled
- GoTrue Auth — email/password, magic links, OAuth, phone OTP
- PostgREST auto-generating REST APIs from your schema
- Realtime for live subscriptions via WebSockets
- Supabase Storage for file uploads with S3-compatible access
- Edge Functions running Deno serverless handlers
- Studio — the full dashboard UI, locally hosted
- HTTPS via Nginx with auto-renewing Let's Encrypt certificates
That's the entire Firebase-alternative stack, running on a server you control. Want to go deeper on customising auth flows, adding Logflare analytics, or wiring up your own S3-compatible object store? Check out our companion guide: Self-Host Supabase Docker: Run Your Own Firebase Alternative With Full Control.
Need Production-Grade Infrastructure?
A single-server Docker setup is a great starting point. When you're ready to add high availability, automated backups to object storage, monitoring, zero-downtime deployments, or security hardening for compliance — that's where the architecture gets more complex.
The Sysbrix team builds and maintains production backend infrastructure for teams who want the benefits of self-hosting without the ops overhead. If you're evaluating whether self-hosting Supabase makes sense for your team at scale, we're happy to talk it through.