DOKPLOY-GUIDE 12 min read fordnox

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

Generating JWT Keys

Supabase requires JWT tokens for API authentication. Generate them before deploying:

  1. Choose a strong JWT_SECRET (at least 32 characters): e.g., your-super-secret-jwt-token-at-least-32-chars
  2. Generate ANON_KEY and SERVICE_ROLE_KEY using the Supabase JWT generator at https://supabase.com/docs/guides/self-hosting#api-keys or 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:

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

  1. In your Dokploy project, navigate to the Domains tab
  2. Click Add Domain and enter your domain (e.g., supabase.yourdomain.com)
  3. Set the container port to 8000 (Kong API gateway)
  4. Enable HTTPS — Dokploy automatically provisions a Let's Encrypt SSL certificate
  5. 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

  1. Create the Kong configuration file in ../files/supabase-kong-config/kong.yml before deploying
  2. In Dokploy, go to your project's Deployments tab and click Deploy
  3. Watch the build logs — all services should start (db first, then dependent services)
  4. Check the Logs tab for the db service to confirm PostgreSQL is ready
  5. Check Kong logs to verify all upstream services are reachable
  6. Open https://supabase.yourdomain.com in your browser — you should see the Supabase Studio dashboard
  7. 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.

~/self-hosted-app/supabase/dokploy/get-started

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

supabase dokploy docker compose self-hosted firebase alternative supabase deployment

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.