Skip to content

Supabase self-host stack

Template · Updated Jun 2026
Validated Jun 2026

Supabase self-host stack

This pattern composes Compute, Network, and Block Storage into the full self-hosted Supabase backend you run on infrastructure you control.

What this template does#

Provisions a single instance running the open-source Supabase stack (the open-source Firebase alternative): a Postgres database with an auto-generated REST API, user authentication, file storage, Realtime subscriptions, and the Studio dashboard. Your application keeps its backend on infrastructure you own:

  • Compute instance that runs the full Supabase stack in Docker (Postgres plus the Kong API gateway, Auth, REST, Realtime, Storage, Studio, and the analytics services), sized for 4 vCPU and 16 GiB RAM
  • Private network, subnet, router, port, and security group; a floating IP for public access
  • A block volume mounted at /opt/supabase, so the database, storage uploads, and analytics data live on a volume you can grow rather than on the boot disk
  • cloud-init installs Docker Engine, clones the official Supabase compose stack, and brings it up

Every secret is generated on first boot and written to /opt/supabase/.env; no credential ships with this template.

What gets generated on first boot#

cloud-init mints all of the following on the instance and never anywhere else:

  • The Postgres password and the JWT_SECRET that signs every API token
  • The anon and service_role API keys, as HS256 JWTs signed with that secret
  • The Studio dashboard username and password
  • The Realtime and Vault encryption keys

Read them once over SSH (sudo cat /opt/supabase/.env) and store them in your secret manager.

Parameters#

ParameterDescriptionDefault
key_nameSSH keypair name (must already exist)No default
flavor_nameInstance size (the full stack runs on 4 vCPU / 16 GiB)m2a.xlarge
image_nameOperating system imageUbuntu-24.04
app_nameDisplay name prefix for resourcessupabase
volume_sizeBlock volume size in GiB, mounted at /opt/supabase40
external_networkExternal network for floating IP allocationPublicStatic
private_cidrCIDR for the private subnet10.47.0.0/24
api_allowed_cidrCIDR allowed to reach the API gateway (8000) and Studio (3000)10.47.0.0/24

Finish setup after apply#

cloud-init brings the stack up with the generated keys and a localhost public URL. Complete the setup over SSH so the stack serves your domain:

  1. Point a domain's DNS A record at the floating IP and put a reverse proxy (Caddy or Nginx) in front for HTTPS on 443.
  2. Edit /opt/supabase/.env: set API_EXTERNAL_URL, SITE_URL, and SUPABASE_PUBLIC_URL to your public HTTPS address.
  3. Restart the stack:
bash
cd /opt/supabase
sudo docker compose up -d

Access and security#

The Kong API gateway listens on port 8000 (REST, Auth, Storage, Realtime) and Studio on port 3000, both over plain HTTP. The security group restricts 8000 and 3000 to api_allowed_cidr, which defaults to the private network only. Because Auth callbacks need a public URL, the normal access path is a domain with HTTPS on 443 behind a reverse proxy. Ports 80 and 443 stay open for that proxy; they carry no traffic until you add one.

When to use this pattern#

Run a complete application backend (database, auth, storage, and realtime) on a host you operate, with the Supabase client libraries and Studio dashboard you already know. The single-VM stack suits development, prototyping, and small production workloads. To scale, move Postgres onto a self-managed PostgreSQL instance and run the stateless services behind a load balancer.

Estimated cost#

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.

Template source#

7 files. Download the zip or expand to copy any file.Download supabase-selfhost.zip
Show source (7 files)
main.tfHCL
data "openstack_images_image_v2" "os" {
  name        = var.image_name
  most_recent = true
}

data "openstack_networking_network_v2" "external" {
  name = var.external_network
}

resource "openstack_networking_network_v2" "private" {
  name           = "${var.app_name}-net"
  admin_state_up = true
}

