SSD Nodes Learn 8GB RAM — $66/yr
How to do am Matt ConnorBy Matt Connor · Updated 2026-08-02

How to Self-Host Supabase on VPS with Docker

Run Supabase for your own VPS with Docker. Replace the published demo secrets, understand the 14 services, plan RAM, back up Postgres, and update safely.

Wetin you dey build

Self-hosting Supabase mean say you dey run the official Docker Compose stack for your own server: Postgres, REST API wey dey front am, auth service, file storage, realtime websockets, and the Studio dashboard. You go clone one repository, edit one .env file, and start about fourteen containers wey together dey behave like Supabase project wey you control.

The installation short. The part wey dey cause problem na the .env file. E come with demo secrets wey dem don publish for the repository, and stack wey start with those defaults dey open to anybody wey find am. This guide explain the secrets wey you must replace, wetin each service dey do, how much memory the stack really need, and how to update am without deleting your database.

If Compose still new to you, read Docker Compose basics for VPS first. Everything below assume say docker compose version dey already print a version.

Wetin really dey inside the stack

Supabase no be one program. The Compose file starts different services for one network, and when you know wetin each one dey do, you fit debug the container names wey plenty.

  • db na PostgreSQL with the Supabase extensions loaded. Every other service dey talk to am. If this container no healthy, everything else go fail too.
  • kong na the API gateway. E dey listen for port 8000 and e dey route /rest/v1/, /auth/v1/ and /storage/v1/ go the correct backend. Na the only container wey you suppose ever expose.
  • rest na PostgREST. E dey read your Postgres schema and serve am as REST API, so new table go become new endpoint without code.
  • auth na GoTrue. E dey issue the JSON web tokens (JWT) wey dey identify your users.
  • storage and imgproxy dey handle file uploads and image resizing.
  • realtime dey stream database changes through websockets.
  • studio and meta na the dashboard and the admin API wey dey behind am.
  • analytics (Logflare) and vector dey collect logs, and supavisor na the Postgres connection pooler.

Na this list make the resource numbers below be the way dem be. You no dey run only database. You dey run database plus about twelve support services.

Sizing: plan for 8 GB of RAM

The stack dey use about 2.5 to 3 GB of resident memory when fresh install don idle, as of July 2026, before your own data or traffic. The analytics service and the Studio Node.js process na the two single services wey dey use the most memory. A 2 GB server go start the containers, then the kernel out of memory killer go remove one, usually analytics or db. The sign na say container dey restart continuously with exit code 137.

Give am 8 GB of RAM and 4 vCPU for anything wey you depend on. 4 GB fit work for one-person development instance if you agree say heavy query and Studio session at the same time go slow. Disk matter too, because Postgres, the storage volume and the log data all dey under the project directory. Start with 40 GB and monitor am.

Install: clone the official repository

The supported way na to copy the docker directory comot from the main repository enter your own project directory. This separation dey important, because e mean say later git pull no fit overwrite your .env.

