Skip to content

Supabase Connection Playbook

How to connect to Supabase Postgres from any container, app, or client. Covers session pool, transaction pool, and direct internal access.

Quick reference

# Session pool (recommended for apps with ORM)
postgresql://postgres.homelab:${DB_PASSWORD}@supabase.tail1137c.ts.net:5432/postgres

# Transaction pool (serverless / high concurrency)
postgresql://postgres.homelab:${DB_PASSWORD}@supabase.tail1137c.ts.net:6543/postgres

# HTTPS API (Studio, REST, Auth, Storage — for frontend / SDK)
https://supabase.tail1137c.ts.net

Credentials: SUPABASE_POSTGRES_PASSWORD Komodo Variable.

Connection endpoints

Port URL / Host:Port Protocol Use case
443 https://supabase.tail1137c.ts.net HTTPS Studio UI, REST API, Auth, Storage, Realtime
5432 supabase.tail1137c.ts.net:5432 PostgreSQL Session pool — long-lived connections, prepared statements
6543 supabase.tail1137c.ts.net:6543 PostgreSQL Transaction pool — high concurrency, short queries

Via Cloudflare Tunnel (public internet)

Endpoint URL Use case
HTTPS API https://supabase.minhluc.info Same as Tailscale, but via CF edge (slower, CF Access SSO for Studio)

Via Tailscale IP (fallback if DNS not resolving)

Port Host:Port Use case
5432 supabase.tail1137c.ts.net:5432 Session pool
6543 supabase.tail1137c.ts.net:6543 Transaction pool

Internal Docker network (supabase-internal)

Endpoint Host:Port Use case
Direct DB db:5432 Supabase internal services only
Pooler supabase-pooler:5432 Internal services with pooling

Connection string format

postgresql://postgres.{TENANT_ID}:{PASSWORD}@{HOST}:{PORT}/{DATABASE}
  • {TENANT_ID} = homelab (Komodo Variable POOLER_TENANT_ID)
  • {PASSWORD} = value of SUPABASE_POSTGRES_PASSWORD Komodo Variable
  • {HOST} = supabase.tail1137c.ts.net (Tailscale) or supabase.tail1137c.ts.net (LAN)
  • {PORT} = 5432 (session) or 6543 (transaction)
  • {DATABASE} = postgres (default)

Session pool (port 5432)

postgresql://postgres.homelab:${DB_PASSWORD}@supabase.tail1137c.ts.net:5432/postgres
  • ✅ Prepared statements work
  • ✅ Session state (SET, temp tables, LISTEN/NOTIFY) preserved
  • Pool size: 20 connections (configurable via POOLER_DEFAULT_POOL_SIZE)

Transaction pool (port 6543)

postgresql://postgres.homelab:${DB_PASSWORD}@supabase.tail1137c.ts.net:6543/postgres
  • ❌ Prepared statements NOT supported
  • ❌ Session state lost between queries
  • Max client connections: 100

Schema access

To use a specific schema (e.g., njav), append ?options=-c search_path=SCHEMA:

postgresql://postgres.homelab:${DB_PASSWORD}@supabase.tail1137c.ts.net:5432/postgres?options=-c%20search_path%3Dnjav

Or in SQL after connecting:

SET search_path TO njav;

Or create a dedicated role with default schema:

CREATE ROLE njav_user LOGIN PASSWORD 'Set via Komodo Variable NJAV_DB_PASSWORD';
GRANT USAGE ON SCHEMA njav TO njav_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA njav TO njav_user;
ALTER ROLE njav_user SET search_path = njav;

Connecting from a new stack

Container stays on its own network, connects via unraid LAN IP:

# stacks/{category}/{service}/compose.yaml
services:
  myapp:
    image: myapp:latest
    environment:
      # Session pool via LAN
      DATABASE_URL: postgresql://postgres.homelab:${DB_PASSWORD}@supabase.tail1137c.ts.net:5432/postgres
    extra_hosts:
      - host.docker.internal:host-gateway
# komodo/stacks/{category}/{service}.toml
environment = """
DB_PASSWORD=[[SUPABASE_POSTGRES_PASSWORD]]
"""

Via Tailscale IP (for containers without MagicDNS)

environment:
  DATABASE_URL: postgresql://postgres.homelab:${DB_PASSWORD}@supabase.tail1137c.ts.net:5432/postgres

(Requires Tailscale running on the container host.)

Option C: Same Docker network (supabase-internal — direct DB, no pooler)

services:
  myapp:
    networks:
      - supabase-internal
networks:
  supabase-internal:
    external: true

⚠️ This bypasses the pooler — use ONLY for admin tasks (migrations, schema changes).

Code examples

Python (psycopg / SQLAlchemy)

import psycopg
conn = psycopg.connect(
    "postgresql://postgres.homelab:${DB_PASSWORD}@supabase.tail1137c.ts.net:5432/postgres"
)

from sqlalchemy import create_engine
engine = create_engine(
    "postgresql://postgres.homelab:${DB_PASSWORD}@supabase.tail1137c.ts.net:5432/postgres",
    pool_size=5,
)

Node.js (pg / Prisma)

// pg
const { Pool } = require('pg')
const pool = new Pool({
  connectionString: 'postgresql://postgres.homelab:${DB_PASSWORD}@supabase.tail1137c.ts.net:5432/postgres'
})

// Prisma schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")  // postgresql://postgres.homelab:${DB_PASSWORD}@...
}

Supabase JS SDK (REST API, not Postgres protocol)