resource "openstack_networking_subnet_v2" "private" {
  name            = "${var.app_name}-subnet"
  network_id      = openstack_networking_network_v2.private.id
  cidr            = var.private_cidr
  ip_version      = 4
  dns_nameservers = ["1.1.1.1", "8.8.8.8"]
}

resource "openstack_networking_router_v2" "main" {
  name                = "${var.app_name}-router"
  external_network_id = data.openstack_networking_network_v2.external.id
}

resource "openstack_networking_router_interface_v2" "private" {
  router_id = openstack_networking_router_v2.main.id
  subnet_id = openstack_networking_subnet_v2.private.id
}

resource "openstack_networking_secgroup_v2" "supabase" {
  name        = "${var.app_name}-sg"
  description = "SSH and HTTP/HTTPS for a reverse proxy; API gateway and Studio ports restricted"
}

resource "openstack_networking_secgroup_rule_v2" "ssh" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 22
  port_range_max    = 22
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = openstack_networking_secgroup_v2.supabase.id
}

# 80 and 443 carry Supabase when it is served over a domain with automatic TLS
# through a reverse proxy (Caddy or Nginx). Auth callbacks and Studio expect a
# stable public URL, so the domain path is the expected way to reach the stack;
# see the reference page.
resource "openstack_networking_secgroup_rule_v2" "http" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 80
  port_range_max    = 80
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = openstack_networking_secgroup_v2.supabase.id
}

resource "openstack_networking_secgroup_rule_v2" "https" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 443
  port_range_max    = 443
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = openstack_networking_secgroup_v2.supabase.id
}

# The Kong API gateway (8000) fronts the REST, Auth, Storage, and Realtime
# APIs. Studio (3000) is the dashboard. Both are restricted to api_allowed_cidr
# (the private network by default). Use them for setup over an SSH tunnel or a
# scoped workstation IP; put a reverse proxy on 443 in front for routine access.
resource "openstack_networking_secgroup_rule_v2" "api_gateway" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 8000
  port_range_max    = 8000
  remote_ip_prefix  = var.api_allowed_cidr
  security_group_id = openstack_networking_secgroup_v2.supabase.id
}

resource "openstack_networking_secgroup_rule_v2" "studio" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 3000
  port_range_max    = 3000
  remote_ip_prefix  = var.api_allowed_cidr
  security_group_id = openstack_networking_secgroup_v2.supabase.id
}

resource "openstack_networking_port_v2" "supabase" {
  name               = "${var.app_name}-port"
  network_id         = openstack_networking_network_v2.private.id
  security_group_ids = [openstack_networking_secgroup_v2.supabase.id]

  fixed_ip {
    subnet_id = openstack_networking_subnet_v2.private.id
  }

  depends_on = [openstack_networking_router_interface_v2.private]
}

resource "openstack_blockstorage_volume_v3" "data" {
  name = "${var.app_name}-data"
  size = var.volume_size
}

resource "openstack_compute_instance_v2" "supabase" {
  name        = var.app_name
  flavor_name = var.flavor_name
  key_pair    = var.key_name

  user_data = templatefile("${path.module}/cloud-init/supabase.yaml.tftpl", {
    app_name = var.app_name
  })

  block_device {
    uuid                  = data.openstack_images_image_v2.os.id
    source_type           = "image"
    destination_type      = "volume"
    volume_size           = 30
    boot_index            = 0
    delete_on_termination = true
  }

  network {
    port = openstack_networking_port_v2.supabase.id
  }
}

resource "openstack_compute_volume_attach_v2" "data" {
  instance_id = openstack_compute_instance_v2.supabase.id
  volume_id   = openstack_blockstorage_volume_v3.data.id
}

resource "openstack_networking_floatingip_v2" "supabase" {
  pool = var.external_network
}

resource "openstack_networking_floatingip_associate_v2" "supabase" {
  floating_ip = openstack_networking_floatingip_v2.supabase.address
  port_id     = openstack_networking_port_v2.supabase.id
}
variables.tfHCL
variable "key_name" {
  description = "SSH keypair name (must already exist in your project)"
  type        = string
}

