7.5 KiB
type, topic, created_at, project
| type | topic | created_at | project |
|---|---|---|---|
| research-report | supabase-postgres-fit | 2026-06-28T23:35:00Z | keepalive |
Research Report: Supabase Postgres Fit
Summary
Yes. This project can apply to Supabase Postgres now through the existing postgresql adapter. Supabase exposes a normal Postgres connection string, and this repo already uses database/sql with github.com/lib/pq, which accepts Postgres URL DSNs and sslmode parameters.
No source-code change required for basic use. Need only add a Supabase service config with the right connection string. For production polish, add a Supabase example, set Postgres max open connections to 1, and consider configurable schema/table to avoid creating public.keepalive.
Research Methodology
- Sources consulted: local repo files + 4 official/external docs.
- Date: 2026-06-28.
- Key terms: Supabase Postgres connection string, Supabase pooler, Supavisor, lib/pq DSN, Supabase RLS public schema.
- Scope: compatibility, config, security, performance. No migration or implementation.
Key Findings
1. Current Project Fit
Current adapter: adapter/postgresql.go
Behavior:
- Accepts
config.url. - Supports adapter names
postgresqlandpostgres. - Opens
sql.Open("postgres", url)viagithub.com/lib/pq. - On connect:
Ping,CREATE TABLE IF NOT EXISTS keepalive, seed key. - On tick: transaction +
UPDATE keepalive SET value = value + 1 WHERE key = $1 RETURNING value.
Supabase fit:
| Requirement | Status |
|---|---|
| Postgres protocol | Supported |
| URL DSN | Supported by lib/pq |
| SSL | Supported via sslmode |
| Cheap write | Supported |
| Reconnect after startup failure | Supported |
| Supabase-specific API | Not needed |
2. Supabase Connection Choice
Supabase docs say Postgres clients use connection strings. Direct connection is best for long-lived servers, but the direct endpoint is IPv6 unless the project has IPv4 add-on. Shared pooler session mode is for persistent app traffic from IPv4-only networks. Transaction mode is for temporary/serverless clients and does not support prepared statements.
This daemon is long-lived. Recommended order:
- Direct connection if runtime supports IPv6 or Supabase IPv4 add-on enabled.
- Shared pooler session mode if runtime is IPv4-only.
- Transaction pooler only if tested, because Supabase warns it does not support prepared statements.
Example direct:
services:
- name: supabase-postgres
adapter: postgresql
interval: 5m
config:
url: "postgresql://postgres:[PASSWORD]@db.[PROJECT-REF].supabase.co:5432/postgres?sslmode=require"
Example shared pooler session mode:
services:
- name: supabase-postgres
adapter: postgresql
interval: 5m
config:
url: "postgresql://postgres.[PROJECT-REF]:[PASSWORD]@aws-[REGION].pooler.supabase.com:5432/postgres?sslmode=require"
3. Security Considerations
Current SQL creates unqualified table keepalive. With a normal Supabase connection, that likely means public.keepalive.
Supabase docs say RLS must be enabled for tables in exposed schemas, and public is exposed by default. Tables created via raw SQL do not automatically get RLS unless configured.
Safer options:
- Use as-is, then enable RLS on
public.keepaliveand add no API policies. - Use a private schema through connection
search_pathif compatible with chosen pooler. - Better code hardening: add optional
schema/tableconfig and qualify identifiers safely.
Runtime role:
- Basic config with
postgresuser works. - Least-privilege role is not ideal today because adapter always runs
CREATE TABLE IF NOT EXISTS; it needs create/init permissions at startup. - Hardening option: add
init: falsemode after manually creating table, then grant onlySELECT,INSERT,UPDATE.
4. Performance Insights
Load is tiny.
At default interval: 1m, each Supabase service does about:
- 1 update/minute
- 1,440 updates/day
- 43,200 updates/month
This is negligible for Postgres. It still keeps one Go sql.DB open. For Supabase small/free tiers, set db.SetMaxOpenConns(1) in the adapter to make connection usage explicit.
5. Free Project Pausing
Supabase docs currently state Free Plan projects with low activity over a 7-day period may be paused, while paid projects are not paused for inactivity.
This daemon performs real database writes, so it is technically aligned with a keepalive use case. Do not treat this as SLA. If the project matters, paid Supabase plan is cleaner than relying on keepalive activity.
Comparative Analysis
| Option | Pros | Cons | Recommendation |
|---|---|---|---|
Existing postgresql adapter |
Works now, simple | Creates keepalive in default schema |
Use now |
New supabase adapter alias |
Friendlier UX | Mostly duplicate of Postgres adapter | Optional only |
| Add Supabase docs/example | Low risk, useful | No behavior change | Do it |
| Add schema/table config | Better security posture | Needs safe identifier handling/tests | Good next hardening |
| Use Supabase Data API | Avoid DB conn details | Wrong tool for daemon counter writes | Do not do |
Implementation Recommendations
Quick Start
- In Supabase dashboard, open project -> Connect.
- Copy direct or session pooler connection string.
- Add
?sslmode=requireunless already present. - Add service config:
services:
- name: supabase-postgres
adapter: postgresql
interval: 5m
counter_key: supabase-postgres
config:
url: "postgresql://postgres.[PROJECT-REF]:[PASSWORD]@aws-[REGION].pooler.supabase.com:5432/postgres?sslmode=require"
- Run:
go run .
- Confirm in Supabase SQL editor:
select * from keepalive;
Optional Code Changes
Minimal docs-only improvement:
- Add Supabase example to
config.example.yml. - Add README note: direct vs session pooler.
Small hardening:
- In
adapter/postgresql.go, aftersql.Open, set:
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(1)
Production hardening:
- Add optional
schemaandtableconfig. - Validate identifiers with strict allow-list.
- Quote identifiers safely.
- Add tests for default table and custom schema/table SQL.
Common Pitfalls
- IPv4-only deploy using direct Supabase host without IPv4 add-on: connection fails. Use shared pooler session mode.
- Transaction pooler with prepared statements: Supabase warns this is unsupported. Prefer direct/session mode for this daemon.
- Raw SQL table in
public: enable RLS or use private schema. - Password special chars in URL: URL-encode password.
- Custom least-privilege role: current adapter still needs init permissions.
Verification
Local tests:
go test ./...
ok github.com/tiennm99/keepalive
ok github.com/tiennm99/keepalive/adapter
Resources & References
- Supabase connect docs: https://supabase.com/docs/guides/database/connecting-to-postgres
- Supabase RLS docs: https://supabase.com/docs/guides/database/postgres/row-level-security
- Supabase free project pausing docs: https://supabase.com/docs/guides/platform/free-project-pausing
- lib/pq docs: https://pkg.go.dev/github.com/lib/pq
Next Steps
- Use existing
postgresqladapter with Supabase session pooler or direct URL. - Add README/config example for Supabase.
- Add
SetMaxOpenConns(1)hardening. - Decide whether
public.keepaliveacceptable; if not, implement schema/table config.
Unresolved Questions
- Target deployment supports IPv6, or IPv4-only?
- Is
public.keepaliveacceptable, or need private schema by default?