화. 8월 12th, 2025

G: Are you a developer looking for more control over your backend services? Or perhaps you want to deploy Supabase closer to your data, or within a specific compliance boundary? While Supabase offers a fantastic managed service, self-hosting gives you ultimate flexibility and ownership. This guide will walk you through setting up Supabase on your own server using Docker Compose, including essential API domain configuration and SSL encryption. Let’s dive in! 🚀


Table of Contents:

  1. Why Self-Host Supabase? 🤔
  2. Prerequisites: Gearing Up Your Server ⚙️
  3. The Core: Setting Up Supabase with Docker Compose 🐳
    • Cloning the Repository
    • Configuring Your Environment Variables (.env)
    • Launching Supabase Services
  4. Domain & SSL: Making Supabase Accessible and Secure 🔒
    • Understanding the Reverse Proxy (Nginx)
    • Setting Up Nginx Configurations
    • Obtaining SSL Certificates with Certbot
    • Updating Supabase Environment Variables for Domains
  5. Testing Your Self-Hosted Supabase 🧪
  6. Important Considerations & Best Practices
    • Security First!
    • Backups are Crucial
    • Monitoring & Logging
    • Updating Your Supabase Stack
    • Email Service for Authentication
    • No Self-Hosted Studio/Dashboard
  7. Conclusion 🎉

1. Why Self-Host Supabase? 🤔

Supabase is an open-source Firebase alternative that provides a suite of tools for building your backend, including:

  • PostgreSQL Database: A powerful relational database.
  • GoTrue (Auth): User authentication and management.
  • Realtime: Real-time subscriptions to database changes.
  • Storage: File storage service.
  • PostgREST: A RESTful API layer for your database.
  • Edge Functions: Serverless functions (like AWS Lambda or Vercel Functions).

While the managed Supabase Cloud is excellent for most, self-hosting offers unique advantages:

  • Ultimate Control: You manage the entire stack, from OS to individual service configurations.
  • Cost Efficiency (Potentially): For very high usage or specific resource allocation, self-hosting might be more cost-effective than a managed service, especially if you already have server infrastructure.
  • Data Sovereignty & Compliance: Keep your data in specific geographical regions or meet stringent compliance requirements by hosting on your own infrastructure.
  • Customization: Tweak configurations, install custom PostgreSQL extensions, or integrate with existing systems exactly as you need.

When NOT to self-host: Self-hosting comes with operational overhead. If you prefer zero maintenance, automatic scaling, and a fully managed experience, the Supabase Cloud is usually the better choice. This guide is for those who embrace the “DevOps” side of things!


2. Prerequisites: Gearing Up Your Server ⚙️

Before we dive into the exciting part, let’s ensure your server is ready.

  • A Dedicated Server or VPS: (Virtual Private Server) with a clean installation of a Linux distribution. Ubuntu Server (20.04 LTS or 22.04 LTS) is highly recommended due to its widespread support.
    • Recommended Specs:
      • CPU: At least 2 Cores (4 Cores or more for production)
      • RAM: Minimum 4GB (8GB+ for production or heavy usage)
      • Disk: 50GB+ SSD (NVMe preferred for performance)
  • Domain Name: A registered domain name (e.g., yourdomain.com) that you control. You’ll need to set up subdomains for your Supabase services.
  • DNS Configuration: Access to your domain’s DNS settings to point subdomains to your server’s IP address.
  • Open Ports: Ensure the following ports are open on your server’s firewall (e.g., ufw on Ubuntu) to allow Nginx to receive external requests:
    • 80 (HTTP – for Certbot SSL challenge)
    • 443 (HTTPS – for secure API access)
  • Root or Sudo Access: You’ll need administrator privileges to install software.

Let’s get the essential tools installed:

# Update your package list
sudo apt update
sudo apt upgrade -y

# Install Git (for cloning the Supabase repository)
sudo apt install git -y

# Install Docker
# Official Docker installation script is the most reliable way
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# Install Docker Compose (if not included with Docker)
# For newer Docker versions, it's often 'docker compose' (without hyphen)
# Check if 'docker compose version' works first. If not, install via pip or standalone:
sudo apt install docker-compose-plugin -y # For 'docker compose' (plugin)
# OR (if you need the old 'docker-compose' command)
sudo curl -L "https://github.com/docker/compose/releases/download/v2.24.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# Verify installation
docker --version
docker compose version # Or docker-compose --version

