Why self-host Supabase on a VPS
Supabase is not a black box: it is a set of open source services (PostgreSQL, GoTrue for auth, PostgREST for the API, Realtime, Storage, Kong as a gateway) orchestrated together. The cloud version bills per project and per monthly active user, and pauses inactive free projects. By self-hosting on a VPS, you get a complete backend-as-a-service without those limits, with direct access to your PostgreSQL for migrations, extensions (pgvector, PostGIS) and tuning. Ideal for an agency hosting several client apps, a bootstrapped SaaS looking to control costs, or any project requiring data sovereignty. You handle updates and backups yourself, in exchange for total freedom.
Concrete benefits of a self-hosted Supabase
- Complete backend: Postgres, Auth, Storage, Realtime and REST/GraphQL API in one stack.
- Direct access to the underlying PostgreSQL: SQL migrations, extensions and RLS policies without limits.
- No pausing or billing per monthly active user.
- Studio included: a web administration interface to manage tables, auth and buckets.
- AI extensions via
pgvectorfor RAG and semantic search, freely enabled. - Multi-projects on the same VPS: practical for an agency or development studio.
Hardware and software requirements
Self-hosted Supabase starts around ten containers (Postgres, Kong, GoTrue, PostgREST, Realtime, Storage, Studio, Meta, Imgproxy...), making it a more resource-intensive stack than a plain database. Count a minimum of 2 vCPU and 4 GB of RAM for a test environment, and prefer 4 vCPU with 8 GB of RAM and 80 GB of SSD for production. On the software side: Ubuntu 22.04/24.04 LTS, Docker and Docker Compose v2, a domain name pointing to the VPS (e.g. api.myapp.com) to expose the gateway over HTTPS, and a reverse proxy such as Caddy, Traefik or Nginx to handle SSL. Also plan to generate strong secrets (JWT_SECRET, anon and service_role keys).
Deploy self-hosted Supabase with Docker
Clone the official repository and prepare the environment
Fetch the
dockerfolder from the Supabase repository, copy.env.exampleto.env, then generate unique secrets:POSTGRES_PASSWORD,JWT_SECRET, and theANON_KEY/SERVICE_ROLE_KEYkeys derived from the JWT. Never deploy with the example values.Configure URLs and the Studio password
In
.env, setSITE_URL,API_EXTERNAL_URLandSUPABASE_PUBLIC_URLwith your domain, and secure Supabase Studio withDASHBOARD_USERNAMEandDASHBOARD_PASSWORD: Studio gives full admin access to your project.Launch the stack
Start with
docker compose up -d, then follow service startup viadocker compose ps. The first Postgres initialisation and internal migration application take one to two minutes; check for the absence of errors indocker compose logs.Place a reverse proxy with SSL
Put Caddy or Traefik in front of Kong (the gateway's port 8000) to expose your API over HTTPS. Caddy automatically obtains and renews Let's Encrypt certificates: a simple
myapp.com { reverse_proxy localhost:8000 }block is enough. Never expose Postgres (5432) publicly.Enable RLS policies
Connect to Studio, create your tables, then enable Row Level Security on each one and define your policies. Without RLS, your
anonkey exposes all your data: this is the setting to configure before exposing anything.Back up Postgres and Storage
Schedule a daily
pg_dumpof the database and archive the Storage volume (file buckets) to external storage. Test a full restoration on a staging VPS before relying on it in production.
Supabase vs Appwrite: which self-hosted BaaS to choose?
Scroll the table
| Criterion | Supabase | Appwrite |
|---|---|---|
| Database | Native PostgreSQL, full SQL | MariaDB internally, document/collection API |
| Data model | Relational, Postgres RLS | Documents and collections, rule-based permissions |
| Auto-generated API | REST (PostgREST) + GraphQL | REST, GraphQL and multiple SDKs |
| Authentication | GoTrue, OAuth, magic links | Integrated auth, OAuth, teams, JWT |
| Resource footprint | Heavier (~10 containers) | Moderate, more compact stack |
| Vector search / AI | Native via pgvector | Not native, must be externalised |
| Best for | Relational apps, RAG, advanced SQL | Mobile/web apps oriented around documents |
| Learning curve | Comfortable if you know SQL | Fast for front/mobile developers |
Update Supabase with care: since images are versioned in docker-compose.yml, always do a full pg_dump and a VPS snapshot before a docker compose pull && docker compose up -d. Some version bumps touch the internal service schema. For production, isolate Postgres on a dedicated high-IOPS volume and enable pgvector from the start if you plan on semantic search: adding it later requires a schema migration.
Deploy from the official Supabase stack, not a homemade compose
It is tempting to write a minimal docker-compose with only the few services you need. In practice, this breaks: the supabase/postgres image runs its own migration script (migrate.sh) that provisions the base roles (supabase_admin, authenticator, supabase_auth_admin...) and the entire auth schema, and expects Supabase SQL initialisation files to be mounted as files with variable expansion at run time. A standalone inline compose cannot reproduce this (Docker Compose interpolates variables inside inline configs, leaving the auth schema half-migrated and GoTrue failing with 'must be owner of function auth.uid()'). The robust approach — the one ServOrbit uses — is to deploy the official Supabase Docker files, pinned to a specific version, and let migrate.sh do the bootstrapping exactly as upstream intends.
Back your instance up automatically
Supabase's Docker deployment backs up nothing on its own — it is the gap self-hosters report most. Two mechanisms complement each other. The first, pg_dump, produces a full logical copy: run it from the host on a cron, write it to a volume separate from the database's own, and keep several generations. The second, WAL archiving, journals every transaction and lets you rewind to a precise moment rather than to the last dump. The first is enough for most projects; the second becomes necessary as soon as losing a day of writes is unacceptable. Either way, a backup is only worth what a restore proves: spin up a blank instance and replay your dump into it before you actually need to.
Trimming the stack: the analytics service
The official Docker composition ships Logflare, Supabase's built-in analytics service, under the name analytics. It is required by neither the API, nor auth, nor storage — it is an observability tool. On a resource-tight VPS it commonly reports unhealthy without the rest of the stack suffering, and many self-hosters end up commenting it out of their docker-compose.yml. Measure what it actually costs you with docker stats before deciding: if your monitoring already runs through an external tool you lose nothing; if you were relying on its logs, disable it only once a replacement is wired in.
Kong→Envoy migration: what changes, and who it breaks
In the week of 9 August 2026, Supabase made Envoy the default API gateway for self-hosted installations; Kong moves to an optional override. Envoy had already shipped for several releases via docker-compose.envoy.yml — what changes is the default.
In docker-compose.yml the service is now called api-gw and the container supabase-envoy, but the kong network alias is kept: services that call the gateway by that name keep working. The HTTP port stays 8000, set through API_GW_HTTP_PORT, so your Caddy or Nginx reverse proxy needs no change.
⚠️ Supabase calls this a breaking change for a SUBSET of self-hosters, and you need to know whether you are one. Three cases: you relied on Kong's built-in HTTPS port 8443 — it is no longer shipped by default, TLS termination now goes through docker-compose.caddy.yml or docker-compose.nginx.yml; you had a customised volumes/api/kong.yml — routes and plugins must be ported to the Envoy configuration, which lives in volumes/api/envoy/; your scripts reference the gateway by service name. In all three cases you can also stay on Kong: sh run.sh config add kong.
Move to Envoy without breaking your gateway
Find out what you are running
Run
docker compose ps. Asupabase-kongcontainer means you are still on Kong;supabase-envoymeans the switch is done. Also check whethervolumes/api/kong.ymldiffers from the repository's — that file decides whether the migration costs you any work.Back up before touching the compose file
Take a full
pg_dumpand, if your host allows it, a VPS snapshot. Switching gateway does not touch the PostgreSQL schema, but it changes the entry path of every request: a fast rollback beats a live diagnosis.Decide: port or stay
If your
kong.ymlis the repository's, there is nothing to port. If it holds routes or plugins of your own, two paths: translate them into Envoy configuration undervolumes/api/envoy/, or stay on Kong withsh run.sh config add kongwhile you do. Staying is a legitimate decision, not a failure.Pull the new compose and restart
Pull the latest
dockerfolder from the official Supabase repository, thendocker compose down && docker compose up -d. Check thatapi-gwreacheshealthyand that the API answers on port 8000.Restore TLS if you relied on Kong's 8443
The built-in HTTPS port is no longer shipped. Add TLS termination with
docker-compose.caddy.ymlordocker-compose.nginx.yml, or let your existing reverse proxy handle it in front of port 8000. This is the most common breakage, and it only shows on the first direct HTTPS call.Check storage and signed URLs
Test a download from a private bucket through a pre-signed URL. If you get a 403 after the switch, look first at
Hostheader rewriting and at your storage service's public URL setting: that is where the two gateways differ.
Kong and Envoy, point by point
Scroll the table
| Aspect | Kong (before August 2026) | Envoy (default since) |
|---|---|---|
| Service in `docker-compose.yml` | `kong` | `api-gw` — `kong` kept as a network alias |
| Container | `supabase-kong` | `supabase-envoy` |
| HTTP port | 8000 | 8000 — via `API_GW_HTTP_PORT` |
| Built-in HTTPS port | 8443 | **removed** — TLS via `docker-compose.caddy.yml` / `.nginx.yml` |
| Configuration | `volumes/api/kong.yml` | `volumes/api/envoy/` (versioned YAML) |
| Opting back out | — | `sh run.sh config add kong` |
CVE-2026-31813: OIDC flaw in GoTrue — update now
A vulnerability was disclosed in 2026 in GoTrue (Supabase's authentication service) under the reference CVE-2026-31813. The flaw lies in the validation of the iss claim during OIDC authentication: a JWT signed by a third-party identity provider (Apple or Azure) but with an iss different from the one configured in GoTrue was accepted under certain configuration conditions.
In practice, an attacker who controls an OIDC provider registered in the client application can issue valid tokens for arbitrary users on your Supabase instance. The actual scope depends on configuration: only instances with at least one third-party OAuth provider enabled (Social Auth) are affected. Instances using only email/password authentication or magic links are not exposed.
The fix is included in Supabase Auth (GoTrue) 2.185.0. To check your version: docker compose exec auth gotrue version. If your gotrue version is below 2.185.0, update immediately.
Update GoTrue to fix CVE-2026-31813
Identify the current version
From your Supabase stack directory:
docker compose exec auth gotrue version. If the output shows a version < 2.185.0, your instance is exposed to CVE-2026-31813. Also note the overall stack version:grep 'SUPABASE_VERSION' .env(if pinned) ordocker compose images | grep supabase.Back up before updating
Before any update, back up the database:
docker compose exec db pg_dumpall -U postgres > /tmp/supabase-backup-$(date +%F).sql. Updating GoTrue does not involve a PostgreSQL schema migration, but a prior backup remains the rule before any version change in production.Update Supabase Auth to 2.185.0 or newer
In your
docker-compose.yml, update thesupabase/gotrueimage tag tov2.185.0or newer, or if following the full stack, pull the latest compatible version:docker compose pull && docker compose up -d auth. Updating GoTrue alone does not require restarting other services. Verify withdocker compose exec auth gotrue versionthat the new version is active.Verify OIDC authentication
After the update, test a complete authentication flow with each configured OIDC provider : login, logout, re-login. Check in GoTrue logs (
docker compose logs auth) that no error messages related toissvalidation appear under normal usage conditions.
If an immediate update is not possible (maintenance window required), you can reduce the attack surface by temporarily disabling third-party OAuth providers in Supabase Studio: Authentication → Providers, then disable each Social provider. Email/password authentication remains functional. This workaround is temporary: it does not fix the vulnerability, it only reduces the attack surface while you prepare the update.