pulse-zax/README-SELFHOSTED-SUPABASE.md

14 KiB

Self-Hosted Supabase Installation Guide

This guide walks you through deploying Pulse with a fully self-hosted Supabase stack (PostgreSQL + GoTrue + Kong) using Docker Compose. No external Supabase project required.


Table of Contents

  1. System Requirements
  2. Prerequisites
  3. Install Docker
  4. Install Bun
  5. Clone the Repository
  6. Generate Secrets
  7. Configure Environment
  8. Build and Start
  9. Set Up HTTPS
  10. Firewall Configuration
  11. Claim Server Ownership
  12. Verify Installation
  13. Updating Pulse
  14. OAuth Setup (Optional)
  15. Federation Setup (Optional)
  16. Troubleshooting

System Requirements

Resource Minimum Recommended
CPU 2 cores 4 cores
RAM 2 GB 4 GB
Disk 10 GB 20 GB+ (depends on file uploads)
OS Ubuntu 22.04 LTS Ubuntu 24.04 LTS
Network Public IP Public IP + domain name

This deployment runs four Docker containers: PostgreSQL, GoTrue (auth), Kong (API gateway), and the Pulse application.


Prerequisites

  • A server with Ubuntu 22.04+ and root/sudo access
  • A domain name with an A record pointing to your server's public IP (for HTTPS)
  • SSH access to your server
  • Git installed (sudo apt install git if not already present)

Install Docker

# Update system packages
sudo apt update && sudo apt upgrade -y

# Install required packages
sudo apt install -y ca-certificates curl gnupg

# Add Docker's official GPG key
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

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

# Install Docker Engine + Compose
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
sudo usermod -aG docker $USER
newgrp docker

# Verify
docker --version
docker compose version

Install Bun

Bun is needed to run the key generator script:

curl -fsSL https://bun.sh/install | bash
source ~/.bashrc
bun --version

Clone the Repository

sudo mkdir -p /opt/pulse
sudo chown $USER:$USER /opt/pulse
git clone https://github.com/plsechat/pulse-chat.git /opt/pulse
cd /opt/pulse

Generate Secrets

Generate a JWT secret and two Supabase API keys:

cd /opt/pulse
bun docker/generate-keys.ts

Output:

JWT_SECRET=<base64 string>
SUPABASE_ANON_KEY=<jwt token>
SUPABASE_SERVICE_ROLE_KEY=<jwt token>

Copy all three values for the next step.


Configure Environment

cp .env.supabase.example .env
nano .env

Fill in the values:

# PostgreSQL password — strong and URL-safe (no / = + characters)
POSTGRES_PASSWORD=ChangeMeToSomethingSecure123

# Paste the three values from generate-keys.ts
JWT_SECRET=<paste here>
SUPABASE_ANON_KEY=<paste here>
SUPABASE_SERVICE_ROLE_KEY=<paste here>

# Your public domain (must match your DNS A record)
SITE_URL=https://your-domain.com

Important:

  • POSTGRES_PASSWORD must be URL-safe. Avoid /, =, +, and other special characters.
  • SITE_URL must start with https:// for production.
  • The JWT_SECRET generated by generate-keys.ts already meets the 32-character minimum.

Environment Variable Reference