variable "flavor_name" {
  description = "Instance size. The full self-hosted Supabase stack (Postgres plus Auth, REST, Realtime, Storage, Studio, Kong, and the analytics services) runs comfortably on 4 vCPU and 16 GiB RAM (m2a.xlarge). Size down to m2a.large (2 vCPU / 8 GiB) for light, single-developer use; size up for heavier workloads."
  type        = string
  default     = "m2a.xlarge"
}

variable "image_name" {
  description = "Operating system image. Ubuntu 24.04 is the recommended base."
  type        = string
  default     = "Ubuntu-24.04"
}

variable "app_name" {
  description = "Display name prefix for compute and network resources"
  type        = string
  default     = "supabase"
}

variable "volume_size" {
  description = "Block volume size in GiB, mounted at /var/lib/docker so the Postgres database, storage uploads, and analytics data live on a volume you can grow rather than on the boot disk."
  type        = number
  default     = 40
}

variable "external_network" {
  description = "Shared external network for router gateway and floating IPs; defaults to PublicStatic (persisted FIP / production pattern). Override with PublicEphemeral for ephemeral demos."
  type        = string
  default     = "PublicStatic"
}

variable "private_cidr" {
  description = "CIDR for the private tenant network the instance lives in"
  type        = string
  default     = "10.47.0.0/24"
}

variable "api_allowed_cidr" {
  description = "CIDR allowed to reach the Supabase API gateway (Kong) on port 8000 and Studio on port 3000. Defaults to the private network only, so the stack is not exposed to the public internet on its raw ports. Serve it over a domain with HTTPS on 443 behind a reverse proxy. To reach the ports directly from your workstation during setup, set this to YOUR_IP/32."
  type        = string
  default     = "10.47.0.0/24"
}
outputs.tfHCL
output "instance_id" {
  description = "ID of the compute instance running the Supabase stack"
  value       = openstack_compute_instance_v2.supabase.id
}

output "floating_ip" {
  description = "Public floating IP address of the Supabase host"
  value       = openstack_networking_floatingip_v2.supabase.address
}

output "private_ip" {
  description = "Private IP address of the instance"
  value       = openstack_compute_instance_v2.supabase.access_ip_v4
}

output "api_url" {
  description = "Supabase API gateway (Kong) URL on port 8000, reachable from api_allowed_cidr (the private network by default). The REST, Auth, Storage, and Realtime APIs route through this gateway. Put a reverse proxy in front and use HTTPS on 443, then set API_EXTERNAL_URL and SITE_URL in /opt/supabase/.env for production."
  value       = "http://${openstack_networking_floatingip_v2.supabase.address}:8000"
}

output "studio_url" {
  description = "Supabase Studio dashboard URL on port 3000, reachable from api_allowed_cidr. Studio is protected by the dashboard username and password generated on first boot into /opt/supabase/.env."
  value       = "http://${openstack_networking_floatingip_v2.supabase.address}:3000"
}
versions.tfHCL
terraform {
  required_version = ">= 1.6.0"

  required_providers {
    openstack = {
      source  = "terraform-provider-openstack/openstack"
      version = "~> 2.0"
    }
  }
}

provider "openstack" {}
terraform.tfvars.exampleHCL
# Required: SSH keypair must already exist in your project
key_name = "YOUR_KEY_NAME"

# Recommended: restrict the API gateway (8000) and Studio (3000) to your
# workstation IP for setup. Leave unset to keep those ports reachable only from
# the private network and tunnel over SSH. The stack expects a public URL for
# Auth callbacks, so the normal access path is a domain with HTTPS on 443
# behind a reverse proxy.
# api_allowed_cidr = "203.0.113.10/32"

