Docker Compose for Supabase: Production Best Practices

Master production-ready Supabase deployments with Docker Compose. Learn security hardening, resource optimization, and operational best practices.

Cover Image for Docker Compose for Supabase: Production Best Practices

Running Supabase locally with docker compose up is straightforward. Running it in production? That's where things get interesting. The default configuration works fine for development, but deploying to production requires careful attention to security, performance, and operational reliability.

This guide covers everything you need to know about hardening your Supabase Docker Compose deployment for production. Whether you're deploying on a VPS provider or your own infrastructure, these best practices will help you avoid common pitfalls.

The Default Configuration Isn't Production-Ready

Supabase's official Docker Compose file is designed for getting started quickly, not for running in production. The official documentation explicitly warns: "While we provided example placeholder passwords and keys in the .env.example file, you should NEVER start your self-hosted Supabase using these defaults."

Here's what needs attention:

  • Placeholder secrets: Default passwords and API keys are publicly known
  • No SSL termination: The default setup exposes unencrypted HTTP
  • All services enabled: You're running components you might not need
  • No backup configuration: Data loss is one misconfiguration away
  • Missing monitoring: You won't know when things break

Let's fix each of these.

Security Hardening

Replace Every Default Secret

The .env file contains secrets for JWT signing, database access, and service communication. Every single one must be regenerated before production deployment.

Generate cryptographically secure secrets:

# Generate a 32-character random string
openssl rand -base64 32

# For JWT secrets, use a longer key
openssl rand -base64 64

Critical secrets to replace in your .env:

# NEVER use defaults for these
POSTGRES_PASSWORD=<generate-unique-password>
JWT_SECRET=<generate-64-char-minimum>
ANON_KEY=<generate-new-jwt>
SERVICE_ROLE_KEY=<generate-new-jwt>
DASHBOARD_PASSWORD=<strong-password>

Use a secrets manager like HashiCorp Vault, AWS Secrets Manager, or even a password manager to generate and store these values. Never commit them to version control.

Generate New JWT Keys

The ANON_KEY and SERVICE_ROLE_KEY are JWTs that must be regenerated with your new JWT_SECRET. You can use the Supabase JWT generator or create them manually:

// Using jsonwebtoken in Node.js
const jwt = require('jsonwebtoken');

const JWT_SECRET = 'your-new-jwt-secret';

const anonKey = jwt.sign(
  {
    role: 'anon',
    iss: 'supabase',
    iat: Math.floor(Date.now() / 1000),
    exp: Math.floor(Date.now() / 1000) + (10 * 365 * 24 * 60 * 60) // 10 years
  },
  JWT_SECRET
);

const serviceKey = jwt.sign(
  {
    role: 'service_role',
    iss: 'supabase',
    iat: Math.floor(Date.now() / 1000),
    exp: Math.floor(Date.now() / 1000) + (10 * 365 * 24 * 60 * 60)
  },
  JWT_SECRET
);

Secure the Dashboard

The Supabase Studio dashboard should never be publicly accessible without authentication. Options include:

  1. Set a strong dashboard password in your environment variables
  2. Use a reverse proxy with additional authentication (HTTP Basic Auth, OAuth)
  3. Restrict access by IP using firewall rules or your reverse proxy
  4. Disable it entirely if you don't need it in production

For most production deployments, disabling the dashboard and using migrations for schema changes is the safest approach.

SSL/TLS Configuration

Production deployments must use HTTPS. You have two options:

Option 1: Reverse Proxy (Recommended)

Place a reverse proxy like Nginx, Caddy, or Traefik in front of Kong. This handles SSL termination, certificate renewal, and can add additional security features.

Caddy is particularly attractive because it handles Let's Encrypt certificates automatically:

# Caddyfile
api.yourdomain.com {
    reverse_proxy kong:8000
}

studio.yourdomain.com {
    reverse_proxy studio:3000
    basicauth * {
        admin $2a$14$hashedpasswordhere
    }
}

Option 2: Configure Kong Directly

