Capacity Planning for Self-Hosted Supabase: A Complete Guide

Learn how to plan resources, identify bottlenecks, and scale your self-hosted Supabase instance effectively with practical monitoring tips.

Cover Image for Capacity Planning for Self-Hosted Supabase: A Complete Guide

You've deployed self-hosted Supabase and it's working great. Your application is growing, users are signing up, and data is flowing. But now you're asking yourself: when do I need to upgrade my server? How do I know if I'm running out of resources before my users notice?

Capacity planning for self-hosted Supabase isn't about guessing when to throw more hardware at the problem. It's about understanding your workload, monitoring the right metrics, and making informed decisions that balance cost and performance. Let's break down exactly how to approach this.

Understanding Your Supabase Resource Footprint

Self-hosted Supabase runs multiple services in Docker containers: PostgreSQL, PostgREST, GoTrue (Auth), Kong (API Gateway), Realtime, Storage, and Studio. Each of these services consumes CPU, memory, and disk I/O. Understanding how they interact is the first step in effective capacity planning.

Base Resource Consumption

Even with minimal load, a self-hosted Supabase instance consumes significant resources. On a server with 4GB RAM, you'll typically see 40-60% memory utilization at idle. This isn't a bug—it's how PostgreSQL and the surrounding services operate. PostgreSQL allocates memory for connection handling, shared buffers, and background processes.

The minimum recommended configuration for production is:

  • Development/Staging: 4 vCPU, 8GB RAM
  • Production: 4 vCPU, 16GB RAM with room to scale
  • Storage: Fast SSD (NVMe preferred), 50GB+ depending on your data

If you're running on the minimum specs, you have very little headroom. That's fine for testing, but you'll need to plan for growth quickly.

The Memory-Performance Connection

PostgreSQL performance is directly tied to how much data it can keep in memory. The shared_buffers setting (typically 25% of total RAM) determines your database cache size. When queries need data that isn't cached, PostgreSQL reads from disk—which is orders of magnitude slower.

Here's the key insight: if your swap usage exceeds 70%, your database is struggling. Swap means your system is using disk as memory overflow, which destroys performance. Monitor this metric religiously.

Identifying Your Bottlenecks

Before upgrading hardware, you need to know what's actually limiting your performance. The three primary bottlenecks for self-hosted Supabase are:

1. CPU Saturation

High CPU usage manifests as slow API responses and query timeouts. Check for:

  • Sustained CPU usage above 80%
  • Query execution times increasing
  • API latency spikes during peak hours

Common culprits include unindexed queries, full-table scans, and complex joins. Before adding CPU cores, run EXPLAIN ANALYZE on your slowest queries. Often, a single index eliminates the problem entirely.

2. Memory Pressure

Memory issues show up before they become critical—if you're watching. Look for:

  • Swap usage above 70%
  • Cache hit rates below 99%
  • Increasing query times for frequently-accessed data

To check your cache hit rate, run:

SELECT 
  sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) AS cache_hit_ratio
FROM pg_statio_user_tables;

A ratio below 0.99 suggests you need more RAM for your workload.

3. Disk I/O Constraints

Disk I/O bottlenecks are sneaky because they often masquerade as CPU or memory issues. Signs include:

  • High I/O wait times in system monitoring
  • Write operations queuing up
  • Backup processes causing performance degradation

If you're not on NVMe storage, this is often the first upgrade that makes sense. The difference between spinning disks and NVMe can be 100x for random I/O operations.

Setting Up Effective Monitoring

You can't plan capacity without data. While monitoring self-hosted Supabase covers observability in depth, here's what matters specifically for capacity planning.

Key Metrics to Track

PostgreSQL-specific:

  • Active connections vs. max_connections
  • Cache hit ratio (target: >99%)
  • Transaction throughput (TPS)
  • Query execution time distribution
  • Disk space usage and growth rate

System-level:

  • CPU utilization (per core and aggregate)
  • Memory usage and swap consumption
  • Disk I/O throughput and latency
  • Network bandwidth utilization

Practical Monitoring Setup

Supabase provides a Grafana dashboard repository that displays real-time metrics. Deploy it alongside your Supabase instance:

git clone https://github.com/supabase/supabase-grafana
cd supabase-grafana
docker-compose up -d

