What self-hosted Supabase actually runs
Supabase is not one program. The compose file starts a stack of services that talk to each other over a private Docker network, and knowing which one failed is half the debugging.
- Postgres — the database itself, with the auth and storage schemas already created.
- PostgREST — turns your tables into a REST API, so every table becomes an endpoint.
- GoTrue — sign-ups, logins, OAuth and the JWTs your app sends.
- Realtime — listens to the Postgres write-ahead log and pushes changes over WebSockets.
- Storage — S3-style file buckets, backed by a local folder or an object store.
- Kong — the API gateway that routes every public request to the right service.
- Studio — the dashboard, plus the metadata and image services it needs.
How much VPS Supabase needs
Eight gigabytes is the floor for a staging instance. Sixteen is the sensible production starting point, and the database is the component that decides when to add more.
| Component | Rough memory | What pushes it up |
|---|---|---|
| Postgres | 1–2 GB plus cache | Data size, shared_buffers, concurrent queries |
| Realtime | 200–500 MB | Open WebSocket connections |
| Studio and meta | 400–700 MB | Only used by admins; can stop between sessions |
| Kong, GoTrue, PostgREST, Storage | 400–700 MB combined | Request volume, not data size |
| Analytics and logs | 300–600 MB | Retention and log volume |
Add up the rows and the 8 GB plan is real, not marketing. Two vCPU runs the stack; four keeps API latency sane while a migration or a query runs. Use NVMe, because Postgres and the storage volume both hammer the disk.
Start the stack from the official compose file
The Supabase repository ships a working compose file in its docker folder. Do not hand-write one for a first install; you will miss a service and spend a day on a 502.
- Install Docker and check both parts:
docker --versionanddocker compose version. - Clone the repository at depth one to save time:
git clone --depth 1 https://github.com/supabase/ supabase.git /opt/supabase - Move into the compose folder and copy the environment template:
cd /opt/supabase/docker && cp .env.example .env - Edit
.envbefore the first start. The defaults are public values, not secrets. - Start it:
docker compose up -d, then watchdocker compose logs -f --tail=50for two to five minutes. - Confirm every container is up:
docker compose psshould show no restarting container, and the database should accept a connection.
Replace the default secrets first
Four values in .env decide whether the instance is safe: the Postgres password, the JWT secret, the anon key, the service-role key and the dashboard password. Generate the first two with openssl rand -base64 32.
- POSTGRES_PASSWORD — set it before the first boot, because the database initialises with whatever is in the file at that moment.
- JWT_SECRET — sign the anon and service-role keys with it. A key signed with the old secret stops working the moment you change this.
- ANON_KEY and SERVICE_ROLE_KEY — two JWTs with the roles
anonandservice_role. Treat the service-role key as a database password and never ship it to a browser. - DASHBOARD_USERNAME and DASHBOARD_PASSWORD — Studio sits behind basic auth. Change both.
- SITE_URL and API_EXTERNAL_URL — your real HTTPS domain, or emailed links and OAuth redirects point at localhost.
Rotating the JWT secret later means reissuing both keys and updating every client. Do it now, while nothing depends on them.
Put nginx in front with WebSocket headers
Kong listens on port 8000 and Studio on 3000. Publish neither. Terminate TLS on nginx, proxy to Kong on the loopback address, and close the rest of the ports.
- Create a server block for your domain that redirects port 80 to 443 and proxies
/tohttp://127.0.0.1:8000. - Add the upgrade headers, or Realtime will connect and then drop:
proxy_http_version 1.1,proxy_set_header Upgrade $http_upgrade,proxy_set_header Connection "upgrade". - Raise the read timeout for long-lived sockets:
proxy_read_timeout 86400. - Issue the certificate:
certbot --nginx -d api.example.com, thennginx -tand reload. - Allow only 22, 80 and 443 through the firewall, and deny 8000, 3000 and 5432.
- If you rely on ufw, remember Docker writes its own iptables rules: set
"iptables": falsein/etc/docker/daemon.jsonand restart Docker, or a published port stays reachable.
Connect an app and prove the API works
Install the client library and point it at your domain with the anon key. The base URL is the nginx host, not a path on it.
npm install @supabase/supabase-js- Create the client with your HTTPS URL and the anon key from
.env. - Test the REST layer without an SDK:
curl 'https://api.should return an OpenAPI document, not a 502.example.com/ rest/v1/' -H "apikey: YOUR_ANON_KEY" - Create a table in Studio, enable Row Level Security, and add a read policy before you insert a row from the app.
- Open two browser tabs, subscribe to a table in one, insert in the other. A row appearing live proves Realtime and the WebSocket headers are both right.
Back up the database and the storage volume
Self-hosting means the backup is yours. Two things need copying: the Postgres database and the storage folder. A database dump alone loses every uploaded file.
- Dump in the custom format:
docker compose -f /opt/supabase/docker/docker- compose.yml exec -T db pg_dump -U postgres -Fc --no-owner postgres > /opt/supabase/ backups/db_ $(date +%F).dump - Archive the files:
tar -czf /opt/supabase/backups/storage_ $(date +%F).tar.gz -C /opt/supabase/docker/volumes storage - Copy both off the server, to object storage or another machine. A backup on the same disk is not a backup.
- Schedule it:
0 3 * * * /opt/supabase/backup.sh >> /var/log/supabase-backup.log 2>&1 - Restore once, on purpose:
pg_restore -U postgres -d postgres --clean --no-owner < db_2026-10-30.dumpinside the db container, then unpack the storage archive.
Upgrade the stack without losing data
Pin the image tags in the compose file to the versions you tested. Upgrading then becomes a deliberate act rather than something that happens on a reboot.
Read the release notes for migration steps, take the dump above, run docker compose pull and docker compose up -d, then watch the logs for a failed migration. The database volume lives in a named Docker volume; removing it with down -v deletes your data, so never use that flag on this stack.
When managed Supabase is the better buy
One self-hosted instance is one project. Studio does not give you organisations or a project switcher, so a second product means a second stack, a second domain and a second backup job. If you need branching, log drains and point-in-time recovery, the managed service earns its price.
Self-host when the data must stay on infrastructure you control, when usage is steady enough that a fixed server is cheaper, or when you need a Postgres extension the managed tier does not offer. A WebJi® VPS gives you root access from first boot, with plans from ₹499 a month before 18% GST and larger sizes quoted by email at support@webji.services. If you plan to put a model on the same box, the LLM on a VPS guide covers that separately, because the two stacks compete for the same RAM.
Frequently Asked Questions
How much RAM does a self-hosted Supabase need?
Eight gigabytes for staging with 4 vCPU, and 16 GB for production. Postgres takes the largest share, Realtime grows with open WebSocket connections, and Studio plus the metadata services add about half a gigabyte.
How do I install Supabase on a VPS?
Install Docker and Compose, clone the supabase repository, copy docker/.env.example to .env, set your own Postgres password and JWT secret, then run docker compose up -d and check docker compose ps for unhealthy containers.
Why does Supabase Realtime disconnect behind nginx?
The proxy is not passing the WebSocket upgrade. Set proxy_http_version 1.1 and the Upgrade and Connection headers, raise proxy_read_timeout, and test with two browser tabs subscribing to the same table.
How do I back up a self-hosted Supabase?
Dump Postgres with pg_dump -Fc inside the db container, archive the storage volume with tar, copy both off the server, and schedule the pair in cron. Restore them together, because the database alone does not include uploaded files.
Can I run several projects on one self-hosted Supabase?
Not the way the hosted product does. A self-hosted instance is a single project with one Studio and one database, so separate products need separate stacks or separate schemas with their own policies.

