Skip to content

Production & Maintenance

Deploying Telmoni in high-security production environments requires hardening database access, establishing automated audit partition rotations, configuring TLS reverse proxies, and transitioning authentication from quickstart passwords to enterprise Single Sign-On (OIDC).

Identity & Authentication: Production OIDC Hardening

Section titled “Identity & Authentication: Production OIDC Hardening”

When first launching Telmoni, you can bootstrap an initial owner account using ADMIN_EMAIL and ADMIN_PASSWORD. However, local username and password authentication is designed only for quickstarts, local testing, and evaluation.

In a production deployment, we strongly recommend connecting an enterprise OpenID Connect (OIDC) identity provider (such as Okta, Keycloak, Microsoft Entra ID, Authentik, or Google Workspace) and disabling local password forms entirely.

  1. Centralized Lifecycle & Access Control: When an employee leaves your organization or changes roles, deprovisioning their access in your IdP immediately blocks their Telmoni session.
  2. Enforce Enterprise MFA: All multi-factor authentication (FIDO2 WebAuthn, hardware security keys, TOTP) is enforced upstream at your identity provider.
  3. Eliminate Password Attacks: Removing local password forms eliminates credential stuffing, password spraying, and brute-force vectors against your console.
  1. Configure your OpenID Connect Provider

    Register Telmoni in your Identity Provider (IdP) with the following parameters:

    • Allowed Redirect URI: https://telmoni.example.com/auth/callback
    • Scopes: openid, email, profile
  2. Add OIDC variables to your environment

    Configure the provider details in your .env or Kubernetes secret:

    Terminal window
    # Identity Provider Discovery URL (must serve /.well-known/openid-configuration)
    OIDC_ISSUER=https://idp.example.com/realms/production
    OIDC_CLIENT_ID=telmoni-client-id
    OIDC_CLIENT_SECRET=telmoni-client-secret
    OIDC_REDIRECT_URI=https://telmoni.example.com/auth/callback
    OIDC_NAME="Corporate SSO"
    # Restrict self-registration
    OIDC_ALLOW_SIGN_UP=false
    ALLOW_SIGN_UP=false
  3. Verify OIDC sign-in

    Restart the server and log in using the newly available “Continue with Corporate SSO” button. Confirm that your user identity logs in and gains administrative access.

  4. Disable the local password form

    Once OIDC login is verified, remove ADMIN_EMAIL and ADMIN_PASSWORD from your environment and set:

    Terminal window
    DISABLE_LOGIN_FORM=true

    [!IMPORTANT] The Telmoni server performs sanity checks at boot:

    • If DISABLE_LOGIN_FORM=true is set without a configured OIDC_ISSUER, the server halts immediately because nobody could sign in.
    • If ADMIN_EMAIL is set alongside DISABLE_LOGIN_FORM=true, the server halts because a password account could never log in. Remove ADMIN_EMAIL before enabling DISABLE_LOGIN_FORM.

Telmoni enforces strict least-privilege principles at the database layer. In a production cluster, the application modules should never connect as a PostgreSQL superuser.

The database tier is split into four distinct roles:

┌─────────────────┬────────────────────────────────────────────────────────┐
│ Role │ Granted Capabilities │
├─────────────────┼────────────────────────────────────────────────────────┤
│ telmoni_migrator│ Owns all tables, schemas, and views. Runs DDL. │
│ telmoni_auth │ CRUD on accounts, orgs, projects, and RBAC tables. │
│ telmoni_notif │ CRUD on delivery queues, feeds, and connector records. │
│ telmoni_agent │ Read/write on document chunks and vector embeddings. │
└─────────────────┴────────────────────────────────────────────────────────┘

