Workload · Running your own Supabase backend

Self-hosted Supabase: your own backend, on a server that is all yours

You can run self-hosted Supabase on a single dedicated server with the official Docker setup: your database, sign-ins, file storage, realtime updates and the dashboard, all on hardware nobody else touches. Supabase says the Docker route takes under 15 minutes. Most of your evening goes on three things it cannot do for you: swapping the sample passwords for your own, keeping the dashboard and database away from strangers, and making backups.

The short version
  1. Supabase's floor: 4 GB of memory, 2 cores, 40 GB of SSD
  2. The sample keys in the download are public, so change them before you go live
  3. Docker can open ports your firewall never sees
  4. Backups are your job once you leave the hosted platform

Will a modest server carry it?

Yes, by Supabase's own measure. Its Docker guide asks for at least 4 GB of memory, 2 CPU cores and a 40 GB SSD to run every part of the stack, and recommends 8 GB or more, 4 cores or more and 80 GB or more of SSD. It describes that as enough for development and for small to medium production use.

You can also make it lighter. If your app never uploads files, never needs live updates or never runs server functions, the guide lets you delete Storage, imgproxy, Realtime or the Edge Runtime from docker-compose.yml. Turning on the optional log viewer goes the other way and needs more memory.

The part that keeps growing is your data. Postgres likes enough memory to hold the tables you use most, and every file your users upload lands on the same disk. Pick memory first, then plan the disk around what you expect to store.

Why a whole server suits it

Supabase is a set of containers sharing one machine, with Postgres at the centre. On a dedicated server the processor, the memory and the disks all belong to that one job, so your database is never slowed down by somebody else's workload. Nobody meters the port, so a busy week of file downloads or realtime traffic does not turn into a bandwidth bill, and there is no contract: you can stay month to month.

  • Trying it out or a small app: one of the compact servers with 16 or 32 GB of memory is plenty.
  • A real product with a growing database: an everyday single-processor server, or a two-processor one when the database gets busy. Most two-processor servers are listed with two SSDs, which you can mirror so one dead disk does not take your data with it.
  • Lots of uploads: look at the NVMe servers or a larger disk option on the order page.

A ready server is handed over within 30 minutes of your payment being approved. Rack servers and blades, plus the two Moonshot models with their own console (m510, m710x), give you a screen and power button you reach yourself, so a firewall mistake does not lock you out for good.

Up and running in an evening

  1. Install a current Ubuntu or Debian from the control panel and log in over SSH. Your server needs Git, plus Docker Engine version 28.0 or newer and its Compose plugin; the script below installs Docker when it is missing, so check docker version afterwards either way. Docker versions before 28.0 allowed neighbouring machines on the local network to connect to ports meant for 127.0.0.1 only, and the key script falls back to Docker when Node 16+ is not installed.
  2. Turn on the firewall before anything else listens: allow SSH, 80 and 443, nothing more (details below).
  3. Download the setup script, read it, then run it. It installs Docker, fetches the files, creates every key and a random dashboard password, and asks for your web addresses.
  4. Lock the settings file and choose your own dashboard username.
  5. Change two lines so the database ports only answer on the server itself.
  6. Switch on the built-in HTTPS proxy, then start everything for the first time.
cd /opt
curl -fsSL https://supabase.link/setup.sh -o setup.sh
less setup.sh        # read what it will do
sh setup.sh
cd supabase-project
chmod 600 .env*      # .env and its .env.old backup hold every key
nano .env            # set DASHBOARD_USERNAME to something other than supabase

The script supports Debian, Ubuntu and Red Hat-family Linux. If you prefer to clone the repository and run the key scripts yourself, the guide covers that path too; the rule is the same either way.

The sample secrets have to go

The download includes an example settings file, .env.example, filled with placeholder values. Those values are published on GitHub for anyone to read, and Supabase's own instruction is never to start your instance with them. The ones that matter most:

  • JWT_SECRET: the shared signing secret behind the older keys. Whoever knows it can sign tokens your API accepts.
  • ANON_KEY and SERVICE_ROLE_KEY: the older public and admin API keys. The admin one must never reach a browser.
  • DASHBOARD_USERNAME and DASHBOARD_PASSWORD: the login for Studio, the dashboard. The password needs at least one letter and no special characters.
  • POSTGRES_PASSWORD: the database password. Letters and numbers only saves you trouble in connection strings.

Supabase documents the setup script generating every secret and a random dashboard password; the username is not in that list, so set your own. Open .env afterwards and check: if any of those lines still matches .env.example, you are not ready. Keep the file readable by root only, since it will also hold your mail server password once you set up the emails Supabase Auth sends.

Keeping the doors shut, IPv6 included

