Docker Compose Reference: Supabase Self-Hosted
Docker Compose reference: Supabase Self-Hosted
TL;DR
- Bottom line: Supabase self-hosted runs 13 containerized services orchestrated by Docker Compose, with Kong as the single API gateway on port 8000 routing to PostgreSQL, GoTrue (Auth), PostgREST, Realtime, Storage, Edge Functions, Studio, and supporting services.
- Key tool/command:
docker compose up -dafter configuring.envwith unique secrets (JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY, POSTGRES_PASSWORD). - Watch out for: Starting with default placeholder passwords from
.env.example-- this is the #1 security mistake; always rungenerate-keys.shfirst. - Works with: Docker Engine 20.10+, Docker Compose v2+, minimum 4 GB RAM / 2 CPU cores (8 GB / 4 cores recommended for production).
Constraints
- NEVER start Supabase with default placeholder passwords from .env.example -- generate unique secrets before first boot
- SERVICE_ROLE_KEY has full database access -- NEVER expose it in client-side code or public repositories
- POSTGRES_PASSWORD must contain only letters and numbers (no special characters) to avoid connection string parsing issues
- JWT_SECRET must be identical across GoTrue, PostgREST, Realtime, and Kong -- mismatched secrets cause silent auth failures
- Docker Compose volumes store persistent data in ./volumes/ -- deleting volumes/db/data destroys all database content irreversibly
- Kong API gateway is the single entry point on port 8000 -- do NOT expose individual service ports to the public internet
Quick Reference
Service Architecture
| Service | Image | Ports | Volumes | Key Env |
|---|---|---|---|---|
| db (PostgreSQL) | supabase/postgres:15.8.1.085 | 5432 | volumes/db/data | POSTGRES_PASSWORD |
| kong (API Gateway) | kong:2.8.1 | 8000, 8443 | volumes/api/kong.yml | DASHBOARD_USERNAME, DASHBOARD_PASSWORD |
| auth (GoTrue) | supabase/gotrue:v2.186.0 | Internal only | None | GOTRUE_JWT_SECRET, GOTRUE_DB_DATABASE_URL |
| rest (PostgREST) | postgrest/postgrest:v14.5 | Internal only | None | PGRST_DB_URI, PGRST_JWT_SECRET |
| realtime | supabase/realtime:v2.76.5 | 4000 (internal) | None | SECRET_KEY_BASE, DB_HOST |
| storage | supabase/storage-api:v1.37.8 | 5000 (internal) | volumes/storage | STORAGE_BACKEND, FILE_SIZE_LIMIT |
| imgproxy | darthsim/imgproxy:v3.30.1 | 5001 (internal) | volumes/storage (read) | IMGPROXY_ENABLE_WEBP_DETECTION |
| studio (Dashboard) | supabase/studio:2026.02.16 | Internal only | None | STUDIO_DEFAULT_ORGANIZATION |
| meta (pg_meta) | supabase/postgres-meta:v0.95.2 | 8080 (internal) | None | PG_META_DB_HOST |
| functions (Edge) | supabase/edge-runtime:v1.70.3 | Internal only | volumes/functions | SUPABASE_URL, SUPABASE_ANON_KEY |
| analytics (Logflare) | supabase/logflare:1.31.2 | 4000 (internal) | None | LOGFLARE_API_KEY |
| vector (Logging) | timberio/vector:0.53.0-alpine | 9001 | volumes/logs/vector.yml | Vector pipeline config |
| supavisor (Pooler) | supabase/supavisor:2.7.4 | 5432, 6543 | volumes/pooler.exs | POOLER_TENANT_ID, SECRET_KEY_BASE |
Key Environment Variables
| Variable | Purpose | Requirements |
|---|---|---|
| POSTGRES_PASSWORD | Database superuser password | Letters and numbers only |
| JWT_SECRET | Signs and verifies all JWTs | Shared across auth, rest, realtime, kong |
| ANON_KEY | Client-side API key (anon role) | Generated from JWT_SECRET |
| SERVICE_ROLE_KEY | Server-side API key (full access) | Never expose publicly |
| SECRET_KEY_BASE | Encrypts Realtime + Supavisor sessions | Minimum 64 characters |
| VAULT_ENC_KEY | Configuration encryption | Exactly 32 characters |
| DASHBOARD_USERNAME | Studio HTTP basic auth username | Default: supabase |
| DASHBOARD_PASSWORD | Studio HTTP basic auth password | Must contain letters |
Service Endpoints (via Kong on port 8000)
| Endpoint | Service | Description |
|---|---|---|
| /rest/v1/ | PostgREST | Auto-generated REST API from PostgreSQL schema |
| /auth/v1/ | GoTrue | Authentication (signup, login, OAuth, JWT refresh) |
| /realtime/v1/ | Realtime | WebSocket subscriptions to database changes |
| /storage/v1/ | Storage API | File upload, download, and management |
| /functions/v1/ | Edge Runtime | Serverless Deno functions |
| / (Studio) | Studio | Web dashboard for database management |
Decision Tree
START: What is your deployment scenario?
├── Local development only?
│ ├── YES → Use default ports, skip SSL. Consider Supabase CLI instead.
│ └── NO ↓
├── Single server / VPS?
│ ├── YES → Full docker-compose + reverse proxy (Nginx/Traefik) + SSL certs
│ └── NO ↓
├── Need to reduce resource usage?
│ ├── YES → Remove optional services: analytics, imgproxy, functions, vector
│ └── NO ↓
├── Need external S3 storage?
│ ├── YES → Set STORAGE_BACKEND=s3 + S3 credentials in .env
│ └── NO ↓
├── Production with high availability?
│ ├── YES → Consider Supabase on Kubernetes or managed Supabase Cloud
│ └── NO ↓
└── DEFAULT → Full stack docker-compose with all services enabled
Step-by-Step Guide
1. Clone and prepare the project directory
Clone the Supabase repository and copy the Docker configuration files to your project directory. [src1]
# Clone the repository (shallow clone for speed)
git clone --depth 1 https://github.com/supabase/supabase
# Create your project directory and copy Docker files
mkdir supabase-project
cp -rf supabase/docker/* supabase-project/
cp supabase/docker/.env.example supabase-project/.env
cd supabase-project
Verify: ls -la .env docker-compose.yml → both files should exist in the project directory.
2. Generate and configure secrets
Never use the default placeholder values. Generate unique secrets for all sensitive environment variables. [src1]
# Generate all secrets automatically
sh ./utils/generate-keys.sh
# Or generate manually with openssl:
openssl rand -base64 32 # JWT_SECRET
Verify: grep -c 'your-super-secret' .env → should return 0 (no placeholder values remain).
3. Pull images and start services
Pull the latest images and start all services in detached mode. [src2]
docker compose pull
docker compose up -d
docker compose ps
Verify: docker compose ps → all services should show Up (healthy).
4. Access and verify services
Once all services are healthy, verify each endpoint through the Kong gateway. [src1]
# Test Auth API health
curl -s http://localhost:8000/auth/v1/health
# Expected: {"status":"ok"}
Verify: curl -s http://localhost:8000/auth/v1/health → {"status":"ok"}.
5. Connect client SDK
Initialize the Supabase client SDK pointing to your self-hosted instance. [src1]
import { createClient } from '@supabase/supabase-js' // ^2.39.0
const supabase = createClient(
'http://localhost:8000', // Kong gateway URL
'your-anon-key-here' // ANON_KEY from .env
)
Verify: Client connects without JWT or network errors.
6. Configure reverse proxy for production
For production, place Nginx or Traefik in front of Kong to handle SSL termination. [src7]
# Nginx config with WebSocket support for Realtime
location /realtime/v1/ {
proxy_pass http://localhost:8000/realtime/v1/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
Verify: curl -s https://supabase.yourdomain.com/auth/v1/health → {"status":"ok"} over HTTPS.
Code Examples
Docker Compose: Minimal Core Services
# docker-compose.override.yml -- Disable optional services to save resources
services:
analytics:
profiles: ["disabled"]
vector:
profiles: ["disabled"]
imgproxy:
profiles: ["disabled"]
functions:
profiles: ["disabled"]
Bash: Automated Backup Script
#!/bin/bash
# Backup PostgreSQL from the db container
BACKUP_DIR="./backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
docker compose exec -T db pg_dump \
-U postgres --clean --if-exists --no-owner \
postgres > "${BACKUP_DIR}/supabase_${TIMESTAMP}.sql"
echo "Backup: ${BACKUP_DIR}/supabase_${TIMESTAMP}.sql"
JavaScript: Health Check All Services
const BASE = process.env.SUPABASE_URL || 'http://localhost:8000';
const KEY = process.env.SUPABASE_ANON_KEY;
const endpoints = [
{ name: 'Auth', path: '/auth/v1/health' },
{ name: 'REST', path: '/rest/v1/', headers: { apikey: KEY } },
{ name: 'Storage', path: '/storage/v1/status' },
];
for (const ep of endpoints) {
const res = await fetch(`${BASE}${ep.path}`, { headers: ep.headers });
console.log(`${ep.name}: ${res.ok ? 'OK' : 'FAIL'} (${res.status})`);
}
Python: Connect to Self-Hosted Supabase
from supabase import create_client # supabase>=2.0.0
supabase = create_client(
"http://localhost:8000",
"your-anon-key-here"
)
result = supabase.table("users").select("*").limit(1).execute()
print(f"Connected: {len(result.data)} rows")
Anti-Patterns
Wrong: Starting with default secrets
# BAD -- .env.example has publicly known placeholder values
cp .env.example .env
docker compose up -d
# Anyone who reads the Supabase repo now has your JWT_SECRET
Correct: Generate unique secrets before first boot
# GOOD -- generate unique secrets before starting
cp .env.example .env
sh ./utils/generate-keys.sh
docker compose up -d
Wrong: Exposing service ports directly
# BAD -- exposing internal service ports
services:
rest:
ports: ["3000:3000"]
auth:
ports: ["9999:9999"]
db:
ports: ["5432:5432"]
Correct: Route all traffic through Kong
# GOOD -- only Kong is exposed
services:
kong:
ports:
- "${KONG_HTTP_PORT:-8000}:8000/tcp"
- "${KONG_HTTPS_PORT:-8443}:8443/tcp"
Wrong: Special characters in POSTGRES_PASSWORD
# BAD -- special characters break connection string parsing
POSTGRES_PASSWORD=p@ss!w0rd#2026
Correct: Alphanumeric passwords only
# GOOD -- letters and numbers only
POSTGRES_PASSWORD=SuperSecure2026RandomString
Wrong: Using docker compose down -v to "restart"
# BAD -- -v removes all volumes including database data
docker compose down -v # ALL DATA IS NOW GONE
Correct: Restart without removing volumes
# GOOD -- restart without touching data
docker compose down
docker compose up -d
Common Pitfalls
- Kong fails with "kong.yml is a directory": Docker creates kong.yml as a directory on some platforms. Fix: Delete the directory and ensure the file exists before
docker compose up; specifyplatform: linux/amd64on ARM. [src6] - Auth/REST return 401 on valid tokens: JWT_SECRET mismatch between services. Fix: Verify
grep JWT_SECRET .envshows one consistent value, then restart all services. [src1] - Realtime WebSockets fail behind reverse proxy: Nginx/Traefik not configured for WebSocket upgrade. Fix: Add
proxy_http_version 1.1andproxy_set_header Upgrade/Connectionheaders. [src7] - Storage uploads fail with "connection refused": Storage API cannot reach imgproxy. Fix: Ensure imgproxy is running or set
IMGPROXY_URL=(empty) if disabled. [src2] - Database data lost after docker compose pull: The volumes/db/data was not persisted. Fix: Always back up with
pg_dumpbefore pulling new images. [src1] - Studio shows "Invalid API key": ANON_KEY not properly generated from JWT_SECRET. Fix: Regenerate keys using the Supabase key generator. [src5]
- Services fail to start in order: Health checks must pass for depends_on conditions. Fix: Check logs with
docker compose logs <service>. [src2] - Connection pooling not working via port 6543: Supavisor requires POOLER_TENANT_ID in connection string. Fix: Use
postgres://postgres.POOLER_TENANT_ID:PASSWORD@host:6543/postgres. [src4]
Diagnostic Commands
# Check health of all services
docker compose ps
# View logs for a specific service
docker compose logs --tail 100 auth
# Check if PostgreSQL is accepting connections
docker compose exec db pg_isready -U postgres
# Verify JWT_SECRET consistency across services
docker compose exec auth printenv | grep JWT
docker compose exec rest printenv | grep JWT
# Test Kong routing to auth service
curl -v http://localhost:8000/auth/v1/health
# Monitor real-time resource usage
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"
# Check Kong declarative config
docker compose exec kong kong config parse /var/lib/kong/kong.yml
# Verify database extensions
docker compose exec db psql -U postgres -c "SELECT * FROM pg_extension;"
Version History & Compatibility
| Component | Current Version | Image | Key Change |
|---|---|---|---|
| PostgreSQL | 15.8.1 | supabase/postgres:15.8.1.085 | Includes pg_net, pg_graphql, pgvector extensions |
| GoTrue (Auth) | v2.186.0 | supabase/gotrue:v2.186.0 | PKCE flow, anonymous auth, SSO/SAML support |
| PostgREST | v14.5 | postgrest/postgrest:v14.5 | Improved aggregate functions, spreads |
| Realtime | v2.76.5 | supabase/realtime:v2.76.5 | Broadcast, Presence, Postgres Changes |
| Storage API | v1.37.8 | supabase/storage-api:v1.37.8 | S3-compatible backend, resumable uploads |
| Kong | 2.8.1 | kong:2.8.1 | Pinned version; declarative config via kong.yml |
| Studio | 2026.02 | supabase/studio:2026.02.16 | SQL editor, table editor, auth UI |
| Edge Runtime | v1.70.3 | supabase/edge-runtime:v1.70.3 | Deno-based functions, npm compatibility |
| Supavisor | 2.7.4 | supabase/supavisor:2.7.4 | Replaced PgBouncer; session + transaction modes |
| imgproxy | v3.30.1 | darthsim/imgproxy:v3.30.1 | WebP/AVIF auto-detection |
| Vector | 0.53.0 | timberio/vector:0.53.0-alpine | Log aggregation pipeline |
| Logflare | 1.31.2 | supabase/logflare:1.31.2 | Analytics and log querying |
| pg_meta | v0.95.2 | supabase/postgres-meta:v0.95.2 | Database introspection for Studio |
When to Use / When Not to Use
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Need full control over data residency and compliance | Quick prototyping or MVP with small team | Supabase Cloud (free tier available) |
| Deploying in air-gapped or restricted networks | Budget under $20/month for infrastructure | Supabase Cloud Pro plan |
| Custom PostgreSQL extensions or configurations needed | No DevOps expertise on the team | Supabase Cloud or managed hosting |
| Cost optimization at scale (many projects, high traffic) | Need automatic updates and zero maintenance | Supabase Cloud with automatic upgrades |
| Regulatory requirement for on-premises hosting | Single developer with limited time | Supabase Cloud + CLI for local dev |
Important Caveats
- Supabase publishes stable Docker Compose releases approximately once a month -- always check the GitHub repository for the latest image tags before updating
- The self-hosted version does not include some Supabase Cloud features: email rate limiting, abuse prevention, automatic backups, and platform-level monitoring
- Updating images with
docker compose pullmay introduce breaking changes -- always read the release notes and back up volumes/db/data before upgrading - Kong 2.8.1 is the pinned version in the official compose file; upgrading Kong independently may break routing configuration
- If using S3-compatible storage, both the Storage API and imgproxy must be configured with the same S3 credentials
- Edge Functions require mounting the volumes/functions/ directory -- each function must be in its own subdirectory with an index.ts entry point
- Supavisor listens on port 5432 (session mode) and 6543 (transaction mode) -- do not confuse these with the direct PostgreSQL port on the db container