DNS Setup: Before proceeding, go to your domain registrar’s DNS management panel and create A records for your chosen subdomains, pointing them to your server’s public IP address. For example:

  • api.yourdomain.com -> Your Server IP
  • auth.yourdomain.com -> Your Server IP
  • storage.yourdomain.com -> Your Server IP
  • realtime.yourdomain.com -> Your Server IP

It might take some time (up to 24-48 hours, though usually faster) for DNS changes to propagate. You can test propagation using dig or online tools like whatsmydns.net.


3. The Core: Setting Up Supabase with Docker Compose 🐳

Supabase provides an official docker directory within its main repository that contains everything you need to self-host.

Cloning the Repository

First, log in to your server and clone the Supabase repository:

git clone --depth 1 https://github.com/supabase/supabase.git
cd supabase/docker

Now you are in the directory containing the docker-compose.yml file and the .env.example file.

Configuring Your Environment Variables (.env)

This is the most critical step for a successful and secure deployment. We need to create a .env file based on the example and populate it with your specific details and secure keys.

cp .env.example .env
nano .env # Or use your preferred text editor like vi or vim

Let’s go through the essential variables you must configure:

# .env file content (simplified for demonstration)

# --- GLOBAL SETTINGS ---
# Generate these securely! You can use 'openssl rand -base64 32' for JWT_SECRET
# and 'openssl rand -hex 32' for ANON_KEY and SERVICE_ROLE_KEY.
# DO NOT USE THESE EXAMPLE VALUES IN PRODUCTION!
JWT_SECRET="YOUR_SUPER_STRONG_JWT_SECRET_HERE_AT_LEAST_32_CHARS" 🔑

# These are your public and private keys for interacting with Supabase services.
# ANON_KEY is the 'public' key for your application.
# SERVICE_ROLE_KEY is the 'private' key, use with extreme care.
ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.YOUR_GENERATED_ANON_KEY_HERE" # Generate a new one!
SERVICE_ROLE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.YOUR_GENERATED_SERVICE_ROLE_KEY_HERE" # Generate a new one!

# Main project URL for Supabase client libraries (e.g., Supabase-JS)
# This will be your primary API endpoint domain (e.g., https://api.yourdomain.com)
SITE_URL="http://localhost:3000" # <-- WE WILL CHANGE THIS LATER TO YOUR DOMAIN!

# Database connection string for Supabase services to connect to PostgreSQL.
# Usually, you don't need to change this unless you modify the PostgreSQL setup.
DATABASE_URL="postgresql://postgres:postgres@db:5432/postgres"

# --- GoTrue (Authentication) Settings ---
# Important: Set this to your authentication domain later (e.g., https://auth.yourdomain.com)
GOTRUE_URL="http://localhost:9999" # <-- WE WILL CHANGE THIS LATER TO YOUR DOMAIN!

# JWT expiration for authentication tokens (e.g., 3600 seconds = 1 hour)
JWT_EXPIRY="3600" # Recommended: Keep this relatively short

# Enable email or SMS for authentication. (Set to true or false)
# Requires additional SMTP settings below if true.
GOTRUE_EXTERNAL_EMAIL_ENABLED="false"
GOTRUE_EXTERNAL_SMS_ENABLED="false"

# Example SMTP settings (uncomment and configure if email enabled)
# SMTP_HOST="smtp.example.com"
# SMTP_PORT="587"
# SMTP_USER="your_smtp_user"
# SMTP_PASS="your_smtp_password"
# SMTP_SENDER_NAME="Your App Name"
# SMTP_SENDER_EMAIL="no-reply@yourdomain.com"

# --- PostgREST (API) Settings ---
# Important: Set this to your API domain later (e.g., https://api.yourdomain.com)
POSTGREST_URL="http://localhost:8000" # <-- WE WILL CHANGE THIS LATER TO YOUR DOMAIN!

# --- Realtime Settings ---
# Important: Set this to your Realtime domain later (e.g., https://realtime.yourdomain.com)
REALTIME_URL="http://localhost:4000" # <-- WE WILL CHANGE THIS LATER TO YOUR DOMAIN!
REALTIME_DB_URL="postgres://postgres:postgres@db:5432/postgres"
REALTIME_DB_SCHEMAS="public,supabase_functions" # Schemas to watch for changes

# --- Storage Settings ---
# Important: Set this to your Storage domain later (e.g., https://storage.yourdomain.com)
STORAGE_URL="http://localhost:5000" # <-- WE WILL CHANGE THIS LATER TO YOUR DOMAIN!
FILE_SIZE_LIMIT="50MB" # Max file size for uploads