Out of the box, Docker publishes three ports to the whole internet: 8000 for the API and dashboard, and 5432 and 6543 for the database pooler. Here is the surprise: Docker writes its own network rules for published ports, so a ufw rule saying deny does not stop them. Your firewall can look closed while the database answers anyway.

The fix is to stop publishing what nobody outside should reach. In docker-compose.yml, under the supavisor service, put 127.0.0.1: in front of both port lines, so they read 127.0.0.1:${POSTGRES_PORT}:5432 and 127.0.0.1:${POOLER_PROXY_PORT_TRANSACTION}:6543. Bound to 127.0.0.1, they answer only on the server itself and stop listening on every public address, both IPv4 and IPv6. Port 8000 disappears in the next step.

Then the host firewall: ufw allow OpenSSH, ufw allow 80/tcp, ufw allow 443, ufw enable, and make sure /etc/default/ufw says IPV6=yes. Your server comes with IPv6 at no charge, and a firewall that only guards IPv4 leaves a second front door.

Studio stays behind a password and HTTPS

Studio is protected by a username and password prompt, and on plain HTTP that password crosses the network unencrypted. Supabase includes a ready-made Caddy setup that fixes this. Point your domain's DNS at the server, put your https addresses and PROXY_DOMAIN in .env, and enable the override with sh run.sh config add caddy; sh run.sh start brings everything up. Caddy fetches a free Let's Encrypt certificate, closes port 8000, sends app traffic to the API and asks for your dashboard login on everything else. To confirm it works, curl -I https://api.example.com/auth/v1/ (with your own domain) should answer 401. Then, from another computer, make sure 5432, 6543 and 8000 refuse connections on both your IPv4 and IPv6 addresses.

When you need the database itself, for a migration or a quick psql session, tunnel in over SSH using your own account on the server: ssh -N -L 15432:127.0.0.1:5432 you@your-server, then on your laptop run psql -h localhost -p 15432 -U postgres.your-tenant-id postgres, where your-tenant-id is whatever .env sets as POOLER_TENANT_ID, and the password it asks for is your POSTGRES_PASSWORD. Nothing about the database is ever public.

Your backups, your responsibility

The hosted platform's backup service and PITR (restoring to any moment) do not come with self-hosting, and Supabase's update script saves your configuration but not your data. Without a backup you make yourself, there is no copy. A simple, consistent way:

  1. Stop the stack with sh run.sh stop. Your app is offline while the copy runs, so pick a quiet hour.
  2. Archive the whole supabase-project folder: settings, the database in volumes/db/data, uploaded files in volumes/storage and your functions.
  3. Also archive the Docker volume supabase_db-config under /var/lib/docker/volumes; it holds the pgsodium decryption key, and the database copy is of little use without it.
  4. Start again with sh run.sh start, and keep the archive root-only, since it contains every secret.
  5. Copy it off the server, and test a restore on a spare machine once.

If you cannot afford the downtime, Supabase's migration guide shows supabase db dump exporting roles, schema and data separately. Before every update, run a backup first, then sh update.sh --dry-run to see what would change.

When something else fits better

Self-hosting gives you one project per install, with settings in environment variables. Branching, managed backups, advanced metrics and the Management API stay on the hosted platform, and self-hosted Supabase is community-supported. If your app only needs a database, a plain PostgreSQL server is less to look after. If you want a panel to deploy and update your containers for you, read about running Coolify yourself.

Questions people ask

Does my self-hosted Supabase report back to Supabase?
No. Supabase states that self-hosted Supabase does not phone home or collect telemetry. The Supabase CLI, a separate development tool, does collect usage data, and its docs explain how to opt out.
Do I need my own domain name?
For a production setup, yes. The Caddy proxy needs a domain pointing at your server to get a Let's Encrypt certificate, and Supabase says production needs HTTPS, especially with social sign-in providers. Without one you are left with plain HTTP, which is fine only for a test on a private network.
Can the server grow with my app?
Memory, disks and port speed can be added to the server you already have, and on some lines a second processor, with no migration and no new IP address. Start with what your database needs today.
How do I keep Supabase up to date?
Supabase publishes a tested set of container versions roughly once a month. Back up first, run sh update.sh, then sh run.sh pull and sh run.sh recreate. The update keeps your .env values and your own edits unless they clash with the same lines upstream, and tells you when they do.
What if I lock myself out with the firewall?
If your server is a rack server or blade, or an m510 or m710x cartridge, its out-of-band console still shows you the screen and gives you the power button, so you can log in and fix the rule. You can also reinstall the operating system from the control panel and start clean.

Give your Supabase backend a server of its own

Pick a machine with the memory your database needs, and you can have the stack running the same evening.

Get a server for Supabase

Read next