DEV Community

Cover image for Deploy Supabase on Bare Metal: Secure Self-Hosted Firebase
Jakson Tate
Jakson Tate

Posted on • Originally published at servermo.com

Deploy Supabase on Bare Metal: Secure Self-Hosted Firebase

Supabase is a magnificent open-source backend alternative providing a massive relational database, robust authentication, and real-time subscription capabilities. However, deploying this architecture securely requires profound engineering knowledge.

Countless online tutorials instruct developers to clone the repository and execute the start command blindly. This practice is extremely dangerous. The default configurations expose your raw database port directly to the public internet and misconfigure critical API routing endpoints. In this masterclass, we will deploy Supabase on a high-performance bare metal server, locking down every microservice with enterprise-grade security architectures.


Phase 1: Clone the Supabase Architecture

First, authenticate into your ServerMO bare metal machine via secure shell. We will clone only the latest release depth of the official repository to save bandwidth and initialize our configuration files perfectly.

# Clone the repository minimizing git history
git clone --depth 1 [https://github.com/supabase/supabase](https://github.com/supabase/supabase)
cd supabase/docker

# Duplicate the template environment file
cp .env.example .env
Enter fullscreen mode Exit fullscreen mode

Phase 2: Generate Cryptographic Secrets

The most critical security failure developers make is ignoring the placeholder secrets. If you deploy using the default JSON Web Token (JWT) keys, anyone on the internet can forge an administrative token and commandeer your infrastructure. We must generate mathematically secure cryptographic keys immediately.

Critical Security Warning

Never reuse keys across different environments. The Service Role Key bypasses all database Row Level Security (RLS) policies automatically. Treat this string with the exact same reverence as your primary database password.

# Execute the official secret generation script
sh utils/generate-keys.sh

# Inject the newly minted asymmetric keys into your environment
sh utils/add-new-auth-keys.sh
Enter fullscreen mode Exit fullscreen mode

After generating these keys, open your environment file and update the public domain parameters so authentication callbacks route correctly back to your users.

# Edit your environment configuration
nano .env
Enter fullscreen mode Exit fullscreen mode
# Modify these specific lines matching your final production domain
SITE_URL=[https://supabase.yourdomain.com](https://supabase.yourdomain.com)
API_EXTERNAL_URL=[https://supabase.yourdomain.com](https://supabase.yourdomain.com)
Enter fullscreen mode Exit fullscreen mode

Phase 3: Fix the Docker Firewall Bypass

Docker automatically alters Linux iptables networking rules to route traffic into containers. This means if you use a standard firewall (like UFW) to block port 5432, but the docker-compose.yml file exposes it globally, the port remains wide open to global attackers. You must explicitly instruct the service to bind these critical ports strictly to your local machine loopback interface.

# Open the main compose configuration
# nano docker-compose.yml

# Find the Kong API gateway service and modify the ports
  kong:
    ports:
      # SECURE: Bound exclusively to localhost
      - 127.0.0.1:${KONG_HTTP_PORT}:8000
      - 127.0.0.1:${KONG_HTTPS_PORT}:8443

# Find the Studio dashboard service and secure it
  studio:
    ports:
      - 127.0.0.1:${STUDIO_PORT}:3000

# Find the Database service and ensure it is not globally exposed
  db:
    ports:
      - 127.0.0.1:${POSTGRES_PORT}:5432
Enter fullscreen mode Exit fullscreen mode

Phase 4: Initialize the Supabase Stack

With your cryptographic secrets secured and your container networking safely bound to localhost, you can now pull the massive microservice architecture. This stack includes the Realtime engine, GoTrue authentication, and the robust PostgREST server.

# Pull the latest container images from the registry
docker compose pull

# Execute the entire infrastructure stack in detached mode
docker compose up -d

# Verify all fifteen containers achieved a healthy operational state
docker compose ps
Enter fullscreen mode Exit fullscreen mode

Phase 5: Configure Exact Nginx Routing

Since we securely locked all containers to localhost, external users cannot access your platform yet. We must deploy an Nginx reverse proxy to intercept public traffic.

Many amateur tutorials incorrectly route all API requests through an arbitrary sub-directory structure, causing instant 404 failures because the official client SDKs expect exact root-level endpoints. Furthermore, failing to inject WebSocket upgrade headers directly into the Kong gateway block will instantly murder your Realtime database connections.

# Install the web server and certificate provisioning tools
sudo apt install nginx certbot python3-certbot-nginx -y

# Create the proxy configuration file
sudo nano /etc/nginx/sites-available/supabase
Enter fullscreen mode Exit fullscreen mode

Paste the following enterprise routing configuration, ensuring WebSockets upgrade properly and client IP addresses forward accurately for rate limiting.

server {
    listen 80;
    server_name supabase.yourdomain.com;

    # Route Studio Dashboard
    location / {
        proxy_pass [http://127.0.0.1:3000](http://127.0.0.1:3000);
        proxy_set_header Host $host;
    }

    # CRITICAL: Route exactly how the Client SDK expects utilizing regular expressions
    location ~ ^/(rest|auth|realtime|storage)/v1/ {
        proxy_pass [http://127.0.0.1:8000](http://127.0.0.1:8000);
        proxy_set_header Host $host;

        # Forward true client identity for secure rate limiting
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # CRITICAL: Prevent the Realtime WebSocket from dropping
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";

        # Maintain persistent idle connections for live database subscriptions
        proxy_read_timeout 86400;
    }
}
Enter fullscreen mode Exit fullscreen mode
# Enable the configuration and acquire encrypted certificates
sudo ln -s /etc/nginx/sites-available/supabase /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d supabase.yourdomain.com
Enter fullscreen mode Exit fullscreen mode

Phase 6: Eradicating Bad Gateway Errors

Many developers complain about encountering sudden 502 Bad Gateway errors after deploying their infrastructure. They mistakenly blame their web server configuration.

The brutal reality is that this architecture requires immense hardware capabilities. When fifteen heavy containers compete for resources on a cheap, shared virtual server, the operating system runs out of memory. The Linux kernel's Out-Of-Memory (OOM) killer responds by silently assassinating the API gateway or database, generating massive connection drops. Furthermore, the Realtime subscription engine requires tremendous disk IOPS to broadcast database changes rapidly.


Technical Architecture Overview: Baseline vs. Enterprise SRE

Architectural Layer Vulnerable Baseline Cloud Setup Enterprise Bare Metal Standard (ServerMO)
Port Exposure Exposing 5432 globally due to Docker iptables overrides. Strict 127.0.0.1 binding for Postgres and Kong.
Authentication Using default placeholder JWT keys from .env.example. Generating cryptographically secure runtime secrets.
Proxy Routing Arbitrary nested /api/ paths causing SDK 404s. Exact Regex root-level endpoints (/rest/v1/).
WebSockets Standard HTTP forwarding (drops Realtime events). Explicit Upgrade & Connection proxy headers.
Uptime Stability 502 Bad Gateway crashes due to shared VPS OOM killing. Unthrottled memory & NVMe on Dedicated Bare Metal.

Secure Deployment FAQ

Why is my Supabase Postgres port exposed to the internet?
By default, Docker dynamically alters your Linux iptables to route network traffic, bypassing standard firewalls (like UFW) completely. If you map a port without specifying an IP address, it becomes globally accessible. You must explicitly bind the database to 127.0.0.1 in your compose file to secure it.

Why does my Supabase Realtime subscription drop instantly?
If your reverse proxy lacks WebSocket upgrade headers or implements a strict timeout limit, your Realtime connections will terminate abruptly. You must configure Nginx to forward HTTP upgrade requests to the Kong gateway and drastically extend the proxy read timeout limits (proxy_read_timeout 86400;).

Why am I getting 404 errors from my Supabase client SDK?
The official client libraries execute requests strictly to root-level endpoints like /rest/v1/ and /auth/v1/. If you configured your proxy to nest these endpoints under an arbitrary /api/ directory, the SDK cannot resolve the pathways, resulting in permanent 404 routing failures.

What causes the Supabase 502 Bad Gateway error?
A 502 error occurs when the Nginx proxy cannot reach the Supabase Kong gateway. This usually happens on underpowered virtual servers where the Linux Out-Of-Memory (OOM) killer terminates the Kong or Realtime containers due to RAM exhaustion.


The ServerMO Enterprise Advantage

You cannot run a heavy production database platform on throttled shared infrastructure. By hosting your stack on ServerMO Dedicated Servers, you gain exclusive access to enterprise Non-Volatile Memory Express (NVMe) storage arrays and unthrottled processor cores. This guarantees your backend scales flawlessly without ever triggering memory panics or gateway timeouts.

🔗 Deploy Your Dedicated Supabase Fleet at ServerMO: ServerMO Enterprise Bare Metal

Top comments (0)