Self-Hosting
Deploy Owlat on your own infrastructure with Docker Compose. Complete guide from first boot to production.
Owlat runs entirely on your own infrastructure via a single Docker Compose file. No cloud services are required — the stack includes its own database, real-time engine, mail server, and antivirus scanner.
Why Self-Host
- Data sovereignty — your emails, contacts, and analytics never leave your servers
- Compliance — meet GDPR, HIPAA, or industry-specific data residency requirements
- Air-gapped environments — run without any external network dependencies
- Full control — customize, audit, and extend every component
Prerequisites
| Requirement | Minimum |
|---|---|
| Docker | 20.10+ |
| Docker Compose | v2 (the docker compose plugin) |
| RAM | 4 GB (8 GB recommended) |
| Disk | 20 GB free |
| openssl | Any version (for generating secrets) |
| Domain + DNS control | Required for production email delivery |
Fastest path
On a fresh Linux VPS with Docker + Docker Compose v2 installed:
curl -fsSL https://get.owlat.app | bash
The one-liner clones the repo, runs preflight checks, and hands off to the interactive setup wizard described below. Non-interactive usage:
# Skip prompts, accept all defaults
OWLAT_ASSUME_YES=1 curl -fsSL https://get.owlat.app | bash
# Read answers from a config file (for CI / Ansible)
OWLAT_CONFIG_FILE=/path/to/answers.env curl -fsSL https://get.owlat.app | bash
Stripe billing, Hetzner VPS provisioning, and tier management live in the hosted-cloud control plane (apps/nest + apps/nest-api) and are disabled in self-hosted mode. Run with docker compose up -d — the control plane is gated behind --profile hosted and only operators of the managed service need it.
Quick Start
Option A: Setup Wizard (recommended)
The interactive wizard generates secrets, writes your .env, starts the Docker stack, deploys functions, and creates your admin account — all automatically.
git clone https://github.com/owlat/owlat.git
cd owlat
bash scripts/setup.sh
# Select "Self-Hosted (Docker Compose)" when prompted
The wizard takes about 5 minutes and handles everything below.
Option B: Manual Setup
1. Clone and configure
git clone https://github.com/owlat/owlat.git
cd owlat
cp .env.selfhost.example .env
2. Generate secrets
# Instance secret (hex)
openssl rand -hex 32
# MTA API key and webhook secret (base64)
openssl rand -base64 32
openssl rand -base64 32
Paste the generated values into .env for INSTANCE_SECRET, MTA_API_KEY, and MTA_WEBHOOK_SECRET.
3. Start the stack
docker compose up -d
4. Wait for Convex to be ready
# Watch logs until you see "ready" or "listening"
docker compose logs -f convex
# Press Ctrl+C once ready
5. Generate the admin key
docker compose exec convex ./generate_admin_key.sh
Copy the output key and add it to your .env:
# In .env, set:
CONVEX_ADMIN_KEY=<paste-key-here>
Restart to pick up the new key:
docker compose up -d
6. Deploy functions
# Deploy the main Convex functions
docker compose --profile deploy run --rm convex-deploy
# Deploy the control plane functions
docker compose --profile deploy run --rm nest-api-deploy
7. Set Convex environment variables
These are application-level variables read by the serverless functions. Set them using the Convex CLI with your admin key:
export CONVEX_URL=http://localhost:3210
export CONVEX_ADMIN_KEY=<your-admin-key>
npx convex env set BETTER_AUTH_SECRET "$(openssl rand -base64 32)" --url $CONVEX_URL --admin-key $CONVEX_ADMIN_KEY
npx convex env set UNSUBSCRIBE_SECRET "$(openssl rand -base64 32)" --url $CONVEX_URL --admin-key $CONVEX_ADMIN_KEY
npx convex env set SITE_URL "http://localhost:3000" --url $CONVEX_URL --admin-key $CONVEX_ADMIN_KEY
npx convex env set CONVEX_SITE_URL "http://localhost:3211" --url $CONVEX_URL --admin-key $CONVEX_ADMIN_KEY
npx convex env set EMAIL_PROVIDER "mta" --url $CONVEX_URL --admin-key $CONVEX_ADMIN_KEY
npx convex env set MTA_API_URL "http://mta:3100" --url $CONVEX_URL --admin-key $CONVEX_ADMIN_KEY
npx convex env set MTA_API_KEY "<your-mta-api-key>" --url $CONVEX_URL --admin-key $CONVEX_ADMIN_KEY
npx convex env set MTA_WEBHOOK_SECRET "<your-mta-webhook-secret>" --url $CONVEX_URL --admin-key $CONVEX_ADMIN_KEY
npx convex env set DEFAULT_FROM_EMAIL "noreply@example.com" --url $CONVEX_URL --admin-key $CONVEX_ADMIN_KEY
npx convex env set DEFAULT_FROM_NAME "Owlat" --url $CONVEX_URL --admin-key $CONVEX_ADMIN_KEY
npx convex env set DEFAULT_FROM_DOMAIN "example.com" --url $CONVEX_URL --admin-key $CONVEX_ADMIN_KEY
8. Open the dashboard
open http://localhost:3000
The setup wizard creates an admin account automatically. For manual setup, use the Convex dashboard at http://localhost:6791 to create your first user, or call the seed endpoint documented in the setup script.
Stack Overview
| Service | Port(s) | Role |
|---|---|---|
| Convex | 3210, 3211 | Database, real-time subscriptions, serverless functions, file storage |
| Convex Dashboard | 6791 | Admin UI for inspecting data and debugging functions |
| Web | 3000 | Nuxt application — dashboard, email builder, settings |
| MTA | 3100, 25 | Mail transfer agent — SMTP delivery, bounce processing, IP warming |
| Redis | 6379 | Job queue and rate limiting state for the MTA |
| ClamAV | 3310 | Antivirus scanning for email attachments |
| Nest | 3001 | Admin panel for organization and infrastructure management |
For a deeper dive into the architecture, see Self-Hosting Architecture.
What's Next
- Configuration Reference — all environment variables, service topology, and volumes
- DNS & Email Setup — SPF, DKIM, DMARC, and bounce handling for production email
- Production Deployment — reverse proxy, TLS, firewall, backups, and monitoring
- Maintenance & Updates — keeping your instance up to date and troubleshooting