Monitoring Self-Hosted Supabase: A Complete Observability Guide

Set up monitoring for self-hosted Supabase with Prometheus, Grafana, and Loki. Track database performance, API latency, and auth events.

Cover Image for Monitoring Self-Hosted Supabase: A Complete Observability Guide

You've deployed your self-hosted Supabase instance, migrated your data, and everything is running smoothly. Then one morning your users start complaining about slow API responses. Your database connections are maxed out. You have no idea what's happening because you can't see what's happening.

This is the reality for many teams running self-hosted Supabase. Unlike Supabase Cloud, which provides built-in dashboards and alerting, self-hosted instances ship with minimal observability out of the box. Setting up proper monitoring isn't optional—it's essential for running production workloads.

This guide walks through building a complete observability stack for self-hosted Supabase, from basic health checks to production-grade dashboards.

Why Self-Hosted Supabase Needs Dedicated Monitoring

Supabase Cloud users get a polished observability experience: query performance reports, connection pooling metrics, auth event logs, and automatic alerting. Self-hosted users get none of this by default.

The official Docker Compose setup includes Vector for log aggregation, but that's about it. You're responsible for:

  • Database metrics: Connection count, cache hit ratios, query performance
  • API latency: Kong gateway response times, error rates
  • Auth events: Failed login attempts, token issuance, suspicious patterns
  • Storage health: Disk usage, S3 bucket access
  • Container health: Memory usage, CPU, restart counts

Without visibility into these metrics, you're flying blind. Problems become apparent only when users complain—by which point they've already impacted your application.

The Observability Stack

For self-hosted Supabase, the recommended stack mirrors what Supabase uses internally:

  • Prometheus: Time-series database for collecting and storing metrics
  • Grafana: Visualization and dashboarding
  • Loki: Log aggregation (pairs with Grafana)
  • Exporters: postgres_exporter, node_exporter, and Kong's built-in Prometheus endpoint

This stack is battle-tested, well-documented, and integrates cleanly with Supabase's architecture.

Setting Up Prometheus

Prometheus scrapes metrics from your services at regular intervals. Here's how to add it to your Supabase deployment.

First, create a prometheus.yml configuration file:

global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'postgres'
    static_configs:
      - targets: ['postgres_exporter:9187']

  - job_name: 'kong'
    static_configs:
      - targets: ['kong:8001']

  - job_name: 'node'
    static_configs:
      - targets: ['node_exporter:9100']

  - job_name: 'gotrue'
    static_configs:
      - targets: ['auth:9100']
    metrics_path: /metrics

Add the Prometheus service to your docker-compose.yml:

prometheus:
  image: prom/prometheus:latest
  container_name: supabase-prometheus
  volumes:
    - ./prometheus.yml:/etc/prometheus/prometheus.yml
    - prometheus_data:/prometheus
  command:
    - '--config.file=/etc/prometheus/prometheus.yml'
    - '--storage.tsdb.path=/prometheus'
    - '--storage.tsdb.retention.time=30d'
  ports:
    - '9090:9090'
  restart: unless-stopped

Adding PostgreSQL Metrics with postgres_exporter

The postgres_exporter service exposes detailed database metrics. Add it to your compose file:

postgres_exporter:
  image: prometheuscommunity/postgres-exporter:latest
  container_name: supabase-postgres-exporter
  environment:
    DATA_SOURCE_NAME: "postgresql://supabase_admin:${POSTGRES_PASSWORD}@db:5432/postgres?sslmode=disable"
  ports:
    - '9187:9187'
  depends_on:
    - db
  restart: unless-stopped

This exposes approximately 200 PostgreSQL metrics, including:

  • pg_stat_activity_count: Active connections by state
  • pg_stat_database_blks_hit: Buffer cache hit ratio
  • pg_stat_database_tup_fetched: Rows fetched per second
  • pg_locks_count: Lock contention metrics
  • pg_replication_lag: Replica lag (if using replicas)

Node Exporter for System Metrics

Understanding host-level resource usage is critical. Node exporter provides CPU, memory, disk, and network metrics:

node_exporter:
  image: prom/node-exporter:latest
  container_name: supabase-node-exporter
  volumes:
    - /proc:/host/proc:ro
    - /sys:/host/sys:ro
    - /:/rootfs:ro
  command:
    - '--path.procfs=/host/proc'
    - '--path.sysfs=/host/sys'
    - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
  ports:
    - '9100:9100'
  restart: unless-stopped

Key metrics to watch:

  • node_cpu_seconds_total: CPU usage by mode
  • node_memory_MemAvailable_bytes: Available RAM
  • node_filesystem_avail_bytes: Disk space remaining
  • node_network_receive_bytes_total: Network throughput

Setting Up Grafana for Visualization

Grafana turns raw metrics into actionable dashboards. Add it to your stack:

grafana:
  image: grafana/grafana:latest
  container_name: supabase-grafana
  environment:
    - GF_SECURITY_ADMIN_USER=admin
    - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
    - GF_USERS_ALLOW_SIGN_UP=false
  volumes:
    - grafana_data:/var/lib/grafana
    - ./grafana/provisioning:/etc/grafana/provisioning
  ports:
    - '3001:3000'
  depends_on:
    - prometheus
  restart: unless-stopped

Supabase provides a community Grafana repository with pre-built dashboards. Clone it and import the dashboards to get started quickly.

Essential Dashboards to Build

PostgreSQL Performance Dashboard

