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 statepg_stat_database_blks_hit: Buffer cache hit ratiopg_stat_database_tup_fetched: Rows fetched per secondpg_locks_count: Lock contention metricspg_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 modenode_memory_MemAvailable_bytes: Available RAMnode_filesystem_avail_bytes: Disk space remainingnode_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:
- Connection Pool Status: Current connections vs. max_connections
- Query Throughput: Queries per second by type (SELECT, INSERT, UPDATE, DELETE)
- Cache Hit Ratio: Target 99% or higher
- Slow Queries: Queries exceeding your threshold
- 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:
Never expose Prometheus or Grafana directly to the internet. Use a reverse proxy with authentication.
Use strong passwords for Grafana admin accounts. Consider integrating with your existing SSO.
Restrict postgres_exporter permissions. Create a dedicated monitoring user with minimal privileges:
CREATE USER monitoring WITH PASSWORD 'secure_password'; GRANT pg_monitor TO monitoring;
Enable TLS between components if running across multiple hosts.
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_connectionsin PostgreSQL (often set too high) - Check for connection leaks in application code
Query Performance
Low cache hit ratios indicate:
- Insufficient
shared_buffersallocation - 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_memfor complex queries - Reduce
maintenance_work_memif 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:
- Deploy Prometheus with postgres_exporter and node_exporter
- Set up Grafana with PostgreSQL and system dashboards
- Configure alerts for connection count, cache ratio, and disk space
- 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.
