Kubernetes integration

Flokk lets your Kubernetes pods connect to PostgreSQL without stored passwords. Pods present a projected service account token; Flokk exchanges it for short-lived database credentials.

Available on Pro and Dedicated tiers.

How it works

  1. Register your K8s cluster's OIDC issuer in the Flokk dashboard
  2. Your pod mounts a projected SA token with audience https://api.flokk.dev
  3. The pod calls the Flokk API with the token
  4. Flokk verifies the token, generates a short-lived PG password (1 hour TTL)
  5. The pod connects to PostgreSQL with the fresh credentials
  6. Before expiry, the pod refreshes — existing connections are not affected

1. Register your OIDC issuer

Go to your database → K8s auth tab. Enter your cluster's OIDC issuer URL and the service accounts you want to allow.

The issuer URL is typically:

2. Configure your pod

apiVersion: v1
kind: Pod
spec:
  serviceAccountName: db-access
  containers:
    - name: app
      volumeMounts:
        - name: db-token
          mountPath: /var/run/secrets/db-token
          readOnly: true
  volumes:
    - name: db-token
      projected:
        sources:
          - serviceAccountToken:
              audience: "https://api.flokk.dev"
              expirationSeconds: 3600
              path: token

3. Fetch credentials

JSON (full details)

TOKEN=$(cat /var/run/secrets/db-token/token)
curl -s -H "Authorization: Bearer $TOKEN" \
  https://api.flokk.dev/api/v1/databases/DB_ID/credentials/k8s

Returns:

{
  "host": "mydb.db.flokk.dev",
  "port": 5432,
  "database": "mydb-a7f3e2",
  "user": "flokk_mydb-a7f3e2_k8s",
  "password": "flk_tmp_...",
  "sslmode": "require",
  "expires_at": "2026-04-12T16:30:00Z",
  "refresh_at": "2026-04-12T16:20:00Z",
  "connection_uri": "postgresql://..."
}

Plain connection string

DATABASE_URL=$(curl -s -H "Authorization: Bearer $TOKEN" \
  https://api.flokk.dev/api/v1/databases/DB_ID/connect/k8s)
psql "$DATABASE_URL"

4. Connect with automatic rotation

Credentials expire after 1 hour. The best way to handle this is using your PG driver's password callback — the driver calls your function whenever it needs to open a new connection, so credentials are always fresh. No background thread needed.

Go (pgx)

package main

import (
    "context"
    "encoding/json"
    "net/http"
    "os"
    "sync"
    "time"

    "github.com/jackc/pgx/v5/pgxpool"
)

type flokkCreds struct {
    mu       sync.Mutex
    user     string
    password string
    expires  time.Time
    dbID     string
}

func (c *flokkCreds) get(ctx context.Context) (string, string, error) {
    c.mu.Lock()
    defer c.mu.Unlock()

    // Return cached if still valid (with 5-min buffer)
    if time.Until(c.expires) > 5*time.Minute {
        return c.user, c.password, nil
    }

    // Fetch fresh credentials from Flokk API
    token, _ := os.ReadFile("/var/run/secrets/db-token/token")
    req, _ := http.NewRequestWithContext(ctx, "GET",
        "https://api.flokk.dev/api/v1/databases/"+c.dbID+"/credentials/k8s", nil)
    req.Header.Set("Authorization", "Bearer "+string(token))

    resp, err := http.DefaultClient.Do(req)
    if err != nil { return "", "", err }
    defer resp.Body.Close()

    var creds struct {
        User      string    `json:"user"`
        Password  string    `json:"password"`
        ExpiresAt time.Time `json:"expires_at"`
    }
    json.NewDecoder(resp.Body).Decode(&creds)
    c.user = creds.User
    c.password = creds.Password
    c.expires = creds.ExpiresAt
    return c.user, c.password, nil
}

func main() {
    creds := &flokkCreds{dbID: "YOUR_DB_ID"}

    cfg, _ := pgxpool.ParseConfig("host=mydb.db.flokk.dev port=5432 dbname=mydb sslmode=require")
    cfg.BeforeConnect = func(ctx context.Context, cc *pgx.ConnConfig) error {
        // Called every time the pool opens a new connection.
        // Credentials are cached and only refreshed when near expiry.
        user, pass, err := creds.get(ctx)
        if err != nil { return err }
        cc.User = user
        cc.Password = pass
        return nil
    }

    pool, _ := pgxpool.NewWithConfig(context.Background(), cfg)
    // Use pool — credentials rotate automatically on new connections
}

Python (psycopg3)

import requests, time, psycopg

DB_ID = "YOUR_DB_ID"
_cache = {"user": "", "password": "", "expires": 0}

def get_password():
    # Return cached if still valid (5-min buffer)
    if time.time() < _cache["expires"] - 300:
        return _cache["password"]

    token = open("/var/run/secrets/db-token/token").read()
    r = requests.get(
        f"https://api.flokk.dev/api/v1/databases/{DB_ID}/credentials/k8s",
        headers={"Authorization": f"Bearer {token}"}
    )
    creds = r.json()
    _cache["user"] = creds["user"]
    _cache["password"] = creds["password"]
    _cache["expires"] = creds["expires_at"]  # parse as needed
    return _cache["password"]

# psycopg3: password can be a callable — called on every new connection
conn = psycopg.connect(
    host="mydb.db.flokk.dev",
    dbname="mydb-a7f3e2",
    user=get_password,   # callable, not a string
    password=get_password,
    sslmode="require",
)

Node.js (pg)

import pg from 'pg';
import fs from 'fs';

const DB_ID = 'YOUR_DB_ID';
let cached = { user: '', password: '', expiresAt: 0 };

async function getCreds() {
  if (Date.now() < cached.expiresAt - 300_000) return cached;

  const token = fs.readFileSync('/var/run/secrets/db-token/token', 'utf8');
  const r = await fetch(
    `https://api.flokk.dev/api/v1/databases/${DB_ID}/credentials/k8s`,
    { headers: { Authorization: `Bearer ${token}` } }
  );
  const creds = await r.json();
  cached = { ...creds, expiresAt: new Date(creds.expires_at).getTime() };
  return cached;
}

// pg Pool: override password on each new connection
const pool = new pg.Pool({
  host: 'mydb.db.flokk.dev',
  database: 'mydb-a7f3e2',
  ssl: { rejectUnauthorized: false },
});

pool.on('connect', async (client) => {
  const c = await getCreds();
  client.user = c.user;
  client.password = c.password;
});

Shell (one-liner)

# For scripts, just fetch fresh on every invocation:
psql "$(curl -s -H "Authorization: Bearer $(cat /var/run/secrets/db-token/token)" \
  https://api.flokk.dev/api/v1/databases/DB_ID/connect/k8s)"

How rotation works under the hood

  1. Your app calls the Flokk API (via the password callback or explicitly)
  2. Flokk generates a random password and runs ALTER ROLE ... PASSWORD ... VALID UNTIL now()+1h
  3. The SCRAM-SHA-256 hash is stored in PostgreSQL's pg_authid catalog
  4. PgBouncer picks up the new hash via auth_query on its next connection to PG — no restart needed
  5. After 1 hour, PostgreSQL rejects the old password. Your driver's callback fetches a fresh one.
  6. Existing open connections are not affected — PG only checks the password at connection time

Security model

Password lifetime1 hour (VALID UNTIL)
Password storageSCRAM-SHA-256 hash in pg_authid
PgBouncer authauth_query against pg_shadow (dynamic)
Open connectionsNot affected by rotation
K8s roleInherits owner role via GRANT
AuditEvery credential exchange is logged
No results found.