# flavor_name = "m2a.xlarge"
# image_name = "Ubuntu-24.04"
# app_name = "supabase"
# volume_size = 40
# external_network = "PublicStatic"
# private_cidr = "10.47.0.0/24"
cloud-init/supabase.yaml.tftpl
#cloud-config
package_update: true
packages:
  - ca-certificates
  - curl
  - git
  - openssl
runcmd:
  - |
    set -e
    # Supabase self-host stack for ${app_name}. The Kong API gateway listens on
    # port 8000 (REST, Auth, Storage, Realtime) and Studio on port 3000. No
    # credential ships with this template: the Postgres password, JWT secret,
    # dashboard login, and the anon and service_role API keys are all generated
    # on first boot and written to /opt/supabase/.env on this instance only.
    #
    # The data volume attaches as /dev/sdb on this platform (not /dev/vdb).
    # Mount it at /opt/supabase before the stack is cloned so the Postgres data
    # (./volumes/db), storage uploads, and analytics data live on the resizable
    # volume rather than the boot disk.
    DEV=/dev/sdb
    for i in $(seq 1 30); do [ -b "$DEV" ] && break; sleep 5; done
    if ! blkid "$DEV" >/dev/null 2>&1; then mkfs.ext4 -F -L supabasedata "$DEV"; fi
    mkdir -p /opt/supabase
    mount "$DEV" /opt/supabase
    grep -q "$DEV" /etc/fstab || echo "$DEV /opt/supabase ext4 defaults,nofail 0 2" >> /etc/fstab

    # Install Docker Engine plus the compose plugin from Docker's convenience
    # script.
    curl -fsSL https://get.docker.com | sh

    # Fetch the official self-hosting stack (compose file, Kong config, and the
    # .env template) into /opt/supabase.
    git clone --depth 1 https://github.com/supabase/supabase /tmp/supabase
    cp -rf /tmp/supabase/docker/. /opt/supabase/
    cp /opt/supabase/.env.example /opt/supabase/.env
    rm -rf /tmp/supabase

    # Generate all secrets on first boot. These never leave this instance.
    POSTGRES_PASSWORD=$(openssl rand -hex 24)
    JWT_SECRET=$(openssl rand -hex 32)
    DASHBOARD_PASSWORD=$(openssl rand -hex 16)
    SECRET_KEY_BASE=$(openssl rand -hex 32)
    VAULT_ENC_KEY=$(openssl rand -hex 16)

    # Mint the anon and service_role API keys as HS256 JWTs signed with the new
    # JWT secret, the same scheme Supabase uses. Ten-year expiry.
    b64url() { openssl base64 -e -A | tr '+/' '-_' | tr -d '='; }
    gen_jwt() {
      ROLE="$1"
      HEADER='{"alg":"HS256","typ":"JWT"}'
      IAT=$(date +%s)
      EXP=$((IAT + 315360000))
      PAYLOAD="{\"role\":\"$ROLE\",\"iss\":\"supabase\",\"iat\":$IAT,\"exp\":$EXP}"
      H=$(printf '%s' "$HEADER" | b64url)
      P=$(printf '%s' "$PAYLOAD" | b64url)
      SIG=$(printf '%s' "$H.$P" | openssl dgst -sha256 -hmac "$JWT_SECRET" -binary | b64url)
      printf '%s.%s.%s' "$H" "$P" "$SIG"
    }
    ANON_KEY=$(gen_jwt anon)
    SERVICE_ROLE_KEY=$(gen_jwt service_role)

    # Replace a key in /opt/supabase/.env (or append it if absent).
    set_env() {
      KEY="$1"; VAL="$2"
      if grep -q "^$KEY=" /opt/supabase/.env; then
        sed -i "s|^$KEY=.*|$KEY=$VAL|" /opt/supabase/.env
      else
        echo "$KEY=$VAL" >> /opt/supabase/.env
      fi
    }

    set_env POSTGRES_PASSWORD "$POSTGRES_PASSWORD"
    set_env JWT_SECRET "$JWT_SECRET"
    set_env ANON_KEY "$ANON_KEY"
    set_env SERVICE_ROLE_KEY "$SERVICE_ROLE_KEY"
    set_env DASHBOARD_USERNAME "supabase"
    set_env DASHBOARD_PASSWORD "$DASHBOARD_PASSWORD"
    set_env SECRET_KEY_BASE "$SECRET_KEY_BASE"
    set_env VAULT_ENC_KEY "$VAULT_ENC_KEY"
    chmod 600 /opt/supabase/.env

    # Bring the stack up with the generated keys. The default API_EXTERNAL_URL
    # and SITE_URL point at localhost; set them to your public HTTPS address in
    # /opt/supabase/.env and restart before you serve real traffic:
    #   cd /opt/supabase && docker compose up -d
    cd /opt/supabase
    docker compose pull
    docker compose up -d