git clone --depth 1 https://github.com/supabase/supabase
mkdir supabase-project
cp -rf supabase/docker/* supabase-project
cp supabase/docker/.env.example supabase-project/.env
cd supabase-project
docker compose pull

docker compose pull dey download several gigabytes of images. E suppose end with every service marked Pulled. If manifest unknown error show here, e mean say dem don remove the pinned image tag from upstream. To fix am, pull newer copy of the repository instead of editing tags by hand.

Secrets wey you must change before the first start

Do dis before you start the stack, no be after. Some of these values dey write inside data for first boot, so if you change dem later, e mean say you go need reset the database.

The repository get generator wey dey produce every value correctly, including the two API keys wey must use your new JWT secret sign.

sh utils/generate-keys.sh --update-env

That script dey write new values for JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY, SECRET_KEY_BASE, REALTIME_DB_ENC_KEY, VAULT_ENC_KEY, PG_META_CRYPTO_KEY and the Logflare tokens inside .env. E need openssl, wey dey available for any normal Ubuntu image.

E no set two values, so you must edit dem by hand for .env:

  • POSTGRES_PASSWORD. Use letters and digits only. Punctuation for here go break the connection strings wey some services dey build by joining strings. The failure go look like authentication error instead of parsing error, so people go dey search for problem for wrong place.
  • DASHBOARD_USERNAME and DASHBOARD_PASSWORD. These na the basic authentication credentials for Studio. The default password wey come with am na literally this_password_is_insecure_and_should_be_updated.

Understand why ANON_KEY and SERVICE_ROLE_KEY no fit be invented. Both na JWTs wey dem sign with JWT_SECRET. The gateway dey verify that signature for every request, so any key wey no match your secret go reject am with {"message":"Invalid authentication credentials"}. Na this be the commonest self-hosting failure: operator change JWT_SECRET but keep the demo keys. Generate all three together, every time.

Treat SERVICE_ROLE_KEY like root password. E dey bypass row level security completely. E suppose dey only for server side code, and nowhere else.

Set SITE_URL and API_EXTERNAL_URL to the address wey your users go really reach, for example https://supabase.example.com. Auth dey build email confirmation and OAuth callback links from those values. So if you leave dem as http://localhost:8000, e go send all your users go their own machine.

Then check wetin you get:

sh run.sh secrets

Start am and confirm say e healthy

sh run.sh start
docker compose ps

run.sh start dey wrap docker compose up -d --wait, so e no go return until the health checks pass. Every service suppose show running (healthy) or running. First boot fit take two to four minutes because Postgres dey run its initialisation scripts before anything else fit connect.

If container dey restart, read its logs with service name:

docker compose logs db
docker compose logs auth

Studio dey for port 8000, and e go ask for the dashboard username and password wey you set.

No put port 8000 for public internet

Kong for port 8000 dey speak plain HTTP. Every API key and every user password dey cross network as clear text, and the Studio credentials na basic authentication, wey be base64 encoding instead of encryption.

Put reverse proxy for front of am, terminate TLS (transport layer security) for there, and bind Kong to loopback address so nothing else fit reach am. For docker-compose.yml, the kong port mapping go become 127.0.0.1:8000:8000, and the proxy go forward traffic to am. Traefik for front of several Compose apps explain the certificate side.

Close the remaining ports for firewall too, because Docker dey publish ports by writing im own iptables rules wey naive ufw configuration no dey see. That trap dey explained for why Docker containers dey ignore your ufw rules.

Back up the database, no be the directory

Postgres data dey inside bind mount for ./volumes/db/data. If you copy that directory while the container dey run, you go get incomplete copy, because Postgres dey buffer writes and the files for disk dey consistent only when checkpoint happen. Restoring am go usually work, but sometimes e go silently lose the last transactions. Na the worst kind failure wey backup fit get.

Use dump instead. pg_dumpall dey run inside the container and e dey produce consistent snapshot:

docker exec -t supabase-db pg_dumpall -U postgres > supabase-$(date +%F).sql

Check say the file no empty before you trust am. Then schedule the dumps to leave the server, na wetin encrypted offsite backups with restic dey do. Back up your .env at the same time. If you lose JWT_SECRET, every token wey dem don issue go become invalid and every encrypted secret wey you store no go readable.

Uploaded files dey inside ./volumes/storage. Dem be ordinary files, so plain copy dey okay.

Update wey no go lose data

Supabase dey pin image versions for docker-compose.yml, so nothing go change until you change am. Make you take dump first, every time.

docker compose pull
sh run.sh recreate

recreate dey stop the stack and start am again with the new images. Your data dey safe because e dey for bind mounts on the host, no be inside the containers. Read CHANGELOG.md for the repository before you make major version jump, because Postgres major upgrades no dey happen automatically and dem need dump and restore.

To collect changes wey dey inside the Compose file itself, clone the upstream repository again and copy the docker directory enter your project. Make sure say you no overwrite .env.

The full reset, wey dey destroy everything including the database, na separate script and e go ask for confirmation:

sh reset.sh

FAQ

Why my API calls dey return "Invalid authentication credentials"?

Your ANON_KEY or SERVICE_ROLE_KEY no use the JWT_SECRET wey dey inside .env sign. The gateway dey verify the signature for every request and e dey reject mismatch. Regenerate all three together with sh utils/generate-keys.sh --update-env, then run sh run.sh recreate make the services read the new values.

I fit run self-hosted Supabase for 2 GB VPS?

E no reliable. The stack dey idle near 3 GB as of July 2026 because e dey run about fourteen services. So, 2 GB box go lose containers to the out of memory killer, and you go see exit code 137 inside docker compose ps. Use 8 GB for production, and treat 4 GB as the minimum for solo development.

Self-hosted Supabase include edge functions?

Yes. The Compose file include the Deno based functions runtime, and e dey serve anything wey you put under ./volumes/functions. E no include the hosted platform global deployment network, so your functions go run for your one server, for one location.

How I go connect directly to the Postgres database?

Use docker exec -it supabase-db psql -U postgres for interactive shell on the server itself. For external client, connect through Supavisor for port 5432 with user postgres.<POOLER_TENANT_ID> and your POSTGRES_PASSWORD. No open that port to the internet. Reach am through VPN or SSH tunnel.

SITE_URL and API_EXTERNAL_URL for .env remain with their default values. The auth service dey build every confirmation and password reset link from those two values, so e dey send the address wey dem tell am to send. Set both to your real public URL, then recreate the stack.