Skip to main contentSkip to navigation
[email protected]
Client AreaSupport
Hosting Mammoth
HostingMammothYour Data, Our Responsibility
Home
Solutions
Hosting Services
Store
Pricing
About
Blog
API
Contact

Stay Ahead of the Curve

Get the latest insights on cybersecurity, AI innovations, and enterprise data solutions delivered to your inbox.

Hosting Mammoth
HostingMammothEnterprise Solutions

Enterprise-grade data solutions. Hosting, recovery, cybersecurity, and AI-powered services for businesses worldwide.

[email protected]
Sun - Fri, 9:00am - 5:00pm

Services

  • Cloud Hosting
  • Data Recovery
  • Cybersecurity
  • Legal Support
  • MSP Services
  • Web Development
  • AI Services
  • Free Server Migration

Hosting

  • VPS Hosting (NVMe SSD)
  • VDS Hosting (NVMe)
  • Storage VPS (High SSD)
  • GPU Servers
  • Managed Services
  • Cloud Firewall
  • Load Balancer
  • One-Click Apps
  • n8n Hosting
  • Object Storage
  • FAQ

Company

  • Store
  • Pricing
  • About Us
  • Locations
  • Blog
  • Testimonials
  • Contact
  • Affiliate Program
  • White-Label
  • Terms of Service
  • Privacy Policy
  • Browser Cookies
  • SLA

Support

  • Client Area
  • Submit Ticket
  • Knowledge Base
  • Server Status
  • API Documentation

© 2026 Hosting Mammoth. All rights reserved.

Knowledge Base
Getting StartedAccount ManagementVPS HostingGPU ServersStorage VPSCloud FirewallLoad BalancerServer ManagementBilling & PaymentsSupport & TicketsAffiliate ProgramReseller ProgramMarketplace & Appsn8n HostingManaged ServicesServer MigrationAPI & DevelopersSecurityTroubleshootingGlossaryInstall Guides
  1. Home
  2. /
  3. Support
  4. /
  5. Install Guides
  6. /
  7. How To Install Supabase Ubuntu
GUIDEInstall Guides

How to Install Self-Hosted Supabase on Ubuntu 24.04 VPS: Open-Source Firebase Alternative

29 min read

How to Install Self-Hosted Supabase on Ubuntu 24.04 VPS: The Open-Source Firebase Alternative

Supabase is the open-source backend-as-a-service (BaaS) that has become the de facto alternative to Firebase for teams who want Postgres-grade power, open standards, and the option to self-host the entire stack. With a single Docker Compose deployment, you get a production-ready Postgres 15 database, an auto-generated REST API, a realtime WebSocket server, an authentication service, S3-compatible object storage, Deno-powered edge functions, and a polished web Studio for managing it all.

This guide walks you through a complete self-hosted Supabase install on Ubuntu 24.04 LTS, from provisioning the VPS to exposing Studio over HTTPS on your own domain. By the end you will have a fully functional Supabase stack that you control end-to-end, with no per-row pricing and no data leaving your server.

Want the speed of a managed platform with the control of self-hosting? Deploy Supabase on a CloudCore Business VPS — our best-value plan for backend services with 8 vCPU, 24 GB RAM and 200 GB NVMe storage.

