docker compose up -d after configuring .env with unique secrets (JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY, POSTGRES_PASSWORD)..env.example -- this is the #1 security mistake; always run generate-keys.sh first.| 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 |
| 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 |
| 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 |
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
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.
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).
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).
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"}.
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.
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.
# docker-compose.override.yml -- Disable optional services to save resources
services:
analytics:
profiles: ["disabled"]
vector:
profiles: ["disabled"]
imgproxy:
profiles: ["disabled"]
functions:
profiles: ["disabled"]
#!/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"
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})`);
}
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")
# 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
# GOOD -- generate unique secrets before starting
cp .env.example .env
sh ./utils/generate-keys.sh
docker compose up -d
# BAD -- exposing internal service ports
services:
rest:
ports: ["3000:3000"]
auth:
ports: ["9999:9999"]
db:
ports: ["5432:5432"]
# GOOD -- only Kong is exposed
services:
kong:
ports:
- "${KONG_HTTP_PORT:-8000}:8000/tcp"
- "${KONG_HTTPS_PORT:-8443}:8443/tcp"
# BAD -- special characters break connection string parsing
POSTGRES_PASSWORD=p@ss!w0rd#2026
# GOOD -- letters and numbers only
POSTGRES_PASSWORD=SuperSecure2026RandomString
# BAD -- -v removes all volumes including database data
docker compose down -v # ALL DATA IS NOW GONE
# GOOD -- restart without touching data
docker compose down
docker compose up -d
docker compose up; specify platform: linux/amd64 on ARM. [src6]grep JWT_SECRET .env shows one consistent value, then restart all services. [src1]proxy_http_version 1.1 and proxy_set_header Upgrade/Connection headers. [src7]IMGPROXY_URL= (empty) if disabled. [src2]pg_dump before pulling new images. [src1]docker compose logs <service>. [src2]postgres://postgres.POOLER_TENANT_ID:PASSWORD@host:6543/postgres. [src4]# 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;"
| 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 |
| 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 |
docker compose pull may introduce breaking changes -- always read the release notes and back up volumes/db/data before upgrading