Your database dashboard should display:

  1. Connection Pool Status: Current connections vs. max_connections
  2. Query Throughput: Queries per second by type (SELECT, INSERT, UPDATE, DELETE)
  3. Cache Hit Ratio: Target 99% or higher
  4. Slow Queries: Queries exceeding your threshold
  5. Lock Contention: Blocked queries and deadlocks

A query to calculate cache hit ratio:

pg_stat_database_blks_hit{datname="postgres"} / 
(pg_stat_database_blks_hit{datname="postgres"} + 
 pg_stat_database_blks_read{datname="postgres"})

API Gateway Dashboard

Kong exposes metrics on its admin port (8001). Track:

  • Request Rate: Requests per second by route
  • Latency Percentiles: p50, p95, p99 response times
  • Error Rate: 4xx and 5xx responses
  • Upstream Health: Backend service availability

Auth Events Dashboard

GoTrue (Supabase Auth) logs authentication events. Monitor:

  • Login Attempts: Success vs. failure rates
  • Token Issuance: Access tokens generated per minute
  • Provider Distribution: Sign-ins by OAuth provider
  • Failed Authentication Patterns: Potential brute-force attempts

Log Aggregation with Loki

Metrics tell you what's happening; logs tell you why. Loki integrates with Grafana for unified observability:

loki:
  image: grafana/loki:latest
  container_name: supabase-loki
  ports:
    - '3100:3100'
  volumes:
    - loki_data:/loki
  command: -config.file=/etc/loki/local-config.yaml
  restart: unless-stopped

promtail:
  image: grafana/promtail:latest
  container_name: supabase-promtail
  volumes:
    - /var/log:/var/log:ro
    - /var/lib/docker/containers:/var/lib/docker/containers:ro
    - ./promtail-config.yml:/etc/promtail/config.yml
  command: -config.file=/etc/promtail/config.yml
  depends_on:
    - loki
  restart: unless-stopped

Promtail ships container logs to Loki, where you can query them with LogQL:

{container_name="supabase-auth"} |= "error" | json | level="error"

Setting Up Alerts

Dashboards are useless at 3 AM. Configure alerting for critical conditions.

Example Alert Rules

Create a prometheus/alerts.yml file:

groups:
  - name: supabase
    rules:
      - alert: HighConnectionCount
        expr: pg_stat_activity_count > 80
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "PostgreSQL connection count is high"
          description: "{{ $value }} connections active (threshold: 80)"

      - alert: LowCacheHitRatio
        expr: pg_stat_database_blks_hit / (pg_stat_database_blks_hit + pg_stat_database_blks_read) < 0.95
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "PostgreSQL cache hit ratio below 95%"

      - alert: HighAPILatency
        expr: histogram_quantile(0.95, rate(kong_latency_bucket[5m])) > 1000
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "API p95 latency exceeds 1 second"

      - alert: DiskSpaceLow
        expr: node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"} < 0.1
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Disk space below 10%"

Alerting Channels

Configure Grafana to send alerts via:

  • Slack: Real-time notifications to your ops channel
  • PagerDuty: On-call escalation for critical alerts
  • Email: Fallback notification method

Securing Your Observability Stack

Your monitoring stack has access to sensitive information. Protect it:

  1. Never expose Prometheus or Grafana directly to the internet. Use a reverse proxy with authentication.

  2. Use strong passwords for Grafana admin accounts. Consider integrating with your existing SSO.

  3. Restrict postgres_exporter permissions. Create a dedicated monitoring user with minimal privileges:

CREATE USER monitoring WITH PASSWORD 'secure_password';
GRANT pg_monitor TO monitoring;
  1. Enable TLS between components if running across multiple hosts.

  2. Network isolation: Keep monitoring services on an internal network, accessible only via VPN or bastion host.

For a comprehensive security approach, consider implementing CrowdSec to detect and block attacks based on log analysis.

Performance Tuning Insights from Metrics

Once you have monitoring in place, use it to identify optimization opportunities.

Connection Pool Optimization

If you see connection counts spiking:

  • Enable PgBouncer with transaction pooling
  • Reduce max_connections in PostgreSQL (often set too high)
  • Check for connection leaks in application code

Query Performance

Low cache hit ratios indicate:

  • Insufficient shared_buffers allocation
  • Too many sequential scans (add indexes)
  • Dataset exceeds available RAM

Use Supabase's index_advisor extension to identify missing indexes.

Memory Pressure

If node_exporter shows high memory usage:

  • Tune work_mem for complex queries
  • Reduce maintenance_work_mem if background jobs are rare
  • Consider upgrading your VPS (check our VPS provider comparison)

The Easier Path with Supascale

Setting up and maintaining a monitoring stack takes time—time you'd rather spend building your product. If you're running multiple Supabase instances or want to reduce operational complexity, Supascale handles much of this for you.

Supascale provides:

  • Project health monitoring through a unified dashboard
  • Automated backups with one-click restore
  • Resource usage tracking per project
  • Alert integration for critical events

For teams who want self-hosting benefits without the full DevOps burden, it's worth exploring. Check our pricing for details.

Summary

Monitoring self-hosted Supabase isn't glamorous, but it's non-negotiable for production workloads. The Prometheus/Grafana/Loki stack provides comprehensive observability that matches—or exceeds—what Supabase Cloud offers.

Start with the essentials:

  1. Deploy Prometheus with postgres_exporter and node_exporter
  2. Set up Grafana with PostgreSQL and system dashboards
  3. Configure alerts for connection count, cache ratio, and disk space
  4. Add Loki for log aggregation when you need deeper debugging

The time investment pays off the first time you catch a problem before users notice it.

Further Reading