# 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 ```bash # 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: ```bash git clone https://github.com/YOUR_ORG/warmbox.git cd warmbox ``` --- ## 2. Configure environment ```bash cp .env.example .env nano .env ``` Set these **before first start**: ```bash # 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. 1. [Cloudflare Zero Trust](https://one.dash.cloudflare.com/) → **Networks** → **Tunnels** → Create tunnel 2. Name it `warmbox`, install connector via **Docker** — copy the token 3. In the tunnel **Public Hostname** tab, route `warmbox.yourdomain.com` → `http://warmbox:3000` (Docker network) If cloudflared runs in the same compose file, use hostname `warmbox` and port `3000`. 4. Add to `.env`: ```bash CLOUDFLARE_TUNNEL_TOKEN=eyJh... PUBLIC_BASE_URL=https://warmbox.yourdomain.com ``` 5. Start: ```bash docker compose --profile cloudflare up -d --build ``` ### Option B — Caddy on a public IP 1. Point DNS `warmbox.yourdomain.com` → your home public IP 2. Forward router ports **80** and **443** to the Ubuntu server 3. Edit `deploy/Caddyfile` with your domain 4. Start: ```bash docker compose --profile caddy up -d --build ``` ### Option C — LAN / direct port (testing only) ```bash # 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 1. Open `https://warmbox.yourdomain.com` 2. Login with your `API_KEY` 3. 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: ```bash # 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 ```bash # 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-data` volume backed up - [ ] `ENCRYPTION_KEY` saved 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_KEY`** when exposed to the internet. - Do not commit `.env` to 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](./self-host-hardening.md) for operator workflows.