Table of Contents

  • What is Self-Hosted Supabase?
  • Why Self-Host Supabase Instead of Using Supabase Cloud?
  • Prerequisites
  • Step 1: Prepare the Ubuntu 24.04 Server
  • Step 2: Install Docker and Docker Compose
  • Step 3: Clone the Official Supabase Repository
  • Step 4: Generate Secure Secrets (JWT, Anon & Service Keys)
  • Step 5: Configure the .env File
  • Step 6: Start the Supabase Stack
  • Step 7: Verify Each Service
  • Step 8: Configure the Kong API Gateway
  • Step 9: Expose Studio via Nginx with HTTPS
  • Step 10: Test Auth, PostgREST, Realtime, Storage and Edge Functions
  • Backups and Upgrades
  • Troubleshooting
  • FAQ
  • Next Steps
  • What is Self-Hosted Supabase?

    Supabase is an open-source developer platform that bundles seven production services behind a single unified API. When you self-host, you run the exact same containers that power Supabase Cloud on your own infrastructure:

    • Postgres 15 — the primary database, with all Supabase extensions pre-enabled (pgcrypto, pgjwt, pg_graphql, pg_stat_statements, pgsodium, vault, and pgvector for AI workloads).
    • Supabase Studio — a Next.js web UI for managing schemas, running SQL, editing rows, browsing storage buckets, and configuring auth providers.
    • GoTrue (Auth) — email, magic link, phone, SAML and 20+ OAuth providers for end-user authentication.
    • PostgREST — automatically turns your Postgres schema into a RESTful JSON API with filtering, pagination, and joins.
    • Realtime — an Elixir/Phoenix server that streams Postgres logical replication changes to connected clients via WebSocket.
    • Storage API — an S3-compatible object storage layer backed by the filesystem (or MinIO/S3), with row-level security tied to your Postgres policies.
    • Edge Functions — a Deno runtime for globally deployable TypeScript functions, ideal for webhooks and third-party integrations.
    • Kong API Gateway — a single entry point that routes /auth/, /rest/, /realtime/, /storage/, and /functions/* to the right service and enforces JWT validation.
    All of this runs from a single docker-compose.yml file maintained by the Supabase team. You get the same SDKs (@supabase/supabase-js, the Python, Flutter, Kotlin and Swift clients) and the same Studio — only the URL changes from xxxx.supabase.co to your-domain.com.

    Why Self-Host Supabase Instead of Using Supabase Cloud?

    Supabase Cloud is excellent for prototypes and small apps, but serious production workloads often hit limits that make self-hosting the better choice:

    • No per-row, per-MAU or per-bandwidth charges. A VPS cost is fixed, whether you store 1 GB or 1 TB, whether you serve 1,000 or 1,000,000 monthly active users.
    • Full data sovereignty and compliance. GDPR, HIPAA, SOC 2 audits, and EU-only data residency rules are easy to satisfy when the database physically lives in a datacenter you selected.
    • Direct Postgres superuser access. You can install any extension, tweak postgresql.conf, run pg_dump at the filesystem level, and attach debugging tools like pgBadger without waiting for vendor support.
    • No paused projects or cold starts. Supabase Cloud pauses inactive free-tier projects after a week. Your self-hosted stack never sleeps.
    • Freedom to customise. You can swap Kong for Traefik, replace the Storage backend with MinIO or Cloudflare R2, patch GoTrue, or run multiple Supabase projects on one server.
    • Predictable cost at scale. The break-even point for most SaaS apps is around 10,000 MAU or 50 GB of storage — beyond that, self-hosting on a CloudCore VPS is dramatically cheaper than Cloud Pro/Team tiers.
    • Vendor independence. Should Supabase change its business model (as has happened with Parse, Heroku, and Firebase's pricing), your stack keeps running exactly as it is today.

    Cost Comparison: Supabase Cloud vs. Self-Hosted

    WorkloadSupabase Cloud (Pro)Supabase Cloud (Team)Self-Hosted on VPS
    Base fee$25/mo$599/mo~EUR 24.99/mo (CloudCore Business)
    Included compute2 vCPU micro2 vCPU small8 vCPU
    Included RAM1 GB2 GB24 GB
    Included storage8 GB8 GB200 GB NVMe
    Bandwidth250 GB included250 GB includedUnmetered (32 TB fair use)
    MAU limit100K100KUnlimited
    Daily backups7-day PITR14-day PITRYour responsibility
    Total for 500K MAU + 100 GB$600+/mo$900+/moEUR 24.99/mo (flat)
    For any app growing past the free tier, the math almost always favours a dedicated VPS.

    Prerequisites

    Before you start, make sure you have:

    • A VPS running Ubuntu 24.04 LTS with root or sudo access.
    • SSH access to your server (Terminal on macOS/Linux, or PuTTY/Windows Terminal on Windows).
    • At least 4 vCPU, 8 GB RAM and 40 GB of SSD storage. Supabase runs roughly 11 containers; anything smaller will swap heavily.
    • A registered domain name pointed to the server's IP via an A record (required for HTTPS and OAuth callback URLs).
    • Basic familiarity with the Linux command line and Docker concepts.
    Recommended Plan: CloudCore Business
    >
    The Supabase stack is a cluster of 11 containers — Postgres alone needs serious RAM headroom for shared buffers, WAL, and logical replication. We recommend the CloudCore Business plan:
    >
    - 8 vCPU cores
    - 24 GB RAM
    - 200 GB NVMe SSD
    - 32 TB bandwidth
    - From EUR 24.99/month
    >
    This gives you comfortable headroom for Postgres tuning, Realtime replication slots, Edge Function cold starts, and room to host your application stack alongside Supabase.

    If you still need to install Postgres, Docker, or Nginx as standalone services elsewhere in your infrastructure, see our related guides:

    • How to Install PostgreSQL on Ubuntu
    • How to Install Docker on Ubuntu
    • How to Install Nginx on Ubuntu
    Connect to your server via SSH:

    bash
    ssh root@your-server-ip

    Step 1: Prepare the Ubuntu 24.04 Server

    Update the system and create a non-root user for day-to-day operations. Running Docker as root is possible, but a dedicated user reduces blast radius if a container is compromised.

    bash
    sudo apt update && sudo apt upgrade -y
    sudo apt install -y ca-certificates curl gnupg lsb-release ufw git jq openssl

    Create a user named supabase (or any name you prefer) and add it to the sudo group:

    bash
    sudo adduser supabase
    sudo usermod -aG sudo supabase

    Configure the firewall. We only expose SSH (22), HTTP (80) and HTTPS (443) to the internet — all Supabase internal ports stay on the Docker network:

    bash
    sudo ufw default deny incoming
    sudo ufw default allow outgoing
    sudo ufw allow 22/tcp
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    sudo ufw --force enable
    sudo ufw status

    Expected output:

    text
    Status: active
    To                         Action      From
    --                         ------      ----
    22/tcp                     ALLOW       Anywhere
    80/tcp                     ALLOW       Anywhere
    443/tcp                    ALLOW       Anywhere

    Reboot if the kernel was updated:

    bash
    sudo reboot

    Reconnect as the supabase user after the reboot:

    bash
    ssh supabase@your-server-ip

    Step 2: Install Docker and Docker Compose

    Supabase is distributed as a Docker Compose stack, so Docker Engine (≥ 24.0) and the Compose v2 plugin are required.

    Add Docker's official GPG key and repository:

    bash
    sudo install -m 0755 -d /etc/apt/keyrings
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
      sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
    sudo chmod a+r /etc/apt/keyrings/docker.gpg

    echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

    Install Docker Engine, CLI, containerd and the Compose plugin:

    bash
    sudo apt update
    sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

    Add your user to the docker group so you do not need sudo for every command:

    bash
    sudo usermod -aG docker $USER
    newgrp docker

    Verify:

    bash
    docker --version
    docker compose version

    Expected output:

    text
    Docker version 27.3.1, build ce12230
    Docker Compose version v2.29.7

    Enable the service on boot:

    bash
    sudo systemctl enable --now docker

    Step 3: Clone the Official Supabase Repository

    Supabase ships all Docker assets in the supabase/supabase monorepo under the docker/ directory. Clone a shallow copy (you only need the current state of main):

    bash
    cd ~
    git clone --depth 1 https://github.com/supabase/supabase.git
    cp -rp supabase/docker supabase-project
    cd supabase-project

    You now have a working directory structured like this:

    text
    supabase-project/
    ├── docker-compose.yml          # the main stack definition (11 services)
    ├── .env.example                # template for secrets and config
    ├── volumes/
    │   ├── api/kong.yml            # Kong API gateway declarative config
    │   ├── db/                     # Postgres init SQL (roles, realtime, etc.)
    │   ├── functions/              # Deno edge function templates
    │   ├── logs/                   # Vector/logflare log routing
    │   └── storage/                # Storage backend mount point
    └── dev/                        # dev-only helpers (ignore)

    Copy the example env file to .env — we will fill it in next:

    bash
    cp .env.example .env

    Step 4: Generate Secure Secrets (JWT, Anon & Service Keys)

    Supabase's security model relies on a JWT secret that signs two long-lived API keys:

    • ANON_KEY — used by browser/mobile clients. Rows returned are filtered by Postgres Row Level Security (RLS) policies.
    • SERVICE_ROLE_KEY — bypasses RLS. Treat it like a root password: only use it from trusted server-side code.
    Generate a strong, 40-character JWT secret:

    bash
    openssl rand -base64 40 | tr -d '\n/+=' | cut -c1-40

    Copy the output — you will paste it into .env as JWT_SECRET.

    Next, generate the two JWTs. Supabase provides an online generator at supabase.com/docs/guides/self-hosting#api-keys, but you can also produce them locally with a small Python script:

    bash
    sudo apt install -y python3-pip
    pip3 install --break-system-packages pyjwt

    Create a helper file:

    bash
    cat > ~/gen-keys.py <<'EOF'
    import jwt, sys, time
    secret = sys.argv[1]
    now = int(time.time())
    

    10-year expiry for self-hosted use

    exp = now + (60 60 24 365 10)

    anon = jwt.encode( {"role": "anon", "iss": "supabase", "iat": now, "exp": exp}, secret, algorithm="HS256" ) service = jwt.encode( {"role": "service_role", "iss": "supabase", "iat": now, "exp": exp}, secret, algorithm="HS256" ) print("ANON_KEY=" + anon) print("SERVICE_ROLE_KEY=" + service) EOF

    Run it with your JWT secret:

    bash
    python3 ~/gen-keys.py "your-40-char-jwt-secret-here"

    Expected output:

    text
    ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
    SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

    Save both values — you will paste them into .env.

    Finally, generate passwords for the Postgres superuser and Studio dashboard:

    bash
    # Postgres password
    openssl rand -base64 32 | tr -d '\n/+=' | cut -c1-32

    Studio dashboard password

    openssl rand -base64 24 | tr -d '\n/+=' | cut -c1-24

    Step 5: Configure the .env File

    Open the .env file you copied earlier and fill in the secrets you just generated:

    bash
    nano .env

    Here are the key variables to set. Anything not shown can be left at its default:

    bash
    ############
    

    Secrets

    ############ POSTGRES_PASSWORD=your-32-char-postgres-password JWT_SECRET=your-40-char-jwt-secret ANON_KEY=eyJhbGciOiJI...your-anon-jwt SERVICE_ROLE_KEY=eyJhbGciOiJI...your-service-role-jwt DASHBOARD_USERNAME=supabase DASHBOARD_PASSWORD=your-24-char-studio-password

    ############

    Database

    ############ POSTGRES_HOST=db POSTGRES_DB=postgres POSTGRES_PORT=5432

    ############

    API Proxy (Kong)

    ############ KONG_HTTP_PORT=8000 KONG_HTTPS_PORT=8443

    ############

    API — public URL clients will hit

    ############ SITE_URL=https://supabase.yourdomain.com SUPABASE_PUBLIC_URL=https://supabase.yourdomain.com API_EXTERNAL_URL=https://supabase.yourdomain.com ADDITIONAL_REDIRECT_URLS=

    ############

    Auth (GoTrue)

    ############ DISABLE_SIGNUP=false ENABLE_EMAIL_SIGNUP=true ENABLE_EMAIL_AUTOCONFIRM=false ENABLE_ANONYMOUS_USERS=false ENABLE_PHONE_SIGNUP=false ENABLE_PHONE_AUTOCONFIRM=false

    ############

    SMTP — required for password resets and confirmations

    ############ [email protected] SMTP_HOST=smtp.yourprovider.com SMTP_PORT=587 SMTP_USER=your-smtp-user SMTP_PASS=your-smtp-password SMTP_SENDER_NAME=Supabase

    ############

    Studio

    ############ STUDIO_DEFAULT_ORGANIZATION=MyOrg STUDIO_DEFAULT_PROJECT=Default Project STUDIO_PORT=3000

    ############

    Edge Functions

    ############ FUNCTIONS_VERIFY_JWT=false

    ############

    Realtime — left as default, uses logical replication on the db container

    ############

    ############

    Storage

    ############ STORAGE_BACKEND=file GLOBAL_S3_BUCKET= REGION=local
    Important: The SITE_URL and API_EXTERNAL_URL must match the public HTTPS URL clients will use. OAuth callbacks, password reset links, and realtime subscriptions all rely on this value. If you change it later, restart the stack.

    Save and exit (Ctrl+O, Enter, Ctrl+X).

    Step 6: Start the Supabase Stack

    Pull all images first — this can take several minutes depending on your bandwidth:

    bash
    docker compose pull

    Then start the stack in detached mode:

    bash
    docker compose up -d

    Expected output (abbreviated):

    text
    [+] Running 11/11
     ✔ Container supabase-db              Healthy
     ✔ Container supabase-vector          Started
     ✔ Container supabase-analytics       Healthy
     ✔ Container supabase-auth            Started
     ✔ Container supabase-rest            Started
     ✔ Container supabase-realtime        Started
     ✔ Container supabase-storage         Started
     ✔ Container supabase-imgproxy        Started
     ✔ Container supabase-meta            Started
     ✔ Container supabase-edge-functions  Started
     ✔ Container supabase-kong            Started
     ✔ Container supabase-studio          Started

    Check running containers:

    bash
    docker compose ps

    All 11 services should be in state running (some with healthy health checks). If any container is restarting, view its logs:

    bash
    docker compose logs -f <service-name>

    Common first-boot issue: the db container takes 30-60 seconds to finish initial migrations before auth, rest, and realtime can connect. Give it a minute before declaring anything broken.

    Step 7: Verify Each Service

    Test each internal service through Kong on port 8000 (still localhost at this stage — we will add HTTPS next):

    PostgREST (REST API)

    bash
    curl http://localhost:8000/rest/v1/ \
      -H "apikey: $ANON_KEY"

    Expected output:

    json
    {"swagger":"2.0","info":{"description":"...","title":"PostgREST API","version":"12.0.1"}, ...}

    Auth (GoTrue)

    bash
    curl http://localhost:8000/auth/v1/health

    Expected output:

    json
    {"version":"v2.160.0","name":"GoTrue","description":"GoTrue is a user registration and authentication API"}

    Realtime

    bash
    curl http://localhost:8000/realtime/v1/api/tenants/realtime-dev/health \
      -H "apikey: $ANON_KEY"

    Expected output:

    json
    {"healthy":true}

    Storage

    bash
    curl http://localhost:8000/storage/v1/bucket \
      -H "apikey: $SERVICE_ROLE_KEY" \
      -H "Authorization: Bearer $SERVICE_ROLE_KEY"

    Expected output (empty array on a fresh install):

    json
    []

    Edge Functions

    bash
    curl http://localhost:8000/functions/v1/hello \
      -H "Authorization: Bearer $ANON_KEY"

    You will get a 404 until you deploy a function — that is expected. The gateway is routing correctly.

    Studio

    Studio is exposed on port 3000 behind basic auth. Try it via SSH tunnel:

    bash
    ssh -L 3000:localhost:3000 supabase@your-server-ip

    Open http://localhost:3000 in your browser. You should see the Supabase Studio login — enter the DASHBOARD_USERNAME and DASHBOARD_PASSWORD from .env.

    Step 8: Configure the Kong API Gateway

    Kong is already running with a sensible default configuration from volumes/api/kong.yml. It declaratively defines:

    • Routes for /auth/v1/, /rest/v1/, /realtime/v1/, /storage/v1/, /functions/v1/, /pg/, /analytics/v1/*.
    • JWT validation on protected routes using your anon and service_role keys.
    • CORS headers for browser clients.
    • Rate limiting plugin stubs you can enable.
    Inspect the current config:

    bash
    cat volumes/api/kong.yml | head -80

    Key sections you may want to customise:

    Rate limiting. Add a plugins block to any service to throttle abusive clients. Example — cap anonymous REST requests at 60/minute:

    yaml
    services:
      - name: rest-v1
        url: http://rest:3000/
        routes:
          - name: rest-v1-all
            strip_path: true
            paths:
              - /rest/v1/
        plugins:
          - name: rate-limiting
            config:
              minute: 60
              policy: local

    Custom CORS. Edit the cors plugin at the top of kong.yml to restrict Access-Control-Allow-Origin to your frontend domain:

    yaml
    plugins:
      - name: cors
        config:
          origins:
            - https://app.yourdomain.com
          methods:
            - GET
            - POST
            - PUT
            - DELETE
            - OPTIONS
            - PATCH
          credentials: true
          max_age: 3600

    After editing kong.yml, reload Kong without downtime:

    bash
    docker compose restart kong

    Kong re-parses the declarative config at startup. You can verify by hitting an endpoint and checking the response headers for the new CORS origin.

    Step 9: Expose Studio via Nginx with HTTPS

    Right now every Supabase service listens only on 127.0.0.1. We will put Nginx in front as a reverse proxy on the host, serve Studio and the public API over HTTPS, and obtain a free Let's Encrypt certificate.

    Install Nginx and Certbot:

    bash
    sudo apt install -y nginx certbot python3-certbot-nginx

    Create a new Nginx site at /etc/nginx/sites-available/supabase:

    bash
    sudo tee /etc/nginx/sites-available/supabase > /dev/null <<'EOF'
    

    Redirect HTTP to HTTPS

    server { listen 80; server_name supabase.yourdomain.com; return 301 https://$host$request_uri; }

    Public API and Studio over HTTPS

    server { listen 443 ssl http2; server_name supabase.yourdomain.com;

    # Certbot will fill these in ssl_certificate /etc/letsencrypt/live/supabase.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/supabase.yourdomain.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5;

    # Security headers add_header X-Content-Type-Options nosniff; add_header X-Frame-Options DENY; add_header Referrer-Policy no-referrer-when-downgrade;

    client_max_body_size 50m;

    # Route Studio (HTML UI) at / location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }

    # Route the Supabase API (Kong) at /auth, /rest, /realtime, /storage, /functions location ~ ^/(auth|rest|realtime|storage|functions|analytics|pg)/ { proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;

    # WebSocket support for Realtime proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 86400; } } EOF

    Enable the site and validate:

    bash
    sudo ln -s /etc/nginx/sites-available/supabase /etc/nginx/sites-enabled/
    sudo rm -f /etc/nginx/sites-enabled/default
    sudo nginx -t

    Obtain an SSL certificate with Certbot (it handles renewing Nginx config automatically):

    bash
    sudo certbot --nginx -d supabase.yourdomain.com
    sudo systemctl reload nginx

    Certbot installs a systemd timer that renews the certificate twice daily. Verify:

    bash
    sudo systemctl list-timers | grep certbot

    Now open https://supabase.yourdomain.com in a browser — you should see Studio with a valid padlock icon.

    Step 10: Test Auth, PostgREST, Realtime, Storage and Edge Functions

    With HTTPS in place, test each service through its public URL. Replace supabase.yourdomain.com and the keys with your own values.

    Sign up a user (Auth)

    bash
    curl -X POST https://supabase.yourdomain.com/auth/v1/signup \
      -H "apikey: $ANON_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "email": "[email protected]",
        "password": "StrongPassword123!"
      }'

    Expected output includes an access_token, a refresh_token, and a user object with a UUID.

    Create a table and query it (PostgREST)

    In Studio, open the SQL editor and run:

    sql
    create table public.todos (
      id bigserial primary key,
      task text not null,
      done boolean default false,
      created_at timestamptz default now()
    );

    alter table public.todos enable row level security;

    create policy "anyone can read todos" on public.todos for select using (true);

    insert into public.todos (task) values ('Ship Supabase self-hosted');

    Then from your terminal:

    bash
    curl "https://supabase.yourdomain.com/rest/v1/todos?select=*" \
      -H "apikey: $ANON_KEY" \
      -H "Authorization: Bearer $ANON_KEY"

    Expected output:

    json
    [{"id":1,"task":"Ship Supabase self-hosted","done":false,"created_at":"2026-04-16T10:00:00Z"}]

    Subscribe to realtime changes

    Using the JavaScript SDK (install with npm install @supabase/supabase-js):

    js
    import { createClient } from '@supabase/supabase-js'

    const supabase = createClient( 'https://supabase.yourdomain.com', 'YOUR_ANON_KEY' )

    supabase .channel('todos') .on('postgres_changes', { event: '*', schema: 'public', table: 'todos' }, (payload) => console.log('Change:', payload)) .subscribe()

    Any INSERT, UPDATE, or DELETE on the todos table streams to your client via WebSocket.

    Upload a file (Storage)

    bash
    # Create a bucket
    curl -X POST https://supabase.yourdomain.com/storage/v1/bucket \
      -H "Authorization: Bearer $SERVICE_ROLE_KEY" \
      -H "Content-Type: application/json" \
      -d '{"id":"public-assets","name":"public-assets","public":true}'

    Upload a file

    echo "hello supabase" > demo.txt curl -X POST https://supabase.yourdomain.com/storage/v1/object/public-assets/demo.txt \ -H "Authorization: Bearer $SERVICE_ROLE_KEY" \ --data-binary @demo.txt

    Fetch the public URL

    curl https://supabase.yourdomain.com/storage/v1/object/public/public-assets/demo.txt

    Deploy an Edge Function

    Install the Supabase CLI locally (on your laptop, not the server):

    bash
    npm install -g supabase
    supabase login

    Create and deploy a function:

    bash
    supabase functions new hello
    

    Edit supabase/functions/hello/index.ts

    supabase functions deploy hello --project-ref your-self-hosted-ref \ --no-verify-jwt

    For self-hosted deployments, copy the function directory into volumes/functions/hello/ on the server and restart the edge runtime:

    bash
    docker compose restart functions

    Then call it:

    bash
    curl https://supabase.yourdomain.com/functions/v1/hello \
      -H "Authorization: Bearer $ANON_KEY"

    Backups and Upgrades

    Automated Postgres Backups

    The db container persists data to the supabase-project/volumes/db/data directory. For production, schedule nightly logical backups to off-server storage.

    Create a backup script:

    bash
    sudo tee /usr/local/bin/supabase-backup.sh > /dev/null <<'EOF'
    #!/bin/bash
    set -euo pipefail
    STAMP=$(date +%Y%m%d-%H%M%S)
    BACKUP_DIR=/var/backups/supabase
    mkdir -p "$BACKUP_DIR"

    docker compose -f /home/supabase/supabase-project/docker-compose.yml exec -T db \ pg_dumpall -U postgres | gzip > "$BACKUP_DIR/supabase-$STAMP.sql.gz"

    Keep last 14 days

    find "$BACKUP_DIR" -name 'supabase-*.sql.gz' -mtime +14 -delete EOF sudo chmod +x /usr/local/bin/supabase-backup.sh

    Add a cron entry at 02:00 daily:

    bash
    sudo crontab -e
    

    Add:

    0 2 * /usr/local/bin/supabase-backup.sh >> /var/log/supabase-backup.log 2>&1

    For off-site safety, pipe the gzipped output to rclone or aws s3 cp to push it to S3, Backblaze B2 or a second VPS.

    Upgrading Supabase

    Pull the latest images and recreate containers:

    bash
    cd ~/supabase-project
    docker compose pull
    docker compose up -d

    Supabase migrations on the db container run automatically at startup. Always back up before upgrading — major Postgres version jumps may require a dump/restore rather than an in-place upgrade.

    Troubleshooting

    ProblemCauseSolution
    supabase-db container keeps restartingStale volume from a previous .env with a different POSTGRES_PASSWORDStop stack, delete volumes/db/data, restart. You will lose data — only do this on a fresh install.
    auth logs show connection refused: db:5432db is still initialisingWait 60 seconds. If it persists, check docker compose logs db for crash reasons.
    Studio shows "Failed to fetch" on the SQL editormeta service crashed or wrong SUPABASE_PUBLIC_URLRestart meta: docker compose restart meta. Verify SUPABASE_PUBLIC_URL matches your HTTPS URL.
    Realtime subscriptions never receive eventsLogical replication not enabled on the publicationRun SELECT * FROM pg_publication; in SQL editor. If empty, connect to db and run CREATE PUBLICATION supabase_realtime FOR ALL TABLES;
    JWT expired errors from PostgRESTServer clock driftsudo timedatectl set-ntp true and restart.
    Edge function returns 401 Missing authorization headerFUNCTIONS_VERIFY_JWT=true but no bearer token passedEither pass Authorization: Bearer $ANON_KEY or set FUNCTIONS_VERIFY_JWT=false in .env.
    kong container healthy but all requests return 404kong.yml syntax error silently ignoreddocker compose logs kong \</td><td>grep -i error<code>. Validate YAML, then </code>docker compose restart kong.
    Nginx returns 502 Bad Gateway for /realtime/Missing WebSocket upgrade headersEnsure the proxy_set_header Upgrade and Connection "upgrade" lines are present in the location block.
    Out of memory — containers OOM-killed4 GB VPS too small for the full stackUpgrade to at least 8 GB RAM, or disable analytics and vector containers if you do not need log routing.

    Useful log commands

    bash
    # Tail all services
    docker compose logs -f

    Just one service

    docker compose logs -f auth

    Postgres slow queries

    docker compose exec db psql -U postgres -c \ "SELECT query, calls, mean_exec_time FROM pg_stat_statements ORDER BY mean_exec_time DESC LIMIT 10;"

    Disk usage per container volume

    docker system df -v

    FAQ

    Can I run self-hosted Supabase alongside my existing app on the same server?

    Yes, and it is a common pattern. Supabase listens only on 127.0.0.1 by default, and only Nginx on 80/443 is exposed. You can run a Next.js/Nuxt/Django app on another internal port (say 3001) and add another Nginx server {} block for its own subdomain, all on the same VPS. On a CloudCore Business plan with 24 GB RAM, Supabase uses around 4-6 GB at idle, leaving plenty for your app, Redis, queues, and a reverse proxy.

    How do I migrate an existing Supabase Cloud project to self-hosted?

    Supabase Cloud lets you download a full Postgres dump from the dashboard: Project Settings → Database → Backups → Download. Transfer the .sql file to your VPS and restore it into the self-hosted db container with docker compose exec -T db psql -U postgres < dump.sql. You then re-apply any storage buckets by copying them from the Cloud via the CLI and updating any hardcoded Cloud URLs in your client to your new SUPABASE_PUBLIC_URL. Auth users migrate as part of the Postgres dump since they live in the auth.users table.

    Is self-hosted Supabase production-ready?

    Yes. The Docker Compose stack you deployed runs the exact same container images that power Supabase Cloud. Production hardening is your responsibility though: schedule backups, put the stack behind a firewall, enable Postgres ssl, rotate JWT keys periodically, monitor with Prometheus/Grafana, and upgrade regularly. For mission-critical workloads consider running a hot-standby replica on a second VPS using Postgres streaming replication.

    What is the difference between the anon key and the service role key?

    The anon key is safe to embed in browser and mobile clients. Every request it authorises is subject to Row Level Security (RLS) policies you define in Postgres — the database itself decides which rows the request can see. The service role key bypasses RLS entirely and grants full database privileges. Never ship it to a client. Use it only in server-side code, cron jobs, and admin tools. If a service role key leaks, rotate JWT_SECRET immediately and regenerate both keys, then restart the stack.

    Can I use a managed Postgres (like AWS RDS or Neon) instead of the bundled db container?

    Yes. Point POSTGRES_HOST, POSTGRES_PASSWORD, and POSTGRES_DB in .env at your external Postgres, remove the db service from docker-compose.yml, and ensure the required extensions (pgcrypto, pgjwt, pg_graphql, pgsodium, pgvector) are installed there. You lose the convenience of one-command startup, but you gain managed backups, HA failover, and point-in-time recovery.

    How do I add OAuth providers like Google or GitHub?

    Add the relevant variables to .env. For example, Google:

    bash
    GOTRUE_EXTERNAL_GOOGLE_ENABLED=true
    GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID=your-client-id
    GOTRUE_EXTERNAL_GOOGLE_SECRET=your-client-secret
    GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI=https://supabase.yourdomain.com/auth/v1/callback

    Restart the auth container: docker compose restart auth. Then in your Google Cloud Console, add the redirect URI shown above to the OAuth client's allow-list. Repeat for GitHub, GitLab, Azure, Discord, and the 20+ other supported providers.

    Next Steps

    Now that Supabase is running on your VPS, here are practical next steps to get the most out of it:

    • Wire up your frontend. Install @supabase/supabase-js in your Next.js, Nuxt, SvelteKit, or React Native app and point it at https://supabase.yourdomain.com with your anon key. The SDK is 100% API-compatible with Supabase Cloud.
    • Tune Postgres. The default container ships with conservative settings. Edit volumes/db/postgres.conf or pass environment variables to increase shared_buffers, effective_cache_size, and work_mem to match your VPS RAM.
    • Install pgvector for AI. Supabase's Postgres image includes pgvector. Run create extension vector; to start storing OpenAI, Cohere, or local embeddings alongside your relational data — perfect for RAG systems.
    • Set up monitoring. Scrape Postgres with postgres_exporter, Kong with its Prometheus plugin, and the Docker daemon with cadvisor. Visualise in Grafana to catch slow queries and memory leaks before users do.
    • Deploy a CI/CD pipeline for Edge Functions. Store your functions in Git and push them to volumes/functions/ via GitHub Actions and SSH — no proprietary deploy platform required.
    • Add rate limiting and WAF. Enable Kong's rate-limiting, bot-detection, and ip-restriction plugins to protect your public endpoints from abuse.
    • Scale horizontally. When one VPS is not enough, run Postgres on a dedicated database VPS, and put the stateless services (auth, rest, realtime, storage, functions, kong, studio) behind a load balancer on two or more app VPSes.
    For deeper self-hosting references, see the official documentation at supabase.com/docs/guides/self-hosting.

    Skip the infrastructure setup — start with a backend-ready VPS
    >
    Our CloudCore Business plan is sized precisely for backend stacks like Supabase, with 8 vCPU, 24 GB RAM and 200 GB NVMe storage on EU-hosted infrastructure.
    >
    - Ubuntu 24.04 LTS pre-installed
    - Unmetered-tier bandwidth (32 TB fair use)
    - Full root access and snapshot backups
    - IPv4 + IPv6 included
    >
    Deploy your CloudCore Business VPS — from EUR 24.99/month.

    Was this article helpful?

    ← Back to Install GuidesBrowse all categories →

    Still have questions?

    Contact Support →Submit a Ticket