Deployment guide

Hosting Supabase on a VPS in 2026

Deploy on a VPS Cloud →

Tutorial

Databases

Hosting Supabase on a VPS in 2026

Supabase is the open source alternative to Firebase built on PostgreSQL: database, authentication, storage, edge functions, and an auto-generated API. Since August 2026, the self-hosted stack has migrated from Kong to **Envoy** as the internal API proxy. Self-hosting it on a VPS gives you a complete backend without project caps or usage-based billing, with your data in your own Postgres.

15 min read22 steps
Contents· Why self-host Supabase on a VPS1/16
  1. 01Why self-host Supabase on a VPS
  2. 02Concrete benefits of a self-hosted Supabase
  3. 03Hardware and software requirements
  4. 04Deploy self-hosted Supabase with Docker
  5. 05Supabase vs Appwrite: which self-hosted BaaS to choose?
  6. 06Deploy from the official Supabase stack, not a homemade compose
  7. 07Back your instance up automatically
  8. 08Trimming the stack: the analytics service
  9. 09Kong→Envoy migration: what changes, and who it breaks
  10. 10Move to Envoy without breaking your gateway
  11. 11Kong and Envoy, point by point
  12. 12CVE-2026-31813: OIDC flaw in GoTrue — update now
  13. 13Update GoTrue to fix CVE-2026-31813
  14. 14Temporarily disable Apple and Azure if you cannot update immediately
  15. 15PostgreSQL 15 → 17 migration: pin before you pull
  16. 16Migrate from PostgreSQL 15 to 17 without losing your data

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, Envoy as a gateway, formerly Kong) 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 pgvector for 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, Envoy, 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

  1. Clone the official repository and prepare the environment

    Fetch the docker folder from the Supabase repository, copy .env.example to .env, then generate unique secrets: POSTGRES_PASSWORD, JWT_SECRET, and the ANON_KEY / SERVICE_ROLE_KEY keys derived from the JWT. Never deploy with the example values.

  2. Configure URLs and the Studio password

    In .env, set SITE_URL, API_EXTERNAL_URL and SUPABASE_PUBLIC_URL with your domain, and secure Supabase Studio with DASHBOARD_USERNAME and DASHBOARD_PASSWORD: Studio gives full admin access to your project.

  3. Launch the stack

    Start with docker compose up -d, then follow service startup via docker compose ps. The first Postgres initialisation and internal migration application take one to two minutes; check for the absence of errors in docker compose logs.

  4. Place a reverse proxy with SSL

    Put Caddy or Traefik in front of the API gateway (Envoy, 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 publicly. Yet the official compose publishes the gateway (8000) and the Supavisor pooler (5432 and 6543) on all interfaces, and ports published by Docker bypass ufw: prefix them with 127.0.0.1: in docker-compose.yml (for example 127.0.0.1:${POSTGRES_PORT}:5432), or close them in your provider's firewall.

  5. Enable RLS policies

    Connect to Studio, create your tables, then enable Row Level Security on each one and define your policies. Without RLS, your anon key exposes all your data: this is the setting to configure before exposing anything.

  6. Back up Postgres and Storage

    Schedule a daily pg_dump of 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

CriterionSupabaseAppwrite
DatabaseNative PostgreSQL, full SQLMariaDB internally, document/collection API
Data modelRelational, Postgres RLSDocuments and collections, rule-based permissions
Auto-generated APIREST (PostgREST) + GraphQLREST, GraphQL and multiple SDKs
AuthenticationGoTrue, OAuth, magic linksIntegrated auth, OAuth, teams, JWT
Resource footprintHeavier (~10 containers)Moderate, more compact stack
Vector search / AINative via pgvectorNot native, must be externalised
Best forRelational apps, RAG, advanced SQLMobile/web apps oriented around documents
Learning curveComfortable if you know SQLFast 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 note that pgvector ships with the image: it can be enabled at any time with create extension vector;, without rebuilding the database.

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

Until self-hosted release 0.5.0 (June 2026), the official Docker composition shipped Logflare, Supabase's internal analytics service, under the name analytics. It is now optional: removed from the default docker-compose.yml, it is enabled with the docker-compose.logs.yml override. It is essential neither to the API, nor to authentication, nor to storage: it is an observability tool. On an older instance it may still be running, and on a VPS with tight resources it often reports an unhealthy state without the rest of the stack suffering. First measure what it consumes on your setup with docker stats before deciding: if your monitoring already goes through an external tool, you lose nothing; if you relied on its logs, turn it off only after wiring up a replacement.

Kong→Envoy migration: what changes, and who it breaks

On 11 August 2026, with its self-hosted release 0.8.0, 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

  1. Find out what you are running

    Run docker compose ps. A supabase-kong container means you are still on Kong; supabase-envoy means the switch is done. Also check whether volumes/api/kong.yml differs from the repository's — that file decides whether the migration costs you any work.

  2. Back up before touching the compose file

    Take a full pg_dump and, 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.

  3. Decide: port or stay

    If your kong.yml is the repository's, there is nothing to port. If it holds routes or plugins of your own, two paths: translate them into Envoy configuration under volumes/api/envoy/, or stay on Kong with sh run.sh config add kong while you do. Staying is a legitimate decision, not a failure.

  4. Pull the new compose and restart

    Pull the latest docker folder from the official Supabase repository, then docker compose down && docker compose up -d. Check that api-gw reaches healthy and that the API answers on port 8000.

  5. 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.yml or docker-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.

  6. 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 Host header 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

AspectKong (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 port80008000 — via `API_GW_HTTP_PORT`
Built-in HTTPS port8443**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 published in March 2026 in GoTrue (Supabase Auth, Supabase's authentication service) under the reference CVE-2026-31813. It affects ID token sign-in with the Apple and Azure providers: GoTrue validated the token against whatever issuer the token named, including an issuer controlled by the attacker.

In practice, an attacker who signs, from their own OIDC issuer, an ID token carrying a victim's email address obtains a valid session (AAL1 level) on that victim's account. Only instances with the Apple or Azure provider enabled are affected. Instances that only use 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 older than 2.185.0, update immediately.

Update GoTrue to fix CVE-2026-31813

  1. 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) or docker compose images | grep supabase.

  2. Back up before updating

    Before any update, back up the database: (umask 077; docker compose exec -T db pg_dumpall -h localhost -U supabase_admin > /root/supabase-backup-$(date +%F).sql), never in /tmp, which every account on the server can read. Updating GoTrue does not involve a PostgreSQL schema migration, but a prior backup remains the rule before any version change in production.

  3. Update Supabase Auth to 2.185.0 or newer

    In your docker-compose.yml, update the supabase/gotrue image tag to v2.185.0 or 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 with docker compose exec auth gotrue version that the new version is active.

  4. 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 to iss validation appear under normal usage conditions.

Temporarily disable Apple and Azure if you cannot update immediately

If an immediate update is not possible (maintenance window required), reduce the exposure by temporarily disabling the Apple and Azure providers. In self-hosted, they are set through the auth service's environment variables: set GOTRUE_EXTERNAL_APPLE_ENABLED and GOTRUE_EXTERNAL_AZURE_ENABLED to false, then run docker compose up -d auth. Email/password authentication keeps working. This workaround is temporary: it does not fix the flaw, it only reduces the attack surface while you prepare the update.

PostgreSQL 15 → 17 migration: pin before you pull

On 17 June 2026, with its self-hosted release 0.6.0, Supabase switched the default PostgreSQL image in its official docker-compose.yml from version 15 to version 17 (a change announced on 18 May 2026 in discussion [#46080](https://github.com/orgs/supabase/discussions/46080)). This change is destructive if you ignore it: the vendor warns never to start PostgreSQL 17 on a data directory created by PostgreSQL 15. Pulling the new compose carelessly on an instance still on PG 15 leaves you with a database that no longer starts.

Second trap: the timescaledb, plv8, plcoffee and plls extensions are not built for the PG 17 image. None is installed by default; if you added one and need to keep it, the vendor advises not to run the upgrade. Check before going further: docker compose exec db psql -U postgres -c "\dx" lists the active extensions.

Migrate from PostgreSQL 15 to 17 without losing your data

  1. Stay on PG 15 before any `docker compose pull`

    First check your version: docker compose exec db psql -U postgres -c "SELECT version();". If it starts with 15, keep the PG 15 image until the migration is done: the repository provides the docker-compose.pg15.yml override, which pins supabase/postgres:15.8.1.085. Add it with sh run.sh config add pg15, or pass it by hand: docker compose -f docker-compose.yml -f docker-compose.pg15.yml up -d. It is also the rollback target of the migration script.

  2. Back up the data and the pgsodium key

    In a root shell, start with umask 077: the files you create will be readable by you only. Copy the data directory: cp -a ./volumes/db/data ./volumes/db/data-manual-backup. Then back up the pgsodium root key, which lives in the db-config named Docker volume, not in that directory: docker compose run --rm db cat /etc/postgresql-custom/pgsodium_root.key > ./pgsodium_root.key.backup. Without it, Vault secrets become unrecoverable. On top of that, a logical export: docker compose exec -T db pg_dumpall -h localhost -U supabase_admin > ./pg15_dump.sql. Copy these files to external storage.

  3. Check extensions and disk space

    docker compose exec db psql -U postgres -c "\dx" lists the active extensions. If timescaledb, plv8, plcoffee or plls appear and you need them, stop there: they do not exist for PG 17. Otherwise, the script will offer to drop them. Also check free space: you need at least twice the database size, plus 5 GB.

  4. Fetch the up-to-date docker folder

    Pull the latest version of the docker folder from the official Supabase repository, which contains utils/upgrade-pg17.sh. Leave all the stack's containers running: the script requires it. Do not delete any Docker volume by hand, especially db-config, which holds the pgsodium key.

  5. Run the official migration

    sudo bash utils/upgrade-pg17.sh. The script stops the stack, runs pg_upgrade from a temporary PG 15 container, re-enables extensions in a PG 17 container, swaps the data directories while keeping the original in ./volumes/db/data.bak.pg15, then restarts Supabase on PG 17. It asks for confirmation at each major step (--yes to skip).

  6. Verify, then clean up

    Check the version with docker compose exec db psql -U postgres -c "SELECT version();", then docker compose ps, and test a full API call. If docker-compose.pg15.yml is still listed in COMPOSE_FILE, remove it with sh run.sh config remove pg15. Only once everything is verified, delete ./volumes/db/data.bak.pg15, ./volumes/db/pgsodium_root.key.bak.pg15 and the ./volumes/db/pg17_upgrade_bin_*.tar.gz archive to reclaim space.

Self-host your Supabase backend

The ServOrbit Cloud VPS with a preconfigured Docker template and generous RAM hosts the entire Supabase stack: Postgres, Auth, Storage, and API over HTTPS.