Set up alerts for:

  • CPU sustained above 80% for 5 minutes
  • Memory usage above 85%
  • Swap usage above 50%
  • Disk space below 20% free
  • Cache hit ratio below 95%

Planning for Growth

The best capacity planning is proactive, not reactive. Here's how to project your needs.

Analyzing Growth Patterns

Track these metrics weekly:

  • Database size growth rate
  • Connection count trends
  • Query volume increase
  • Storage consumption

If your database is growing 10% month-over-month, you can project when you'll need more disk space. If connection counts are climbing, you'll need to consider connection pooling before hitting limits.

The 70% Rule

A practical rule: plan to upgrade when any critical resource consistently exceeds 70% utilization. This gives you headroom for traffic spikes without risking outages. Hitting 90% utilization during normal operations means your next traffic spike could cause problems.

Load Testing Your Headroom

Before production traffic tells you your limits, find them yourself. Tools like pgbench and k6 can simulate load:

# Test PostgreSQL directly
pgbench -c 50 -j 4 -T 60 your_database

# Test API throughput
k6 run --vus 100 --duration 5m load-test.js

Record the point where latency spikes or errors appear. That's your current ceiling.

Scaling Strategies

When it's time to scale, you have options depending on your bottleneck.

Vertical Scaling (Bigger Server)

The simplest approach: more CPU, more RAM, faster disks. This works well until you hit the limits of single-server architecture.

When to choose vertical scaling:

  • You're below 16 vCPU / 64GB RAM
  • Your bottleneck is clearly CPU or memory
  • You want minimal architectural complexity

PostgreSQL configuration adjustments after upgrading:

  • Increase shared_buffers to 25% of new RAM
  • Increase effective_cache_size to 75% of new RAM
  • Adjust work_mem based on max_connections

Connection Pooling

If you're hitting connection limits, pooling is more effective than adding connections. Supabase uses Supavisor, which can scale to handle massive connection counts.

For self-hosted deployments, ensure your pooler is properly configured:

# In your docker-compose.yml
supavisor:
  environment:
    - POOL_SIZE=100
    - POOL_MODE=transaction

Transaction mode is recommended for most API-driven workloads.

Read Replicas

For read-heavy workloads, adding PostgreSQL read replicas can dramatically increase capacity. Route read queries to replicas while writes go to the primary. This requires application-level changes but can multiply your read throughput.

When Self-Hosting Scaling Gets Complex

There's a point where self-hosted scaling becomes a full-time job. High-availability configurations and geographic redundancy add operational complexity.

This is where management tools become valuable. Supascale handles much of the operational burden: automated backups, monitoring integration, and simplified deployments. When you'd rather focus on your application than PostgreSQL tuning, a management layer pays for itself quickly.

Check our pricing for a one-time cost that covers unlimited projects—no recurring fees that scale with your infrastructure.

Practical Decision Framework

Here's a simplified decision tree for capacity planning:

Is performance acceptable?

  • Yes → Continue monitoring, no action needed
  • No → Identify the bottleneck

Is the bottleneck software or hardware?

  • Software (bad queries, missing indexes) → Optimize first
  • Hardware → Proceed to scaling

Which resource is constrained?

  • CPU → Add cores or optimize queries
  • Memory → Add RAM, increase shared_buffers
  • Disk I/O → Upgrade to faster storage (NVMe)
  • Connections → Implement connection pooling

Is vertical scaling viable?

  • Yes → Upgrade server, adjust PostgreSQL config
  • No → Consider horizontal scaling (replicas, sharding)

Key Takeaways

Capacity planning for self-hosted Supabase comes down to:

  1. Establish baselines: Know your normal resource utilization before problems occur
  2. Monitor the right metrics: CPU, memory, swap, cache hit ratio, disk I/O
  3. Identify bottlenecks first: Don't throw hardware at software problems
  4. Plan at 70% utilization: Leave headroom for spikes
  5. Test your limits: Load testing reveals capacity before users do
  6. Scale incrementally: Vertical scaling handles most growth; horizontal scaling adds complexity

Self-hosting gives you control over your infrastructure costs and performance tuning. But that control comes with responsibility. Set up proper monitoring, understand your growth patterns, and make informed decisions about when to scale.

The alternative—discovering your capacity limits when your application goes down—is never worth the savings from skipping proper planning.


Further Reading