To apply the production hardening policies, run the superuser setup script provided in the repository:

  1. Create the login roles

    Connect to your PostgreSQL 17 cluster as a superuser and create the service roles:

    CREATE ROLE telmoni_migrator WITH LOGIN PASSWORD 'migrator_secure_password';
    CREATE ROLE telmoni_auth WITH LOGIN PASSWORD 'auth_secure_password';
    CREATE ROLE telmoni_notif WITH LOGIN PASSWORD 'notif_secure_password';
    CREATE ROLE telmoni_agent WITH LOGIN PASSWORD 'agent_secure_password';
  2. Execute hardening script

    Apply crates/migrator/sql/role_hardening.sql:

    Terminal window
    psql "$SUPERUSER_DATABASE_URL" -v ON_ERROR_STOP=1 -f crates/migrator/sql/role_hardening.sql

    This script:

    • Strips SUPERUSER, CREATEROLE, and CREATEDB from all service roles.
    • Enforces NOBYPASSRLS across telmoni_auth, telmoni_notif, and telmoni_agent so that application pools cannot bypass PostgreSQL Row-Level Security policies.
    • Locks down default permissions in the public schema.
  3. Run migrations and apply object grants

    Execute the migrator as the telmoni_migrator user:

    Terminal window
    MIGRATOR_DATABASE_URL="postgres://telmoni_migrator:migrator_secure_password@postgres:5432/telmoni" \
    telmoni migrate

    The migrator automatically executes object_grants.sql, granting only the specific SELECT, INSERT, UPDATE, and DELETE privileges each service module needs to operate.


Telmoni’s audit log uses range-partitioned tables partitioned monthly by timestamp (occurred_at). To maintain query performance and automatically drop tables that exceed your organization’s retention window, run the rotation command:

Terminal window
telmoni rotate
  • Forward Runway: Always ensures that the current calendar month and the next three months of partitions exist ahead of time ({table}_YYYY_MM).
  • Retention Pruning: Automatically drops expired monthly child tables that fall outside your configured retention window.
  • Idempotency: Safe to run continuously; if partitions exist, no changes are made.

Run the rotation job once per week (or once per day):

/etc/cron.d/telmoni-rotate
0 3 * * 0 root docker compose -f /opt/telmoni/deploy/compose/docker-compose.yml run --rm migrate telmoni rotate

The telmoni binary includes administrative commands for cluster operators.

While telmoni serve runs scheduled background sweeps in its main loop, you can trigger individual maintenance jobs on demand:

Terminal window
# Execute soft-deletion cleanup saga (permanently purges organizations past 14-day grace)
telmoni sweep deletion
# Verify cryptographic hash chain across the audit ledger
telmoni sweep audit-verify
# Purge read notifications and webhook delivery logs past retention
telmoni sweep retention
# Re-index all document and metadata embeddings into pgvector
telmoni sweep agent-reindex

If an organization needs to be terminated or recovered manually by a platform administrator:

Terminal window
# Immediately suspend an organization and enter the 14-day soft-deletion window
telmoni terminate org_01j7abcde987654321
# Restore a suspended organization within the 14-day grace period
telmoni restore org_01j7abcde987654321

In production, place Telmoni behind a reverse proxy that terminates TLS, enforces HTTP/2 or HTTP/3, and handles WebSocket upgrades. Never expose internal ports 3000 or 8082 directly to the public internet.

Caddy automatically provisions and renews Let’s Encrypt / ZeroSSL certificates:

/etc/caddy/Caddyfile
telmoni.example.com {
encode zstd gzip
# Next.js Web Console
reverse_proxy localhost:3000 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}

Telmoni stores all persistent state in PostgreSQL. Create periodic physical or logical backups using pg_dump:

Terminal window
# Logical database dump
pg_dump -U telmoni -d telmoni -Fc -f "/backups/telmoni_$(date +%Y%m%d_%H%M%S).dump"

To restore from a backup file:

Terminal window
pg_restore -U telmoni -d telmoni --clean --if-exists "/backups/telmoni_backup.dump"

[!IMPORTANT] If your deployment uses CONNECTOR_KEK for encrypting connector tokens at rest, always safeguard and backup your encryption key alongside your database dumps. Restoring database rows without the matching key will make connector credentials permanently unreadable.


To upgrade a self-hosted Telmoni installation:

  1. Pull the latest container images

    Terminal window
    docker compose -f deploy/compose/docker-compose.yml pull
  2. Execute database migrations

    Always run the migration step before starting new application containers:

    Terminal window
    docker compose -f deploy/compose/docker-compose.yml run --rm migrate
  3. Restart the server and web console

    Terminal window
    docker compose -f deploy/compose/docker-compose.yml up -d