Supabase dashboard only — not used in application code
Key tables
Table
Schema
Purpose
dev_r_services
public
Unified element registry — compliance source of truth
dev_r_projects
public
Project registry
dev_r_incidents
public
Infrastructure incident log
dev_r_exporters_queues
public
Queue-exporter scrape config (dynamic, no code redeploy needed)
whatsapp_messages
public
WAHA → n8n → Supabase message store
incidents
public
Business incident tracking
fleet_positions
public
GPS position history (Traccar sync)
pending_transcriptions
public
Audio transcription job queue
pending_pdf_processing
public
PDF processing job queue
p24_issues
public
Issue tracking mirror
All tables have RLS enabled. dev_r_* tables have permissive policies for anon + public access (DevOps tooling pattern). Application tables use role-based or auth-scoped policies.
Config Management
Schema migrations
Schema changes are applied via Supabase MCP (apply_migration) or direct SQL in the Supabase SQL editor. There is no local migration file directory tracked in the repo.
Before any schema change:
Check current structure: SELECT * FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name;
Apply migration via MCP or SQL editor.
Verify RLS is enabled on the new table: ALTER TABLE public.<table> ENABLE ROW LEVEL SECURITY;
Grant access as required (see RLS standard in CLAUDE.md).
Update dev_r_services if the change affects a tracked element.
RLS standard (mandatory)
Every table we own must have RLS enabled. service_role bypasses RLS by design. For anon/grafana_readonly access, add explicit policies. See CLAUDE.md for the full RLS decision table.
grafana_readonly grants
The grafana_readonly role must be granted SELECT explicitly on each table it needs to read:
GRANT SELECT ON public.<table> TO grafana_readonly;
Current grants (as of last audit): pending_transcriptions, pending_pdf_processing, incidents, fleet_positions, whatsapp_messages, p24_issues, dev_r_services, dev_r_projects, dev_r_incidents, dev_r_exporters_queues, plus any tables added to dev_r_exporters_queues.
Backup
Supabase Pro plan includes:
Point-in-time recovery (PITR): 7-day rolling window, granularity 1 second
Daily snapshots: retained 7 days, accessible via Supabase dashboard
No additional backup configuration is required. Backups are fully provider-managed.
Manual pg_dump (emergency snapshot before a risky migration):
# Via session pooler (works from any machine with psql)PGPASSWORD="${SUPABASE_DB_PASSWORD}" pg_dump \ -h aws-1-eu-central-1.pooler.supabase.com \ -p 5432 \ -U postgres.<project_ref> \ -d postgres \ --schema=public \ -f /tmp/supabase-manual-$(date +%F-%H%M).sql# Compress and upload to Wasabi for off-platform copygzip /tmp/supabase-manual-*.sqls3cmd put /tmp/supabase-manual-*.sql.gz s3://p24-infra/supabase-manual/ \ --host=s3.eu-central-1.wasabisys.com
Replace <project_ref> with mwkqmgadqnkkihjdeqsi.
Restore
Scenario 1: Row-level data accidentally deleted
Use Supabase PITR to restore a copy of the database to a specific point in time:
Supabase dashboard → Project → Database → Backups → Point in Time.
Select a timestamp before the deletion.
Restore to a new project (do not overwrite production — use the restored project to extract the rows, then insert back into production).
Restoration process takes 15–60 minutes depending on database size.
After restore completes, update all consumers with the new project credentials if the project ref has changed (it will change if you restore to a new project).
Estimated RTO: < 2 hours for full restore. RPO: up to 1 second with PITR, or up to 24h with daily snapshots.
Container on vps-i1 (port :9201) connects to Supabase every 60s via session pooler and scrapes extensions.pg_stat_statements for top-200 slowest queries. If the exporter is up and returning metrics, the database is reachable.
# Check exporter health on vps-i1curl -s http://localhost:9201/metrics | grep pg_stats_query_duration_ms | head -5
Prometheus alert PgStatsExporterDown fires if the exporter is unreachable for > 5 minutes.
queue-exporter
Container on vps-i1 (port :9200) polls queue depths from tables listed in dev_r_exporters_queues. If polling fails, metrics go stale. TranscriptionQueueCritical and similar alerts will fire on queue depth thresholds.
Direct connectivity check
# From any machine with psql + network access to SupabasePGPASSWORD="${SUPABASE_DB_PASSWORD}" psql \ -h aws-1-eu-central-1.pooler.supabase.com \ -p 5432 \ -U postgres.mwkqmgadqnkkihjdeqsi \ -d postgres \ -c "SELECT version();"
Grafana datasource check
In Grafana: Settings → Data Sources → Supabase PostgreSQL → Save & test. Should return “Database Connection OK”.
Supabase dashboard
Project health and real-time metrics at https://supabase.com/dashboard/project/mwkqmgadqnkkihjdeqsi. Check for:
Database CPU and connections under Database → Reports
Storage size under Database → Database Size
API error rates under Logs → API
Password / Credential Rotation
Credential
What it is
Tracked entry
Rotation frequency
Last rotated
Next due
SUPABASE_SERVICE_KEY
service_role JWT
dev_r_services — supabase
90d
2026-05-08
2026-08-06
SUPABASE_ACCESS_TOKEN
Management API token
dev_r_services — supabase
90d
2026-05-08
2026-08-06
SUPABASE_GRAFANA_PASSWORD
grafana_readonly DB role password
dev_r_services — supabase
90d
2026-05-08
2026-08-06
SUPABASE_DB_PASSWORD
Direct DB access password (postgres superuser)
dev_r_services — supabase
90d
2026-06-13
2026-09-13
Rotate SUPABASE_SERVICE_KEY (service_role JWT)
The service_role key is a JWT signed with the project’s JWT secret. Resetting it invalidates all existing tokens.
Or update in .env if the datasource YAML reads from an env variable.
Update GH Secret and .env.local.
Verify Grafana datasource: Settings → Data Sources → Supabase → Save & test.
Log rotation in docs/secrets-rotation-log.md.
Rotate SUPABASE_DB_PASSWORD (postgres direct access)
Used by manual pg_dump, emergency psql access, and the db-maintenance.py GitHub Actions job.
Scope:postgres superuser only. Grafana uses the separate grafana_readonly role (SUPABASE_GRAFANA_PASSWORD) — rotating SUPABASE_DB_PASSWORD does NOT require updating Grafana or vps-i1 monitoring/.env.
Correct API verb:PATCH — PUT and POST return 404 on this endpoint.
# 1. Read access token from .env.local$token = (gc d:\code_2026\p24-infra\.env.local | sls "^SUPABASE_ACCESS_TOKEN=(.+)").Matches[0].Groups[1].Value$ref = "mwkqmgadqnkkihjdeqsi"# 2. Rotate via management APIInvoke-RestMethod "https://api.supabase.com/v1/projects/$ref/database/password" ` -Method PATCH ` -Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } ` -Body (ConvertTo-Json @{ password = "<new_password>" })# 3. Update GH Secretgh secret set SUPABASE_DB_PASSWORD -b "<new>" -R radieu/p24-infra# 4. Update .env.local on local workstation# (vps-i1 monitoring/.env does NOT need updating — grafana_readonly is a separate role)
SELECT * FROM dev_r_exporters_queues; — set active=true
pg-stats-exporter connection refused
Wrong SUPABASE_DB_HOST
Use session pooler aws-1-eu-central-1.pooler.supabase.com, not db.*.supabase.co
Grafana datasource “connection refused”
SUPABASE_GRAFANA_PASSWORD wrong or DB host changed
Verify credentials in .env; restart grafana
RLS error on exporter query
grafana_readonly lacks SELECT on referenced table
GRANT SELECT ON public.<table> TO grafana_readonly;
service_role operations fail
JWT expired / key rotated
Redeploy with new SUPABASE_SERVICE_KEY
PITR not available in dashboard
Project on Free plan
Pro plan required for PITR — confirm billing in Supabase dashboard
Slow query alerts firing
New unindexed query pattern
Check pg-stats-exporter metrics; add index or optimize query
Schema Maintenance Log
2026-05-15 — Drop archive and backup tables (#176)
Applied migration drop_archive_backup_tables to remove 5 dead-weight tables from the public schema.
Motivation: schema introspection (Supabase Dashboard) was slow due to 151+ tables. These 5 tables were confirmed as dead weight with no active application code references.
Table dropped
Reason
p24_l_cars_deleted
Archive snapshot of deleted cars — no longer queried
p24_l_cars_duplicate
Duplicate detection scratch table — unused
p24_l_cars_fleet
Old schema version of p24_l_cars — superseded
profiles_backup
Manual backup snapshot from migration 20260317224417
user_roles_backup
Manual backup snapshot from migration 20260317224417
Pre-drop verification: grepped et-operational-platform codebase — references found only in auto-generated type files (types.ts, database.types.ts) and historical migration SQL. No active service, hook, page, or component queried these tables. No n8n workflow references found in p24-infra.