README.mdMarkdown
# Supabase self-host stack

Single compute instance running the full self-hosted [Supabase](https://supabase.com/docs/guides/self-hosting) stack (the open-source Firebase alternative) on infrastructure you control. After apply, the stack is running with generated credentials; you point a domain at the host, serve it over HTTPS, set the public URL, and start building against the Postgres database, Auth, Storage, Realtime, and the auto-generated REST API.


**Network class:** production — `external_network` defaults to `PublicStatic` for persisted floating IPs and multi-tier stacks; override with `PublicEphemeral` for ephemeral demos.

The instance provisions a private network, a floating IP, and a block volume mounted at `/opt/supabase` so the database, storage uploads, and analytics data live on a resizable volume. cloud-init installs Docker Engine, clones the official Supabase compose stack, generates every secret on first boot, and brings the stack up.

## Where this fits

Supabase is the backend-as-a-service layer for an application: a Postgres database with a REST and Realtime API, user authentication, file storage, and the Studio dashboard, all on infrastructure you own rather than on a third-party SaaS. It is the heaviest of the self-hosted application backends because it runs roughly a dozen coordinated containers.

## What gets generated on first boot

No credential ships with this template. cloud-init generates and writes the following into `/opt/supabase/.env`:

- `POSTGRES_PASSWORD`: the Postgres superuser password
- `JWT_SECRET`: the secret that signs and verifies all API tokens
- `ANON_KEY` and `SERVICE_ROLE_KEY`: HS256 JWTs signed with `JWT_SECRET`, minted on the instance with a ten-year expiry
- `DASHBOARD_USERNAME` and `DASHBOARD_PASSWORD`: the Studio login
- `SECRET_KEY_BASE` and `VAULT_ENC_KEY`: the Realtime and Vault encryption keys

Read them once over SSH and store them in your secret manager.

## Prerequisites

- OpenTofu >= 1.6.0 or Terraform >= 1.6.0
- Quake AI account with OpenStack credentials
- An existing SSH keypair in your project (the value of `key_name` must match that keypair)
- A domain you can point at the instance (Auth callbacks and Studio expect a stable public URL)

## Resource baseline

The full Supabase stack runs on 4 vCPU and 16 GiB RAM. The default `m2a.xlarge` flavor leaves headroom for Postgres plus the API, Auth, Realtime, Storage, Studio, and analytics containers. Size down to `m2a.large` (2 vCPU / 8 GiB) for light single-developer use; size up for heavier workloads.

## Usage

1. Clone or copy this template directory
2. Copy `terraform.tfvars.example` to `terraform.tfvars` and fill in your values
3. Source your OpenStack credentials: `source openrc.sh`
4. Initialize: `tofu init`
5. Preview: `tofu plan`
6. Apply: `tofu apply`

After apply, cloud-init installs Docker, clones the Supabase stack, generates every secret into `/opt/supabase/.env`, and runs `docker compose up -d`. The default `API_EXTERNAL_URL` and `SITE_URL` point at localhost. Finish the setup over SSH:

1. Point a domain's DNS A record at `floating_ip` and put a reverse proxy (Caddy or Nginx) in front for HTTPS on 443.
2. Edit `/opt/supabase/.env`: set `API_EXTERNAL_URL`, `SITE_URL`, and `SUPABASE_PUBLIC_URL` to your public HTTPS address.
3. Restart the stack:

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

## Access and security

The Kong API gateway listens on port 8000 (REST, Auth, Storage, Realtime) and Studio on port 3000, both over plain HTTP. The security group restricts 8000 and 3000 to `api_allowed_cidr`, which defaults to the private network only. Because Auth callbacks need a public URL, the normal access path is a domain with HTTPS on 443 behind a reverse proxy. Point the domain's DNS A record at `floating_ip`. Ports 80 and 443 stay open for that reverse proxy; they carry no traffic until you add one.

Studio is protected by `DASHBOARD_USERNAME` and `DASHBOARD_PASSWORD`. Rotate the `ANON_KEY` and `SERVICE_ROLE_KEY` (by changing `JWT_SECRET` and re-minting) before production if the instance was ever reachable with the generated defaults in place.

## Variables

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `key_name` | string | yes | n/a | SSH keypair name (must already exist in your project) |
| `flavor_name` | string | no | `m2a.xlarge` | Instance size (the full stack runs on 4 vCPU / 16 GiB) |
| `image_name` | string | no | `Ubuntu-24.04` | Operating system image |
| `app_name` | string | no | `supabase` | Display name prefix for resources |
| `volume_size` | number | no | `40` | Block volume size in GiB, mounted at `/opt/supabase` |
| `external_network` | string | no | `PublicStatic` | Persisted FIP / production default; override with `PublicEphemeral` for demos |
| `private_cidr` | string | no | `10.47.0.0/24` | CIDR for the private subnet |
| `api_allowed_cidr` | string | no | `10.47.0.0/24` | CIDR allowed to reach the API gateway (8000) and Studio (3000) |

## Outputs

| Name | Description |
| --- | --- |
| `floating_ip` | Public floating IP assigned to the instance |
| `private_ip` | Private IP address of the instance |
| `api_url` | Supabase API gateway URL on port 8000 |
| `studio_url` | Supabase Studio dashboard URL on port 3000 |
| `instance_id` | Compute instance ID |

## Scope

This is a single-VM Supabase host that you operate, not the managed Supabase cloud. It 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 watch resource use as your data grows. This template self-hosts Supabase; it does not route to or provision the hosted Supabase platform. For high availability, move Postgres onto its own instance and run the stateless services behind a load balancer.

## Documentation

See also: [self-managed PostgreSQL](/resources/iac-templates/self-managed-postgres), [S3 object storage](/resources/iac-templates/s3-storage-acl), [Coolify host](/resources/iac-templates/coolify-host)
Resources, parameters, and variables
Provisions
Parameterized by
Variables
  • key_namerequired
  • flavor_name="m2a.xlarge"
  • image_name="Ubuntu-24.04"
  • app_name="supabase"
  • volume_size=40
  • external_network="PublicStatic"
  • private_cidr="10.47.0.0/24"
  • api_allowed_cidr="10.47.0.0/24"

Customize this pattern#

See also#

Usage Guidelines

The sample code, software libraries, command line tools, proofs of concept, templates, and other related technology on this page (including any of the foregoing that is provided by Quake AI personnel) is provided to you as Quake AI Content under the Quake AI Customer Agreement, or the relevant written agreement between you and Quake AI (whichever applies). Do not use this Quake AI Content in your production accounts, or on production or other critical data. You are responsible for testing, securing, and optimizing the Quake AI Content (such as sample code) as appropriate for production grade use based on your specific quality control practices and standards. Deploying Quake AI Content may incur Quake AI charges for creating or using Quake AI chargeable resources, such as running Compute instances or storing data in Object Storage. Your use is also subject to the Acceptable Use Policy.

For the full policy, see Usage Guidelines.

Last validated: 30.06.2026

Was this page helpful?