Kong can handle SSL on port 8443. Mount your certificates and update the configuration:

# docker-compose.yml snippet
kong:
  volumes:
    - ./certs/fullchain.pem:/etc/ssl/certs/fullchain.pem:ro
    - ./certs/privkey.pem:/etc/ssl/private/privkey.pem:ro
  ports:
    - "8443:8443"

The reverse proxy approach is generally preferred because it's easier to manage certificate renewals and provides an additional security layer.

Resource Optimization

Disable Unused Services

Supabase includes many services you might not need. Each consumes memory and CPU. The default stack includes:

  • Realtime: WebSocket subscriptions for live updates
  • Storage: S3-compatible file storage with imgproxy
  • Edge Functions: Deno runtime for serverless functions
  • Analytics/Logflare: Usage analytics and log aggregation
  • Vector: Log collection and forwarding

If you're not using a service, remove it from docker-compose.yml along with its dependencies. A minimal API-focused deployment might only need:

  • PostgreSQL (database)
  • PostgREST (REST API)
  • GoTrue (authentication)
  • Kong (API gateway)

This can reduce memory usage from 4GB+ to under 1GB.

Configure Resource Limits

Docker Compose supports resource constraints. Set them based on your server capacity and expected load:

services:
  db:
    deploy:
      resources:
        limits:
          memory: 2G
          cpus: '2'
        reservations:
          memory: 1G
          cpus: '1'

PostgreSQL typically needs the most resources. A good starting point for a small to medium workload:

ServiceMemory LimitCPU Limit
PostgreSQL2-4 GB2 cores
PostgREST512 MB0.5 cores
GoTrue256 MB0.25 cores
Kong512 MB0.5 cores
Realtime512 MB0.5 cores
Storage512 MB0.5 cores

Adjust based on your actual usage patterns after monitoring in production.

Tune PostgreSQL

The default PostgreSQL configuration is conservative. For production, tune these settings in a custom postgresql.conf or via environment variables:

# Based on 4GB RAM server
POSTGRES_SHARED_BUFFERS=1GB
POSTGRES_EFFECTIVE_CACHE_SIZE=3GB
POSTGRES_MAINTENANCE_WORK_MEM=256MB
POSTGRES_WORK_MEM=16MB

Use PGTune to generate settings based on your server specs and workload type.

Backup Strategy

This is where most self-hosted deployments fail. A production deployment without automated backups is a disaster waiting to happen.

Database Backups

The minimum viable backup strategy uses pg_dump:

#!/bin/bash
# backup.sh
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR=/backups

docker exec supabase-db pg_dump -U postgres \
  --format=custom \
  --compress=9 \
  postgres > "$BACKUP_DIR/supabase_$TIMESTAMP.dump"

# Retain last 7 days
find $BACKUP_DIR -name "*.dump" -mtime +7 -delete

Run this via cron:

0 2 * * * /opt/supabase/backup.sh

For serious production deployments, consider WAL-G for point-in-time recovery with continuous archiving.

Storage Backups

Database backups don't include files uploaded to Supabase Storage. If you're using local storage, back up the volumes:

docker run --rm \
  -v supabase_storage:/data \
  -v /backups:/backup \
  alpine tar czf /backup/storage_$(date +%Y%m%d).tar.gz /data

Better yet, configure Storage to use S3-compatible object storage with versioning enabled.

Don't Forget: Test Your Restores

A backup you haven't tested restoring is not a backup. Schedule regular restore tests to a separate environment.

For comprehensive backup strategies including one-click restore, check out our backup and restore guide.

Monitoring and Health Checks

Add Health Checks to Compose

Docker Compose supports health checks that automatically restart unhealthy containers:

services:
  db:
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

  rest:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/"]
      interval: 30s
      timeout: 10s
      retries: 3

External Monitoring

Health checks handle container-level issues, but you need external monitoring for:

  • Uptime monitoring: Is your API responding?
  • Performance metrics: Response times, error rates
  • Resource usage: CPU, memory, disk space
  • Certificate expiry: Don't let SSL certificates expire