# --- Inbucket (Email Testing - Development Only) ---
# Set to true if you want an in-memory email server for testing auth emails
INBUCKET_ENABLED="false" # KEEP THIS FALSE FOR PRODUCTION!

# --- Postgres Extensions ---
# List of PostgreSQL extensions to enable by default.
# pg_net and pg_cron are commonly used.
# PG_NET_ENABLED="true"
# PG_CRON_ENABLED="true"

Key Points for .env:

  • Generate Strong Secrets! Do not use the example values. Use tools like openssl rand -base64 32 for JWT_SECRET and openssl rand -hex 32 to generate random strings for ANON_KEY and SERVICE_ROLE_KEY. Even better, generate them via a secure key generator and ensure they are Base64 encoded if needed, or follow Supabase’s specific instructions for key formats. The provided ANON_KEY and SERVICE_ROLE_KEY in .env.example are JWT tokens themselves, not just random strings. You’ll need to generate valid JWTs with appropriate role and iss claims if you want to change them. For simplicity, you can initially use the ones provided by supabase/.env.example as a template, but change the underlying JWT secret to your own generated one.
  • Initial SITE_URL and service URLs: For the initial startup, leave them as http://localhost:PORT. We will update these after Nginx and SSL are configured.
  • SMTP Settings: If you plan to use email authentication, uncomment and fill in your SMTP server details. You’ll need an external email provider (e.g., SendGrid, Mailgun, AWS SES, or your own SMTP server).

After editing, save and close the .env file.

Launching Supabase Services

Once your .env is configured, you can bring up the Supabase services using Docker Compose:

docker compose up -d

This command will:

  • Download all necessary Docker images (PostgreSQL, GoTrue, PostgREST, Realtime, Storage, etc.).
  • Create and start containers for each service in detached mode (-d).

Give it a few minutes for all services to start, especially PostgreSQL. You can check the status:

docker compose ps
# Expected output similar to this (status should be 'running'):
# Name                    Command               State    Ports
# -----------------------------------------------------------------------------------------------------------------
# docker-db-1             docker-entrypoint.sh ... Up       0.0.0.0:5432->5432/tcp
# docker-gotrue-1         ./gotrue               Up       0.0.0.0:9999->9999/tcp
# docker-inbucket-1       inbucket               Up       9000/tcp, 0.0.0.0:9000->9000/tcp, 0.0.0.0:9001->9001/tcp
# docker-kong-1           /docker-entrypoint.sh ...Up       0.0.0.0:8000->8000/tcp, 0.0.0.0:8443->8443/tcp, 8001/tcp, 8444/tcp
# docker-postgrest-1      ./postgrest            Up       0.0.0.0:8000->8000/tcp
# docker-realtime-1       ./realtime             Up       0.0.0.0:4000->4000/tcp
# docker-storage-1        ./storage              Up       0.0.0.0:5000->5000/tcp

(Note: Kong is often part of the example, acting as an API gateway, but we’ll use Nginx for external exposure and SSL.)

At this point, your Supabase services are running internally on your server, accessible via localhost:PORT.


4. Domain & SSL: Making Supabase Accessible and Secure 🔒

To expose your Supabase services to the outside world using your custom domains and secure them with HTTPS, we’ll use Nginx as a reverse proxy and Certbot for automatic Let’s Encrypt SSL certificates.

Understanding the Reverse Proxy (Nginx)

A reverse proxy acts as an intermediary for requests from clients to your backend services.

  • It listens on standard ports (80 for HTTP, 443 for HTTPS).
  • It forwards requests to the correct internal Docker container port (e.g., GoTrue running on port 9999, PostgREST on 8000).
  • It handles SSL/TLS termination, decrypting incoming HTTPS requests and forwarding them as HTTP to the backend, or re-encrypting if configured.

Setting Up Nginx Configurations

Install Nginx:

sudo apt install nginx -y
sudo systemctl start nginx
sudo systemctl enable nginx

Now, create Nginx configuration files for each of your Supabase services. We’ll put them in /etc/nginx/sites-available/ and then link them to /etc/nginx/sites-enabled/.

Let’s assume your domain is yourdomain.com and your subdomains are:

  • api.yourdomain.com (for PostgREST API)
  • auth.yourdomain.com (for GoTrue Authentication)
  • storage.yourdomain.com (for Storage service)
  • realtime.yourdomain.com (for Realtime service)