Variable Required Default Description
POSTGRES_PASSWORD Yes Password for PostgreSQL
JWT_SECRET Yes Secret key for signing JWTs (min 32 chars)
SUPABASE_ANON_KEY Yes Supabase public/anonymous API key
SUPABASE_SERVICE_ROLE_KEY Yes Supabase admin service role key
SITE_URL Yes Public URL (e.g., https://pulse.example.com)
PULSE_PORT No 4991 Host port for Pulse
JWT_EXPIRY No 3600 Token expiry in seconds
PUBLIC_IP No auto Public IP for WebRTC
GOOGLE_OAUTH_ENABLED No false Enable Google login
GOOGLE_OAUTH_CLIENT_ID No Google OAuth client ID
GOOGLE_OAUTH_SECRET No Google OAuth client secret
DISCORD_OAUTH_ENABLED No false Enable Discord login
DISCORD_OAUTH_CLIENT_ID No Discord OAuth client ID
DISCORD_OAUTH_SECRET No Discord OAuth client secret
FACEBOOK_OAUTH_ENABLED No false Enable Facebook login
FACEBOOK_OAUTH_CLIENT_ID No Facebook OAuth client ID
FACEBOOK_OAUTH_SECRET No Facebook OAuth client secret
TWITCH_OAUTH_ENABLED No false Enable Twitch login
TWITCH_OAUTH_CLIENT_ID No Twitch OAuth client ID
TWITCH_OAUTH_SECRET No Twitch OAuth client secret
ADDITIONAL_REDIRECT_URLS No Extra OAuth callback URLs
REGISTRATION_DISABLED No false Block new registrations (existing users can still log in; valid invite codes bypass)
GIPHY_API_KEY No Giphy API key for GIF search

Build and Start

cd /opt/pulse
docker compose -f docker-compose-supabase.yml up -d

The first launch takes a few minutes while Docker downloads the required images.

Verify containers are running

docker compose -f docker-compose-supabase.yml ps

You should see four containers, all Up:

NAME         IMAGE                            STATUS
pulse-db     supabase/postgres:15.6.1.143     Up (healthy)
pulse-auth   supabase/gotrue:v2.170.0         Up
pulse-kong   kong:3.4                         Up
pulse        pulse-pulse                      Up

Check logs

docker logs pulse

Test the health endpoint

curl http://localhost:4991/healthz

Should return {"status":"ok","timestamp":...}.


Set Up HTTPS

Pulse does not handle TLS itself. Use a reverse proxy for HTTPS.

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy

Configure /etc/caddy/Caddyfile:

your-domain.com {
    handle /auth/v1/* {
        reverse_proxy localhost:8000
    }
    handle {
        reverse_proxy localhost:4991
    }
}
sudo systemctl restart caddy
sudo systemctl enable caddy

Caddy automatically obtains and renews Let's Encrypt certificates.

Option B: Nginx

sudo apt install nginx certbot python3-certbot-nginx

Create /etc/nginx/sites-available/pulse:

server {
    server_name your-domain.com;

    location /auth/v1/ {
        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;
    }

    location / {
        proxy_pass http://127.0.0.1:4991;
        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_read_timeout 86400;
    }
}
sudo ln -s /etc/nginx/sites-available/pulse /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl restart nginx
sudo certbot --nginx -d your-domain.com

Firewall Configuration

sudo ufw allow 22/tcp    # SSH
sudo ufw allow 80/tcp    # HTTP
sudo ufw allow 443/tcp   # HTTPS
sudo ufw allow 40000:40020/udp  # WebRTC voice/video
sudo ufw allow 40000:40020/tcp  # WebRTC TCP fallback
sudo ufw enable
Port Protocol Purpose
22 TCP SSH access
80 TCP HTTP (redirects to HTTPS)
443 TCP HTTPS (web + WebSocket)
4991 TCP Pulse (only if no reverse proxy)
40000-40020 UDP + TCP WebRTC media (voice/video/screen share)

Note: Docker manipulates iptables directly and can bypass ufw rules. Ports mapped in docker-compose-supabase.yml may be publicly accessible even if ufw doesn't allow them. The compose file binds Kong (port 8000) to 127.0.0.1 so it is only accessible from the reverse proxy — do not change this to 0.0.0.0.


Claim Server Ownership

  1. Open https://your-domain.com in your browser
  2. Register a new account
  3. Find the ownership token in the server logs:
docker logs pulse 2>&1 | grep -i token
  1. In the Pulse web interface, open browser DevTools (F12)
  2. Go to the Console tab and run: useToken('your_token_here')
  3. Your account is now the server owner

The ownership token is printed once on first start. Save it somewhere secure.


Verify Installation

  • https://your-domain.com loads the login page
  • You can register and log in
  • You claimed ownership with the token
  • Text channels work (send/receive messages)
  • File uploads work
  • Voice channels work (join, speak, hear others)
  • docker compose -f docker-compose-supabase.yml ps shows all 4 containers healthy

Updating Pulse

cd /opt/pulse
docker compose -f docker-compose-supabase.yml pull
docker compose -f docker-compose-supabase.yml up -d

This pulls the latest published image and restarts the containers. Database migrations are handled automatically on startup.


OAuth Setup (Optional)

Pulse supports OAuth login via Google, Discord, Facebook, and Twitch through GoTrue. All OAuth configuration is done through your .env file — do not edit docker-compose-supabase.yml directly.

Google OAuth

  1. Go to Google Cloud Console
  2. Create a project and go to APIs & Services > Credentials
  3. Create an OAuth 2.0 Client ID (Web application)
  4. Set the authorized redirect URI to: https://your-domain.com/auth/v1/callback
  5. Add to your .env:
GOOGLE_OAUTH_ENABLED=true
GOOGLE_OAUTH_CLIENT_ID=your-client-id
GOOGLE_OAUTH_SECRET=your-client-secret
  1. Restart: docker compose -f docker-compose-supabase.yml up -d

Discord OAuth

  1. Go to Discord Developer Portal
  2. Create an application, go to OAuth2, add redirect: https://your-domain.com/auth/v1/callback
  3. Add to your .env:
DISCORD_OAUTH_ENABLED=true
DISCORD_OAUTH_CLIENT_ID=your-client-id
DISCORD_OAUTH_SECRET=your-client-secret
  1. Restart: docker compose -f docker-compose-supabase.yml up -d

The same pattern applies for Facebook and Twitch — replace DISCORD with FACEBOOK or TWITCH in the variable names.


Federation Setup (Optional)

Federation lets multiple Pulse instances connect so users can discover and join servers across instances.

  1. Edit the config file:
nano data/pulse/config.ini
  1. Add or modify:
[federation]
enabled=true
domain=your-domain.com
  1. Restart:
docker compose -f docker-compose-supabase.yml restart pulse

Connect Two Instances

On Instance A: Go to Server Settings > Federation > Generate Keys > Add Instance (enter Instance B's domain).

On Instance B: Server Settings > Federation > Generate Keys > Accept Instance A's request.


Troubleshooting

Containers won't start

docker logs pulse-db
docker logs pulse-auth
docker logs pulse-kong
docker logs pulse

Common issues:

  • pulse-db: POSTGRES_PASSWORD contains special characters. Use only alphanumeric characters.
  • pulse-auth: Database connection failed. Check db container is healthy: docker compose -f docker-compose-supabase.yml ps
  • pulse-kong: Config error. Verify docker/kong-supabase.yml is valid YAML.

GoTrue auth fails

docker exec pulse curl -s http://kong:8000/auth/v1/health

The supabase_auth_admin password is synced automatically on every container start via docker/db-entrypoint.sh. If you change POSTGRES_PASSWORD in your .env, just restart:

docker compose -f docker-compose-supabase.yml up -d

The DB container will re-sync the password automatically.

WebRTC voice/video not working

  1. Check UDP ports are open: sudo ufw status | grep 40000
  2. Check Docker port mapping: docker port pulse
  3. If behind NAT, forward ports 40000-40020/UDP from your router
  4. Check public IP detection: docker logs pulse 2>&1 | grep -i "public ip"

Database issues

docker exec -it pulse-db psql -U postgres
\dt
SELECT pg_size_pretty(pg_database_size('postgres'));

Reset everything

cd /opt/pulse
docker compose -f docker-compose-supabase.yml down -v
rm -rf data/
docker compose -f docker-compose-supabase.yml up -d

View real-time logs

docker compose -f docker-compose-supabase.yml logs -f
docker compose -f docker-compose-supabase.yml logs -f pulse