Guide

Self-Hosting sTELgano: Your Server, Your Rules

2026-03-08 9 min read

Why Self-Host?

The ultimate trust boundary. When you run your own sTELgano instance, you control the server, the database, the network, and the logs. You do not have to trust anyone — not even us.

Even though our hosted version is zero-knowledge by design, self-hosting removes the last remaining trust assumption: that we are actually running the code we publish. With your own instance, you verify by running.

What You Need

Elixir/Erlang Runtime

Elixir 1.17+ and Erlang/OTP 27+. Install via asdf, mise, or your system package manager.

PostgreSQL

PostgreSQL 15+ with binary UUID support. The only external dependency besides the runtime.

Domain Name

A domain name pointed at your server. Required for TLS and for the WebSocket connection.

TLS Certificate

HTTPS is mandatory. Use Let’s Encrypt for free automated certificates via certbot or Caddy.

Environment Variables

The application requires a small set of environment variables to run in production:

Variable Required Purpose
PHX_SERVER Yes Set to true so the release binds the HTTP endpoint on boot
SECRET_KEY_BASE Yes Phoenix session signing (64+ hex chars)
DATABASE_URL Yes PostgreSQL connection string
PHX_HOST Yes Your production domain name
ADMIN_PASSWORD Yes HTTP Basic Auth password for /admin
POOL_SIZE No DB connection pool size (default: 10)

Generate a secret key base with:

mix phx.gen.secret

Custom Salts

You can override the default cryptographic salts via environment variables: ROOM_SALT, ACCESS_SALT, SENDER_SALT, and ENC_SALT.

WARNING: Changing salts after users have created rooms will make all existing rooms permanently inaccessible. The same phone number and PIN will produce different hashes with different salts. There is no migration path. This is a one-way, destructive operation.

Only set custom salts before your first user creates a room. If you are unsure, leave them at the defaults.

Database Setup

PostgreSQL with binary UUIDs. All table IDs are generated as binary UUIDs, and all timestamps use UTC. The setup is straightforward:

mix ecto.setup

This single command creates the database, runs all migrations, and seeds initial data. Oban job tables are created alongside the application tables — no separate migration step is needed.

Deployment Options

Bare Metal

Build a release with mix release and run it directly. Simplest deployment, full control.

Docker

Containerised deployment for consistent environments. Works with any container orchestrator.

PaaS

Fly.io, Railway, or Render. Platform-managed infrastructure with minimal configuration.

The app is a single Elixir release. One binary, one process tree. No microservices, no external message brokers, no Redis. PostgreSQL is the only infrastructure dependency.

Security Considerations

The built-in security headers and Content Security Policy are configured by default. Out of the box, sTELgano ships with:

  • HSTS — enforces HTTPS for all connections
  • CSP — strict Content Security Policy blocking inline scripts and external resources
  • Cache-Control: no-store — prevents caching of sensitive pages
  • Rate limiting — IP-based throttling via PlugAttack

On your end: configure your firewall, keep Elixir and Erlang updated, and restrict SSH access to your server.

Monitoring

The admin dashboard at /admin provides aggregate metrics about your instance. It is protected by HTTP Basic Auth — set your credentials via environment variables.

The dashboard shows operational data only: room counts, message volumes, and system health. It never exposes individual room contents, user identifiers, or message content. The admin can see that the system is running; they cannot see what anyone is saying.

Maintenance

Oban handles automatic cleanup with no manual intervention needed:

  • Messages are hard-deleted immediately when a reply arrives (N=1 invariant)
  • Every hour, expired TTL rooms are deactivated and their messages and (room_hash, access_hash) records are hard-deleted in the same transaction

The maintenance queue runs with 2 workers, ensuring cleanup tasks complete without impacting real-time chat performance.

Upgrades

Upgrading is straightforward:

git pull origin main
mix deps.get
mix ecto.migrate
mix phx.server

The protocol is designed for backward compatibility within the same major version. Migrations are additive — they add columns and tables but do not remove or rename existing ones. Your data is safe across upgrades.

Ready to run your own instance?

Try the Hosted Version First