Deploy Supabase with Dokploy: Docker Compose Setup Guide
Step-by-step guide to deploying Supabase on your VPS using Dokploy and Docker Compose. Includes PostgreSQL, Auth, Storage, Realtime, and Studio dashboard with SSL.
Deploy Supabase with Dokploy
Dokploy is an open-source server management platform that simplifies deploying Docker Compose applications on your VPS. It handles reverse proxy configuration, SSL certificates, and deployment management — which is valuable for running Supabase's complex multi-service architecture.
This guide walks you through deploying the full self-hosted Supabase stack: Studio dashboard, Kong API gateway, GoTrue authentication, PostgREST, Realtime, Storage, PostgreSQL database, and supporting services. Supabase is the most complex deployment in this guide series with 12+ interconnected services.
Prerequisites
- A VPS with at least 4 vCPUs, 4 GB RAM, and 50 GB storage
- Dokploy installed and running on your server (installation docs)
- A domain name (e.g.,
supabase.yourdomain.com) with DNS A record pointing to your server's IP - Pre-generated JWT keys (instructions below)
Generating JWT Keys
Supabase requires JWT tokens for API authentication. Generate them before deploying:
- Choose a strong
JWT_SECRET(at least 32 characters): e.g.,your-super-secret-jwt-token-at-least-32-chars - Generate
ANON_KEYandSERVICE_ROLE_KEYusing the Supabase JWT generator athttps://supabase.com/docs/guides/self-hosting#api-keysor use a JWT library to sign tokens with your secret
Docker Compose Configuration
Create a new Compose project in Dokploy and paste the following configuration:
version: "3.8"
services:
studio:
image: supabase/studio:latest
restart: unless-stopped
environment:
- STUDIO_DEFAULT_ORGANIZATION=Default Organization
- STUDIO_DEFAULT_PROJECT=Default Project
- STUDIO_PG_META_URL=http://meta:8080
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- DEFAULT_ORGANIZATION_NAME=Default Organization
- DEFAULT_PROJECT_NAME=Default Project
- SUPABASE_URL=http://kong:8000
- SUPABASE_PUBLIC_URL=https://${SUPABASE_DOMAIN}
- SUPABASE_ANON_KEY=${ANON_KEY}
- SUPABASE_SERVICE_KEY=${SERVICE_ROLE_KEY}
depends_on:
meta:
condition: service_healthy
kong:
condition: service_healthy
kong:
image: kong:2.8.1
restart: unless-stopped
ports:
- "8000:8000"
- "8443:8443"
environment:
- KONG_DATABASE=off
- KONG_DECLARATIVE_CONFIG=/var/lib/kong/kong.yml
- KONG_DNS_ORDER=LAST,A,CNAME
- KONG_PLUGINS=request-transformer,cors,key-auth,acl,basic-auth
- KONG_NGINX_PROXY_PROXY_BUFFER_SIZE=160k
- KONG_NGINX_PROXY_PROXY_BUFFERS=64 160k
- SUPABASE_ANON_KEY=${ANON_KEY}
- SUPABASE_SERVICE_KEY=${SERVICE_ROLE_KEY}
volumes:
- ../files/supabase-kong-config:/var/lib/kong
healthcheck:
test: ["CMD-SHELL", "kong health"]
interval: 10s
timeout: 5s
retries: 5
auth:
image: supabase/gotrue:v2.185.0
restart: unless-stopped
environment:
- GOTRUE_API_HOST=0.0.0.0
- GOTRUE_API_PORT=9999
- API_EXTERNAL_URL=https://${SUPABASE_DOMAIN}
- GOTRUE_DB_DRIVER=postgres
- GOTRUE_DB_DATABASE_URL=postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@db:5432/postgres
- GOTRUE_SITE_URL=https://${SUPABASE_DOMAIN}
- GOTRUE_URI_ALLOW_LIST=
- GOTRUE_JWT_ADMIN_ROLES=service_role
- GOTRUE_JWT_AUD=authenticated
- GOTRUE_JWT_DEFAULT_GROUP_NAME=authenticated
- GOTRUE_JWT_EXP=3600
- GOTRUE_JWT_SECRET=${JWT_SECRET}
- GOTRUE_DISABLE_SIGNUP=false
- GOTRUE_EXTERNAL_EMAIL_ENABLED=true
- GOTRUE_MAILER_AUTOCONFIRM=true
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:9999/health || exit 1"]
interval: 10s
timeout: 5s
retries: 5
rest:
image: postgrest/postgrest:v12.2.3
restart: unless-stopped
environment:
- PGRST_DB_URI=postgres://authenticator:${POSTGRES_PASSWORD}@db:5432/postgres
- PGRST_DB_SCHEMAS=public,storage,graphql_public
- PGRST_DB_ANON_ROLE=anon
- PGRST_JWT_SECRET=${JWT_SECRET}
- PGRST_DB_USE_LEGACY_GUCS=false
- PGRST_APP_SETTINGS_JWT_SECRET=${JWT_SECRET}
- PGRST_APP_SETTINGS_JWT_EXP=3600
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/ready || exit 1"]
interval: 10s
timeout: 5s
retries: 5
realtime:
image: supabase/realtime:v2.30.34
restart: unless-stopped
environment:
- PORT=4000
- DB_HOST=db
- DB_PORT=5432
- DB_USER=supabase_admin
- DB_PASSWORD=${POSTGRES_PASSWORD}
- DB_NAME=postgres
- DB_AFTER_CONNECT_QUERY=SET search_path TO _realtime
- DB_ENC_KEY=supabaserealtime
- API_JWT_SECRET=${JWT_SECRET}
- SECRET_KEY_BASE=${JWT_SECRET}
- ERL_AFLAGS=-proto_dist inet_tcp
- DNS_NODES=
- RLIMIT_NOFILE=10000
depends_on:
db:
condition: service_healthy
storage:
image: supabase/storage-api:v1.11.13
restart: unless-stopped
environment:
- ANON_KEY=${ANON_KEY}
- SERVICE_KEY=${SERVICE_ROLE_KEY}
- POSTGREST_URL=http://rest:3000
- PGRST_JWT_SECRET=${JWT_SECRET}
- DATABASE_URL=postgres://supabase_storage_admin:${POSTGRES_PASSWORD}@db:5432/postgres
- FILE_SIZE_LIMIT=52428800
- STORAGE_BACKEND=file
- FILE_STORAGE_BACKEND_PATH=/var/lib/storage
- REGION=us-east-1
- GLOBAL_S3_BUCKET=stub
volumes:
- ../files/supabase-storage:/var/lib/storage
depends_on:
db:
condition: service_healthy
rest:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:5000/status || exit 1"]
interval: 10s
timeout: 5s
retries: 5
meta:
image: supabase/postgres-meta:v0.84.2
restart: unless-stopped
environment:
- PG_META_PORT=8080
- PG_META_DB_HOST=db
- PG_META_DB_PORT=5432
- PG_META_DB_NAME=postgres
- PG_META_DB_USER=supabase_admin
- PG_META_DB_PASSWORD=${POSTGRES_PASSWORD}
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:8080/health || exit 1"]
interval: 10s
timeout: 5s
retries: 5
db:
image: supabase/postgres:15.8.1.085
restart: unless-stopped
environment:
- POSTGRES_HOST=/var/run/postgresql
- PGPORT=5432
- POSTGRES_PORT=5432
- PGPASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- PGDATABASE=postgres
- POSTGRES_DB=postgres
- JWT_SECRET=${JWT_SECRET}
- JWT_EXP=3600
volumes:
- ../files/supabase-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -h localhost"]
interval: 10s
timeout: 5s
retries: 10
command:
- postgres
- -c
- wal_level=logical
- -c
- max_slot_wal_keep_size=1024
- -c
- max_replication_slots=5
- -c
- max_connections=150
Note: This is a simplified Supabase deployment suitable for development and small production use. The full Supabase stack includes additional services (Analytics/Logflare, Edge Functions, Vector, Supavisor) which can be added as needed. Kong requires a configuration file — see the setup instructions below.
Kong Configuration
Before deploying, create the Kong declarative config file at ../files/supabase-kong-config/kong.yml on your server. You can find the official Kong config in the Supabase Docker repository. Replace the placeholder JWT keys in that file with your actual ANON_KEY and SERVICE_ROLE_KEY.
Environment Variables
Set these in Dokploy's Environment tab for your compose project:
| Variable | Purpose | Example |
|---|---|---|
SUPABASE_DOMAIN |
Your Supabase domain | supabase.yourdomain.com |
POSTGRES_PASSWORD |
PostgreSQL superuser password | a-very-strong-password |
JWT_SECRET |
JWT signing secret (32+ characters) | your-super-secret-jwt-token-at-least-32-chars |
ANON_KEY |
Anonymous role JWT token | (generated from JWT_SECRET) |
SERVICE_ROLE_KEY |
Service role JWT token | (generated from JWT_SECRET) |
In Dokploy, environment variables are set via the Environment editor in the project settings. Do not create a .env file manually — Dokploy manages this for you. The JWT secret must be consistent across all services — using a single environment variable ensures this.
Volumes & Data Persistence
This setup uses Dokploy's ../files convention for bind-mounted volumes:
../files/supabase-db-data— PostgreSQL database (all user data, auth records, schemas)../files/supabase-storage— User-uploaded files via Supabase Storage API../files/supabase-kong-config— Kong API gateway declarative configuration
The ../files path is relative to the compose file inside Dokploy's project directory. This ensures your data persists across redeployments. The database volume is critical — back it up regularly.
If you need S3 backup support, consider using named Docker volumes instead. Named volumes can be backed up with Dokploy's built-in backup features. For file storage specifically, Supabase also supports S3-compatible backends by changing STORAGE_BACKEND=s3.
Domain & SSL Setup
- In your Dokploy project, navigate to the Domains tab
- Click Add Domain and enter your domain (e.g.,
supabase.yourdomain.com) - Set the container port to
8000(Kong API gateway) - Enable HTTPS — Dokploy automatically provisions a Let's Encrypt SSL certificate
- Save and wait for the certificate to be issued (usually under a minute)
Dokploy's built-in Traefik reverse proxy handles TLS termination and routes traffic to Kong, which acts as the API gateway for all Supabase services. The Studio dashboard is accessible through Kong at / when properly configured.
Verifying the Deployment
- Create the Kong configuration file in
../files/supabase-kong-config/kong.ymlbefore deploying - In Dokploy, go to your project's Deployments tab and click Deploy
- Watch the build logs — all services should start (db first, then dependent services)
- Check the Logs tab for the
dbservice to confirm PostgreSQL is ready - Check Kong logs to verify all upstream services are reachable
- Open
https://supabase.yourdomain.comin your browser — you should see the Supabase Studio dashboard - Log in and verify you can create tables, manage users, and test the API
Troubleshooting
Services fail to start with connection errors
Supabase services have strict dependency ordering. The database must be fully healthy before other services start. If services restart repeatedly, check the db service logs first. Ensure POSTGRES_PASSWORD is set and consistent across all services.
Kong returns "no Route matched" or 404 errors
Verify the Kong configuration file exists at ../files/supabase-kong-config/kong.yml and contains the correct service definitions. The JWT keys in the Kong config must match your ANON_KEY and SERVICE_ROLE_KEY environment variables.
Auth (GoTrue) returns JWT errors
Ensure JWT_SECRET is at least 32 characters and is identical across all services. Regenerate ANON_KEY and SERVICE_ROLE_KEY if you change the JWT secret. Token expiry defaults to 3600 seconds (1 hour).
Studio dashboard not loading
Studio connects to meta and kong services. Check that both are healthy in Dokploy's logs. Verify SUPABASE_URL points to http://kong:8000 (internal) and SUPABASE_PUBLIC_URL points to your external HTTPS domain.
Database runs out of connections
The default max_connections=150 may not be enough for heavy workloads. Increase it in the db service command section. Consider adding Supavisor (connection pooler) for production deployments.
Learn more about Supabase in our complete overview.
Need a VPS? Hostinger VPS starts at $4.99/mo — perfect for running Supabase.
For more on Docker Compose deployments in Dokploy, see the Dokploy Docker Compose documentation.
App data sourced from selfh.st open-source directory.
Ready to get started?
Get the best VPS hosting deal today. Hostinger offers 4GB RAM VPS starting at just $4.99/mo.
Get Hostinger VPS — $4.99/mo// up to 75% off + free domain included
// related topics
fordnox
Expert VPS reviews and hosting guides. We test every provider we recommend.
// last updated: February 12, 2026. Disclosure: This article may contain affiliate links.