Create /etc/nginx/sites-available/supabase-api.conf:

# /etc/nginx/sites-available/supabase-api.conf
server {
    listen 80;
    server_name api.yourdomain.com; # <--- REPLACE WITH YOUR API DOMAIN!

    location / {
        proxy_pass http://localhost:8000; # PostgREST internal port
        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_buffering off; # Important for streaming/realtime
    }
}

Create /etc/nginx/sites-available/supabase-auth.conf:

# /etc/nginx/sites-available/supabase-auth.conf
server {
    listen 80;
    server_name auth.yourdomain.com; # <--- REPLACE WITH YOUR AUTH DOMAIN!

    location / {
        proxy_pass http://localhost:9999; # GoTrue internal port
        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_buffering off; # Important for streaming/realtime
    }
}

Create /etc/nginx/sites-available/supabase-storage.conf:

# /etc/nginx/sites-available/supabase-storage.conf
server {
    listen 80;
    server_name storage.yourdomain.com; # <--- REPLACE WITH YOUR STORAGE DOMAIN!

    location / {
        proxy_pass http://localhost:5000; # Storage internal port
        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_buffering off; # Important for streaming/realtime
    }
}

Create /etc/nginx/sites-available/supabase-realtime.conf:

# /etc/nginx/sites-available/supabase-realtime.conf
server {
    listen 80;
    server_name realtime.yourdomain.com; # <--- REPLACE WITH YOUR REALTIME DOMAIN!

    location / {
        # For Realtime, we specifically need WebSocket proxying
        proxy_pass http://localhost:4000; # Realtime internal port
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        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_buffering off;
    }
}

After creating these files, enable them by creating symbolic links:

sudo ln -s /etc/nginx/sites-available/supabase-api.conf /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/supabase-auth.conf /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/supabase-storage.conf /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/supabase-realtime.conf /etc/nginx/sites-enabled/

Test your Nginx configuration for syntax errors and restart Nginx:

sudo nginx -t
sudo systemctl restart nginx

Obtaining SSL Certificates with Certbot

Now that Nginx is configured to listen on port 80 for your domains, we can use Certbot to automatically obtain and install SSL certificates from Let’s Encrypt.

sudo apt install certbot python3-certbot-nginx -y

Run Certbot for all your Supabase domains:

sudo certbot --nginx -d api.yourdomain.com -d auth.yourdomain.com -d storage.yourdomain.com -d realtime.yourdomain.com

Follow the prompts. Certbot will automatically modify your Nginx configuration files to include the SSL certificates and redirect HTTP traffic to HTTPS.

After Certbot finishes, test Nginx again and restart:

sudo nginx -t
sudo systemctl restart nginx

Your Supabase services are now accessible via HTTPS on your custom domains! 🎉

Updating Supabase Environment Variables for Domains

Finally, you need to tell your Supabase services about their new external domain names. Go back to your supabase/docker/.env file:

cd ~/supabase/docker # If you're not already there
nano .env

Update the following variables to reflect your new https:// domains:

# .env file

# Main project URL for Supabase client libraries
SITE_URL="https://api.yourdomain.com" # <--- IMPORTANT: This is the base URL for Supabase.js!

# --- GoTrue (Authentication) Settings ---
GOTRUE_URL="https://auth.yourdomain.com"

# --- PostgREST (API) Settings ---
POSTGREST_URL="https://api.yourdomain.com" # Generally same as SITE_URL for client libs

# --- Realtime Settings ---
REALTIME_URL="https://realtime.yourdomain.com"

# --- Storage Settings ---
STORAGE_URL="https://storage.yourdomain.com"

Save the .env file. Then, restart your Supabase Docker containers for the changes to take effect:

docker compose down
docker compose up -d

Give it a moment for everything to spin up. You now have a self-hosted Supabase instance accessible via your secure, custom domains! 🥳


5. Testing Your Self-Hosted Supabase 🧪

It’s time to verify everything works as expected.

Basic API Call (PostgREST)

You can try fetching data (even if your database is empty) or checking the PostgREST root.

curl https://api.yourdomain.com/rest/v1/
# You should get a JSON response with schema details, indicating PostgREST is alive.
# Example: {"openapi":"3.0.3","info":{"title":"...

Authentication (GoTrue)

Try to sign up a new user using your auth.yourdomain.com endpoint.

