Self-host Supabase with Docker Compose without the CLI: custom registry setup
Run a production Supabase stack using plain Docker Compose without the CLI. Full setup with custom registries, environment variables, and inter-service networking.
This runbook extends the ECR 403 registry override guide. That one fixes local dev. This one covers production self-hosted deployments where you don’t want the CLI in the loop at all.
Symptoms
You want to run Supabase self-hosted but:
supabase startis not suitable for production (it’s a development-only command).- Your CI/CD pipeline can’t depend on the Supabase CLI being installed.
- You need images from a private registry, not public ECR.
- You need full control over environment variables, networking, and service versions.
Cause
The Supabase CLI bundles a hidden Docker Compose configuration that it generates at runtime. This means:
- You can’t pin exact versions in your IaC or GitOps flow.
- The CLI applies its own opinionated networking and volume setup.
- Upgrades happen when the CLI updates, not when you decide.
- Corporate environments that require image provenance and signing can’t audit CLI-generated configs.
The solution is to maintain your own docker-compose.yml that references Supabase images directly, with full version pinning and registry control.
Fix
Step 1: Create a base Docker Compose file
Start from the official Supabase self-hosting reference, then customise for your registry:
# docker-compose.yml
version: "3.8"
services:
postgres:
image: ${IMAGE_REGISTRY:-docker.io}/supabase/postgres:15.6.1.143
restart: unless-stopped
ports:
- "5432:5432"
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: postgres
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
auth:
image: ${IMAGE_REGISTRY:-docker.io}/supabase/gotrue:v2.158.1
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@postgres:5432/postgres
GOTRUE_SITE_URL: ${SITE_URL}
GOTRUE_URI_ALLOW_LIST: ${ADDITIONAL_REDIRECT_URLS:-}
GOTRUE_JWT_SECRET: ${JWT_SECRET}
GOTRUE_JWT_EXP: 3600
GOTRUE_EXTERNAL_EMAIL_ENABLED: "true"
GOTRUE_MAILER_AUTOCONFIRM: "false"
API_EXTERNAL_URL: ${API_EXTERNAL_URL}
rest:
image: ${IMAGE_REGISTRY:-docker.io}/supabase/postgrest:v12.2.3
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD}@postgres:5432/postgres
PGRST_DB_SCHEMAS: public,storage,graphql_public
PGRST_DB_ANON_ROLE: anon
PGRST_JWT_SECRET: ${JWT_SECRET}
realtime:
image: ${IMAGE_REGISTRY:-docker.io}/supabase/realtime:v2.30.34
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
DB_HOST: postgres
DB_PORT: "5432"
DB_USER: supabase_admin
DB_PASSWORD: ${POSTGRES_PASSWORD}
DB_NAME: postgres
SECRET_KEY_BASE: ${SECRET_KEY_BASE}
API_JWT_SECRET: ${JWT_SECRET}
storage:
image: ${IMAGE_REGISTRY:-docker.io}/supabase/storage-api:v1.0.6
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD}@postgres:5432/postgres
STORAGE_BACKEND: file
FILE_STORAGE_BACKEND_PATH: /var/lib/storage
ANON_KEY: ${ANON_KEY}
SERVICE_KEY: ${SERVICE_ROLE_KEY}
POSTGREST_URL: http://rest:3000
volumes:
- storage-data:/var/lib/storage
kong:
image: ${IMAGE_REGISTRY:-docker.io}/library/kong:3.5
restart: unless-stopped
ports:
- "8000:8000"
- "8443:8443"
environment:
KONG_DATABASE: "off"
KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml
volumes:
- ./kong.yml:/var/lib/kong/kong.yml:ro
volumes:
postgres-data:
storage-data:
Step 2: Create an .env file for secrets
# .env
IMAGE_REGISTRY=docker.io
POSTGRES_PASSWORD=your-strong-password-here
JWT_SECRET=your-jwt-secret-at-least-32-chars
ANON_KEY=your-anon-key
SERVICE_ROLE_KEY=your-service-role-key
SITE_URL=https://your-app.example.com
API_EXTERNAL_URL=https://api.example.com
SECRET_KEY_BASE=your-secret-key-base-at-least-64-chars
Step 3: Switch registries
To pull from your private ECR:
IMAGE_REGISTRY=123456789.dkr.ecr.eu-west-1.amazonaws.com
To pull from GHCR:
IMAGE_REGISTRY=ghcr.io
All services pick up the registry prefix from a single variable. No need to edit each image line.
Step 4: Start the stack
docker compose up -d
# Watch logs for startup errors
docker compose logs -f --tail=50
Step 5: Kong API gateway configuration
Create a kong.yml declarative config to route requests to the correct services:
# kong.yml
_format_version: "3.0"
services:
- name: auth
url: http://auth:9999
routes:
- name: auth-routes
paths:
- /auth/v1
strip_path: true
- name: rest
url: http://rest:3000
routes:
- name: rest-routes
paths:
- /rest/v1
strip_path: true
- name: realtime
url: http://realtime:4000
routes:
- name: realtime-routes
paths:
- /realtime/v1
strip_path: true
- name: storage
url: http://storage:5000
routes:
- name: storage-routes
paths:
- /storage/v1
strip_path: true
Validation
# 1. Confirm all containers are running
docker compose ps
# Expected: all services show "Up" status
# 2. Verify images came from the correct registry
docker compose images
# Expected: IMAGE column shows your registry prefix
# 3. Test PostgREST is responding
curl -s http://localhost:8000/rest/v1/ \
-H "apikey: ${ANON_KEY}" | head -c 100
# Expected: JSON response (empty array or schema info)
# 4. Test Auth (GoTrue) health
curl -s http://localhost:8000/auth/v1/health
# Expected: {"status":"ok"}
# 5. Test database connectivity
docker compose exec postgres pg_isready -U postgres
# Expected: accepting connections
If all five checks pass, your Supabase stack is running independently of the CLI. You own the versions, the registry source, and the full configuration. From here, integrate into your deployment pipeline by templating the .env file from your secrets manager (AWS Secrets Manager, Vault, etc.).
For teams that need image scanning and supply chain controls, see our guide on self-hosting Supabase on AWS without ECR which covers building and signing your own images.
Need production-grade Supabase on AWS?
Book a free 30-minute call. We build infrastructure for self-hosted Supabase with private registries, automated backups, and full observability.