Self-Hosting sTELgano: Your Server, Your Rules
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