Skip to content

Deploy Supabase with the supabase-selfhost template

Deployment · Updated Jun 2026

Deploy Supabase with the supabase-selfhost template

Stand up the full self-hosted Supabase backend on a single Quake AI instance using the validated OpenTofu template supabase-selfhost. You apply the template, read the generated credentials, point a domain at the host and serve it over HTTPS, set the public URL, sign in to Studio, create a table, and query it through the REST API.

Supabase keeps your application's database, auth, storage, and realtime layer on infrastructure you own. You run it yourself; this is a self-hosted backend you operate, not the managed Supabase cloud.

App clientFloating IPUbuntu instanceCaddyreverse proxyKongAPI gatewayStudioAuthRESTRealtimeStoragePostgres proxies 443 to 8000HTTPS
Click to zoom
What you'll build: the full Supabase stack on a single instance, reached over HTTPS through a Caddy reverse proxy, with Postgres and uploads on an attached volume

Monthly cost estimate

Pricing calculator ↗

Sized as a custom package on dedicated vCPU.

Starting template$132.60/mo

Monthly total for the required template above. Use the configurator below to add optional pieces and see the total update.

What each resource is for

Supabase self-host stack

m2a.xlarge · 4 dedicated vCPU, 16 GiB RAM, 1 Gbps

Runs the full self-hosted Supabase stack in Docker (Postgres, Auth, REST, Realtime, Storage, Studio, and the Kong API gateway) on a single instance, with the database and uploads on an attached volume.

The full stack runs on 4 vCPU and 16 GiB RAM. Size down to 2 vCPU / 8 GiB for light single-developer use; size up for heavier workloads.

$132.00/mo

Compute shown per role at custom-package rates ($29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM). The headline above is the billed total: the cheaper of a named plan and the custom package, plus add-ons.

Included in baseline

m2a.xlarge

4 dedicated vCPU, 16 GiB RAM, 1 Gbps

$132.00

Compute + RAM rate basis

4 vCPU + 16 GiB RAM at $29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM (regular). Totals apply the flat −$5/mo package promotion.

—

Block storage (70 GiB)

70 GiB at $0.08/GiB/mo

$5.60

Public IP (included)

1 included with the custom package

$0.00

Package promotional discount

Flat −$5.00/mo on the custom package (same promotion as named plans).

$-5.00

Included at no charge

These line items are zero on Quake AI. Many other providers meter them separately.

Data transfer (inbound and outbound)

Unlimited data transfer on every plan; Quake AI does not meter per-GB egress.

AWS, GCP, and Azure meter outbound transfer per GB. DigitalOcean and Hetzner include an allowance on compute plans, then charge overage.

Learn more
$0.00

Private networking

Private networks, subnets, Neutron routers, and security groups are included with the plan.

VPC objects are usually free to create elsewhere, but NAT gateways bill hourly plus per-GB processed. Quake AI uses router SNAT with no separate NAT line item.

$0.00

Control-plane API requests

OpenStack API calls for provisioning and management are included.

Some managed services on other clouds meter API calls or charge for premium control-plane features.

$0.00

Dev/test vs production

Start on shared CPU for dev/test, then promote to dedicated for production with a flavor resize. The network, storage, and template stay the same.

Dev/test on shared CPU

Burstable s1a flavors; suited to prototyping and low or bursty load.

$33.60/mo

Production on dedicated CPU

The headline estimate above; predictable steady-load performance.

$132.60/mo

Saves $99.00/mo while you build on shared CPU.

Shared flavors carry less RAM (m2a.xlarge (16 GiB RAM) -> s1a.medium (4 GiB RAM)). A resize reboots the instance; data on attached volumes persists. Size the dedicated flavor for the RAM your production workload needs.

Pricing data last validated: . For current rates, check quake.ai/pricing.

Prerequisites#

You need:

  • OpenTofu 1.6.0 or later (or Terraform 1.6.0 or later) installed locally.
  • Your OpenStack credentials sourced into the shell (source openrc.sh). See the OpenStack CLI guide.
  • An SSH keypair that already exists in your project. Record its name for the key_name variable.
  • A copy of the supabase-selfhost template directory from the template reference page.
  • A domain you can point at the instance. Supabase Auth builds callback URLs from a stable public URL.

Step 1: Apply the template#

Copy the template's example variables file and set key_name:

