6 KiB
Deploy Warmbox on Ubuntu (Docker)
Warmbox is a long-running worker (cron dispatcher, rescuer, SMTP/IMAP). It needs:
- A persistent database (SQLite volume)
- Outbound SMTP/IMAP from the server
- A public HTTPS URL for the dashboard and OAuth callbacks
Recommended for your case: Ubuntu home server + Docker + Cloudflare Tunnel (free HTTPS, no router port forwarding).
Not suitable: Netlify, GitHub Pages, Vercel static — no background jobs, no SQLite, no mail sockets.
Quick comparison
| Option | Best for | Pros | Cons |
|---|---|---|---|
| Ubuntu + Docker + Cloudflare Tunnel | Home server (your preference) | Free, persistent, full control | You maintain the box |
| Ubuntu + Docker + Caddy | Home server with public IP + domain | Simple TLS | Open ports 80/443 on router |
| Hetzner / DigitalOcean VPS + same Docker | No home server | Always on, static IP | ~$5–6/mo |
| Railway / Fly.io | Managed container | Easy deploy | Cost, less control for 30-day test |
1. Prepare the Ubuntu server
# SSH into your home server
sudo apt update && sudo apt upgrade -y
sudo apt install -y git docker.io docker-compose-v2
sudo usermod -aG docker $USER
# log out and back in so docker group applies
Clone the repo:
git clone https://github.com/YOUR_ORG/warmbox.git
cd warmbox
2. Configure environment
cp .env.example .env
nano .env
Set these before first start:
# Generate once — keep forever (changing it breaks encrypted mailbox passwords)
openssl rand -hex 32 # → ENCRYPTION_KEY
# Dashboard login password
openssl rand -hex 24 # → API_KEY
PUBLIC_BASE_URL=https://warmbox.yourdomain.com # your real public URL
AI_API_KEY=sk-or-v1-... # OpenRouter or compatible
Migrating from local: copy your existing prisma/dev.db to the server and use the same ENCRYPTION_KEY from your local .env. Otherwise mailbox credentials cannot be decrypted.
3. Choose how the world reaches your server
Option A — Cloudflare Tunnel (recommended for home)
No port forwarding. Works behind CGNAT.
-
Cloudflare Zero Trust → Networks → Tunnels → Create tunnel
-
Name it
warmbox, install connector via Docker — copy the token -
In the tunnel Public Hostname tab, route
warmbox.yourdomain.com→http://warmbox:3000(Docker network)
If cloudflared runs in the same compose file, use hostnamewarmboxand port3000. -
Add to
.env:CLOUDFLARE_TUNNEL_TOKEN=eyJh... PUBLIC_BASE_URL=https://warmbox.yourdomain.com -
Start:
docker compose --profile cloudflare up -d --build
Option B — Caddy on a public IP
-
Point DNS
warmbox.yourdomain.com→ your home public IP -
Forward router ports 80 and 443 to the Ubuntu server
-
Edit
deploy/Caddyfilewith your domain -
Start:
docker compose --profile caddy up -d --build
Option C — LAN / direct port (testing only)
# In docker-compose.yml change ports to "3000:3000"
docker compose up -d --build
Use http://SERVER_LAN_IP:3000 — not suitable for OAuth or remote access.
4. First login
- Open
https://warmbox.yourdomain.com - Login with your
API_KEY - Leave Base URL blank (dashboard and API are same origin in production)
5. Update OAuth redirect URIs
Replace localhost with your public URL in:
| Integration | Redirect URI |
|---|---|
| Google Postmaster | https://warmbox.yourdomain.com/api/integrations/google/callback |
| Microsoft Outlook | https://warmbox.yourdomain.com/api/integrations/microsoft/callback |
Re-connect integrations in Settings after deploy.
6. Migrate local data (optional)
If you already configured mailboxes and campaigns locally:
# On your Mac — stop local dev server first
scp prisma/dev.db user@home-server:/tmp/warmbox.db
# On Ubuntu — before first start OR with stack stopped:
docker compose down
docker volume create warmbox_warmbox-data 2>/dev/null || true
docker run --rm -v warmbox_warmbox-data:/data -v /tmp:/backup alpine \
cp /backup/warmbox.db /data/warmbox.db
docker compose --profile cloudflare up -d
Copy the same ENCRYPTION_KEY and API_KEY (or set a new API_KEY and use that to log in).
7. Operations
# Logs
docker compose logs -f warmbox
# Restart after code pull
git pull
docker compose --profile cloudflare up -d --build
# Backup database (run daily via cron)
docker compose exec warmbox sh -c 'cp /data/warmbox.db /data/warmbox-$(date +%F).db'
# Health check
curl -s https://warmbox.yourdomain.com/health
30-day warmup checklist
- Container
restart: unless-stopped(default in compose) warmbox-datavolume backed upENCRYPTION_KEYsaved in your password manager- Campaign started and status active
- Settings → Jobs shows dispatcher/rescuer running (or check logs every few hours day 1)
- OAuth redirect URIs updated if using Postmaster / Microsoft
8. Security notes (single-tenant MVP)
- Always set
API_KEYwhen exposed to the internet. - Do not commit
.envto git. - Restrict dashboard access further with Cloudflare Access (optional) or VPN if desired.
- Mailbox app passwords live encrypted in SQLite — protect the volume and
ENCRYPTION_KEY.
Troubleshooting
| Symptom | Fix |
|---|---|
| Login fails | Check API_KEY in .env matches what you enter; restart container after .env change |
| OAuth redirect error | PUBLIC_BASE_URL must match browser URL; update provider redirect URI |
| Jobs not sending | docker compose logs warmbox — verify cron lines; confirm SMTP/IMAP from server IP isn't blocked |
ENCRYPTION_KEY error on decrypt |
Wrong key vs database — restore original key or re-add mailboxes |
| Container unhealthy | docker compose logs warmbox — migration or port conflict |
See also self-host-hardening.md for operator workflows.