curl -X POST 'https://auth.yourdomain.com/signup' \
-H 'Content-Type: application/json' \
-d '{
    "email": "test@example.com",
    "password": "supersecretpassword"
}'
# If GOTRUE_EXTERNAL_EMAIL_ENABLED is false, user will be created immediately.
# Otherwise, it will send an email for confirmation.

Using Supabase-JS Client

This is how you’ll typically interact with your self-hosted instance from your application.

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

const supabaseUrl = 'https://api.yourdomain.com' // Your main API domain
const supabaseAnonKey = 'YOUR_ANON_KEY_FROM_YOUR_DOT_ENV_FILE' // Get this from your .env!

const supabase = createClient(supabaseUrl, supabaseAnonKey)

async function testSupabase() {
  // Example: Fetch data from a table (replace 'your_table' with an actual table)
  // Or just call a simple auth method to test connectivity
  const { user, error } = await supabase.auth.signUp({
    email: 'newuser@example.com',
    password: 'password123'
  })

  if (error) {
    console.error('Auth Error:', error.message)
  } else {
    console.log('User signed up successfully:', user)
  }

  // Example: Query your database
  const { data, error: dbError } = await supabase.from('your_table').select('*')
  if (dbError) {
    console.error('DB Error:', dbError.message)
  } else {
    console.log('Data from your_table:', data)
  }
}

testSupabase()

Important: Ensure you use the correct supabaseUrl which is your SITE_URL (usually api.yourdomain.com) and your actual ANON_KEY from the .env file.


6. Important Considerations & Best Practices ✨

Self-hosting gives you power, but with power comes responsibility!

  • Security First!

    • Strong Secrets: Never use default or weak JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY. Generate truly random, long strings.
    • Firewall: Only open necessary ports (80, 443 for web traffic, 22 for SSH). Close 5432 (PostgreSQL), 9999 (GoTrue), etc., to external access. Nginx proxies take care of forwarding.
    • SSH Security: Use SSH keys instead of passwords, disable root login, and change default SSH port.
    • Keep Software Updated: Regularly update your OS, Docker, Nginx, and Supabase components.
    • Regular Audits: Periodically review your server configurations and logs for suspicious activity.
  • Backups are Crucial 💾

    • Your data is now your responsibility. Implement a robust backup strategy for your PostgreSQL database.
    • You can use pg_dump to create logical backups:
      docker compose exec db pg_dump -U postgres -d postgres > your_supabase_db_backup_$(date +%F).sql
    • Automate this with cron jobs and store backups securely, preferably off-site.
  • Monitoring & Logging 📊

    • Monitor your server’s resources (CPU, RAM, Disk I/O) to ensure optimal performance.
    • Check Docker container logs for errors or warnings:
      docker compose logs -f
      docker compose logs -f gotrue
    • Set up alerts for critical issues.
  • Updating Your Supabase Stack ⬆️

    • To update your Supabase services, you’ll generally pull the latest changes from the supabase/supabase repository and then rebuild/restart your Docker containers.
    • Always read release notes first! Breaking changes can occur.
    • Process:
      cd ~/supabase # Go to the root of your cloned repo
      git pull origin master
      cd docker
      docker compose pull # Pull latest images for all services
      docker compose up -d --remove-orphans # Recreate containers with new images
  • Email Service for Authentication 📧

    • If you enabled email authentication (GOTRUE_EXTERNAL_EMAIL_ENABLED=true), ensure your SMTP settings in .env are correctly configured and that your SMTP server allows emails to be sent from your server’s IP address.
    • Test email delivery thoroughly.
  • No Self-Hosted Studio/Dashboard 🚫

    • As of now, the interactive Supabase Studio (the web-based dashboard you see in the cloud version) is not officially part of the self-hosting Docker Compose setup.
    • You’ll interact with your database using tools like psql, DBeaver, or VS Code’s database extensions. For authentication, storage, and real-time, you’ll use their respective APIs or client libraries directly.

7. Conclusion 🎉

Congratulations! You’ve successfully deployed Supabase on your own server, complete with custom API domains and SSL encryption. This powerful setup gives you complete control over your backend, enabling you to build scalable and secure applications with confidence.

Remember that self-hosting requires ongoing maintenance and vigilance, but the benefits of ownership and customization can be well worth the effort. Keep learning, keep building, and happy coding with your very own Supabase instance! If you encounter issues, the Supabase Discord and GitHub issues are excellent resources. 🚀

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다