Tools like Uptime Kuma, Prometheus + Grafana, or managed services like Better Uptime work well.

Log Management

By default, Docker logs can grow unbounded and fill your disk. Configure log rotation:

# docker-compose.yml
services:
  db:
    logging:
      driver: "json-file"
      options:
        max-size: "50m"
        max-file: "3"

For centralized logging, consider forwarding to Loki, Elasticsearch, or a managed logging service.

Deployment Workflow

Version Control Everything

Your entire Supabase configuration should be in version control:

supabase-production/
├── docker-compose.yml
├── docker-compose.override.yml  # Local overrides (gitignored)
├── .env.example                 # Template (never real secrets)
├── kong/
│   └── kong.yml
├── postgres/
│   └── postgresql.conf
└── migrations/
    └── *.sql

The actual .env with secrets should never be committed.

Zero-Downtime Updates

Updating Supabase requires pulling new images and recreating containers. To minimize downtime:

# Pull new images without stopping services
docker compose pull

# Recreate containers one at a time
docker compose up -d --no-deps db
docker compose up -d --no-deps rest
docker compose up -d --no-deps auth
# Continue for each service

For true zero-downtime deployments, you'd need a blue-green or rolling deployment strategy with a load balancer, which is beyond the scope of a single Docker Compose file.

Database Migrations

Schema changes should be managed through migrations, not the dashboard. Supabase CLI helps with this:

# Generate migration from local changes
supabase db diff -f my_migration

# Apply to production
supabase db push --db-url postgres://user:pass@host:5432/postgres

Test migrations against a backup of production data before applying them.

Common Pitfalls to Avoid

1. Running as root: Create a dedicated user for Docker and the Supabase services.

2. Exposing ports directly: Only Kong (your API gateway) should be accessible from the internet. Other services communicate internally.

3. Ignoring disk space: PostgreSQL, logs, and storage can fill your disk. Monitor usage and set up alerts.

4. No restart policy: Containers should restart automatically after server reboots:

services:
  db:
    restart: unless-stopped

5. Skipping RLS: Row Level Security is disabled by default on new tables. This is the most common security mistake in Supabase deployments—83% of exposed databases involve RLS misconfigurations.

When Docker Compose Isn't Enough

Docker Compose works well for single-server deployments. You'll outgrow it when:

  • Traffic exceeds one server's capacity: You need load balancing and horizontal scaling
  • You need high availability: Single server = single point of failure
  • Management overhead becomes painful: Updating 10 Docker Compose deployments manually doesn't scale

At that point, consider Kubernetes, Docker Swarm, or a management platform.

Simplifying Production Deployments

Managing production Docker Compose deployments involves significant operational overhead: generating secrets, configuring SSL, setting up backups, monitoring health. It's achievable, but time-consuming.

Supascale handles the operational complexity of self-hosted Supabase. Instead of manually configuring Docker Compose files and managing infrastructure, you get:

  • Automated deployments with proper security defaults
  • Built-in backups to S3-compatible storage with one-click restore
  • Custom domains with automatic SSL certificate management
  • OAuth configuration through a UI instead of environment variables
  • Selective services: Only run what you need

For a one-time $39.99 purchase, you can deploy unlimited projects without the operational burden. If you're spending hours on Docker Compose configuration, check if Supascale makes sense for your use case.

Summary

Production-ready Supabase with Docker Compose requires:

  1. Replace all default secrets with cryptographically secure values
  2. Configure SSL via reverse proxy or Kong
  3. Disable unused services to reduce resource consumption
  4. Set resource limits to prevent runaway containers
  5. Implement automated backups and test restores regularly
  6. Add health checks and monitoring for visibility
  7. Enable RLS on every table containing user data

The effort is significant, but self-hosting gives you full control, predictable costs, and data sovereignty. For teams managing multiple projects or wanting to reduce operational overhead, tools like Supascale can automate much of this configuration.

Further Reading