bash
cp terraform.tfvars.example terraform.tfvars
HCL
key_name = "YOUR_KEY_NAME"

Initialize, preview, and apply:

bash
tofu init
tofu plan
tofu apply

OpenTofu provisions a private network, a router, a security group, a block volume mounted at /opt/supabase, an instance, and a floating IP. On first boot, cloud-init installs Docker Engine, clones the Supabase stack, generates every secret into /opt/supabase/.env, and runs docker compose up -d.

Read the outputs and record floating_ip, api_url, and studio_url:

bash
tofu output

Step 2: Read the generated credentials#

Every secret was generated on the instance. SSH in and read them once:

bash
ssh ubuntu@YOUR_FLOATING_IP
sudo cat /opt/supabase/.env

Record ANON_KEY, SERVICE_ROLE_KEY, DASHBOARD_USERNAME, and DASHBOARD_PASSWORD, and store them in your secret manager. The anon key is safe to ship in client code; the service_role key bypasses row-level security and stays server-side only.

The stack takes a few minutes to pull and start on first boot. Confirm the containers are healthy:

bash
cd /opt/supabase
sudo docker compose ps

Step 3: Point a domain at the host and serve HTTPS with Caddy#

Supabase Auth builds absolute callback links from its public URL, so it needs a domain with TLS before it serves production traffic.

  1. Create a DNS A record for your domain (for example api.example.com) pointing at YOUR_FLOATING_IP. Follow How to point a domain at a Quake AI resource. Wait until it resolves:
bash
dig +short api.example.com
  1. On the instance, create /opt/supabase/Caddyfile:
api.example.com {
  reverse_proxy 127.0.0.1:8000
}
  1. Add Caddy to /opt/supabase/docker-compose.yml:
YAML
services:
  caddy:
    image: caddy:2
    restart: unless-stopped
    network_mode: host
    volumes:
      - /opt/supabase/Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
volumes:
  caddy_data:

For background on certificates, see How to issue and auto-renew a TLS certificate with Let's Encrypt.

Step 4: Set the public URL and restart#

Edit /opt/supabase/.env and set the three URL variables to your HTTPS address:

API_EXTERNAL_URL=https://api.example.com
SUPABASE_PUBLIC_URL=https://api.example.com
SITE_URL=https://api.example.com

Restart the stack so the services pick up the new URLs:

bash
cd /opt/supabase
sudo docker compose up -d

Step 5: Sign in to Studio and create a table#

  1. Open Studio. Over the reverse proxy, route a second hostname (for example studio.example.com) to port 3000, or tunnel to it over SSH for setup: ssh -L 3000:localhost:3000 ubuntu@YOUR_FLOATING_IP and open http://localhost:3000.
  2. Sign in with DASHBOARD_USERNAME and DASHBOARD_PASSWORD from /opt/supabase/.env.
  3. Open the Table Editor, create a table named notes with a text column called body, and insert a row.

Step 6: Query the REST API#

Supabase generates a REST endpoint for every table. Query notes through the API gateway with your anon key:

bash
curl "https://api.example.com/rest/v1/notes?select=*" \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer YOUR_ANON_KEY"

The row you inserted in Studio comes back as JSON. Point a Supabase client library at https://api.example.com with the anon key to build against the same API from your application.

What you built#

  • Applied the supabase-selfhost template to provision a network, security group, data volume, instance, and floating IP, with the full Supabase stack started by cloud-init
  • Read the generated credentials from /opt/supabase/.env
  • Served the stack over HTTPS by pointing a domain at the floating IP and routing it through a Caddy reverse proxy
  • Set the public URL so Auth callbacks resolve
  • Created a table in Studio and queried it through the auto-generated REST API

Scope of this deployment#

This template runs a single-VM Supabase host, not the managed Supabase cloud. The instance is CPU-only and runs in one region, and it runs every Supabase service as a container on one host. You operate the instance, Docker, Postgres, and the data volume yourself: back them up, patch them, and snapshot the volume before you resize or rebuild. For high availability, move Postgres onto its own instance and run the stateless services behind a load balancer.

Next steps#

Clean up#

When you no longer need the deployment, destroy everything the template created:

bash
tofu destroy

Then remove the DNS A records you created in step 3. Because Postgres, storage uploads, and the analytics data all live on the instance and its attached volume, tofu destroy removes them along with the infrastructure. Export any data you want to keep first with pg_dump against the database.

Before this
Was this page helpful?