import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  "https://supabase.tail1137c.ts.net",
  "YOUR_ANON_KEY", // SUPABASE_ANON_KEY
);

// Query njav schema
const { data } = await supabase.schema("njav").from("table").select("*");

psql (CLI)

# Session pool
PGPASSWORD='(from SUPABASE_POSTGRES_PASSWORD Komodo Variable)' psql "host=supabase.tail1137c.ts.net port=5432 user=postgres.homelab dbname=postgres"

# Transaction pool
PGPASSWORD='(from SUPABASE_POSTGRES_PASSWORD Komodo Variable)' psql "host=supabase.tail1137c.ts.net port=6543 user=postgres.homelab dbname=postgres"

# With njav schema
PGPASSWORD='(from SUPABASE_POSTGRES_PASSWORD Komodo Variable)' psql "host=supabase.tail1137c.ts.net port=5432 user=postgres.homelab dbname=postgres" \
  -c "SET search_path TO njav; SELECT * FROM your_table LIMIT 5;"

When to use which

graph TD
    ROOT["Need DB access?"]
    ROOT --> ORM["App with ORM?
(Prisma, SQLAlchemy, Django, Rails)"] ORM -->|Yes| SP["Session pool :5432 ✅"] ROOT --> SERVERLESS["Serverless / edge / short-lived?"] SERVERLESS -->|Yes| TP["Transaction pool :6543 ✅"] ROOT --> ADMIN["Admin task?
(migrations, schema)"] ADMIN -->|Yes| DD["Direct DB internal db:5432 ✅"] ROOT --> FE["Frontend (browser)?"] FE -->|Yes| SDK["Supabase JS SDK via HTTPS ✅"] ROOT --> CLI["Quick CLI query?"] CLI -->|Yes| PS["psql session pool :5432 ✅"]

For other stacks: how to connect (step-by-step)

  1. Get the password: Komodo Variable SUPABASE_POSTGRES_PASSWORD

  2. Add to your stack's TOML:

environment = """
SUPABASE_DB_URL=postgresql://postgres.homelab:[[SUPABASE_POSTGRES_PASSWORD]]@supabase.tail1137c.ts.net:5432/postgres
"""
  1. Reference in compose.yaml:
environment:
  - DATABASE_URL=${SUPABASE_DB_URL}
  1. Create your schema/table:
PGPASSWORD='(from SUPABASE_POSTGRES_PASSWORD Komodo Variable)' psql "host=supabase.tail1137c.ts.net port=5432 user=postgres.homelab dbname=postgres" \
  -c "CREATE SCHEMA IF NOT EXISTS myapp;"
  1. Grant permissions (recommended: dedicated role per app):
CREATE ROLE myapp_user LOGIN PASSWORD 'Set via Komodo Variable MYAPP_DB_PASSWORD';
GRANT USAGE ON SCHEMA myapp TO myapp_user;
GRANT ALL ON ALL TABLES IN SCHEMA myapp TO myapp_user;
  1. Update connection string to use the new role:
    postgresql://myapp_user.homelab:${MYAPP_DB_PASSWORD}@supabase.tail1137c.ts.net:5432/postgres?options=-c%20search_path%3Dmyapp
    

Secrets reference

Komodo Variable Used for
SUPABASE_POSTGRES_PASSWORD DB password (used by pooler + direct — but postgres role is NOT superuser, see below)
SUPABASE_ANON_KEY Public API key (Supabase JS SDK)
SUPABASE_SERVICE_ROLE_KEY Admin API key (bypasses RLS)
SUPABASE_S3_PROTOCOL_ACCESS_KEY_ID S3 protocol access (Storage)
SUPABASE_S3_PROTOCOL_ACCESS_KEY_SECRET S3 protocol secret (Storage)

Admin DB Access (writing to Supabase DB)

The postgres role connected via pooler is NOT a superuser (rolsuper=f). It lacks UPDATE/DELETE privileges on tables owned by other schemas.

supabase_admin is the real superuser (rolsuper=t). Use it for one-off admin SQL (schema changes, system_setting updates, role management):

# Pattern: pipe SQL via SSH to supabase_admin inside the DB container
echo "UPDATE memos.system_setting SET value = '...' WHERE name = 'STORAGE';" \
  | ssh root@100.68.251.84 "docker exec -i supabase-db psql -U supabase_admin -d postgres"

Do NOT use:

  • psql -U postgres.homelab ... (pooler) for writes — permission denied for table
  • km-exec supabase-db psql -U postgres -c "SQL WITH SPACES" — km-exec splits args at spaces, breaking the SQL. Use the SSH+pipe pattern above.

Troubleshooting

Connection refused on Tailscale ports

Sidecar may have lost serve config after deploy. Re-apply:

ssh root@100.68.251.84 'docker exec tailscale-supabase tailscale serve reset'
ssh root@100.68.251.84 'docker exec tailscale-supabase tailscale serve --bg --https 443 http://supabase-kong:8000'
ssh root@100.68.251.84 'docker exec tailscale-supabase tailscale serve --bg --tcp 5432 tcp://supabase-pooler:5432'
ssh root@100.68.251.84 'docker exec tailscale-supabase tailscale serve --bg --tcp 6543 tcp://supabase-pooler:6543'

Tenant not found error

Username must be postgres.homelab (format: postgres.{POOLER_TENANT_ID}).

no such host resolving container names

You're on a different Docker network. Use LAN IP (supabase.tail1137c.ts.net:5432) or Tailscale URL instead.