7.9 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
Not suitable: Netlify, GitHub Pages, Vercel — no background jobs, no SQLite, no mail sockets.
Your setup (warmbox.jonnyn.com)
You already have:
- DNS A record → home public IP
- Nginx Proxy Manager (NPM) on Docker network
npm_network
Recommended for you: Option B — Nginx Proxy Manager (simplest; reuses what you have).
Use Option A — Cloudflare Tunnel only if you want HTTPS without opening ports 80/443, or if your ISP blocks inbound traffic.
Do not run Caddy (Option C) alongside NPM — both want ports 80 and 443.
1. Prepare the server
cd ~/warmbox # or wherever you cloned the repo
cp .env.example .env
nano .env
Required in .env:
ENCRYPTION_KEY=<64 hex chars — openssl rand -hex 32>
API_KEY=<openssl rand -hex 24>
PUBLIC_BASE_URL=https://warmbox.jonnyn.com
AI_API_KEY=sk-or-v1-...
Keep ENCRYPTION_KEY forever. If migrating from your Mac, copy the same key and prisma/dev.db.
2. Fix: “port 3000 already allocated”
Warmbox listens on port 3000 inside its container. That does not conflict with homepage using host port 3000 — unless you publish Warmbox to the host with a ports: mapping.
NPM and Cloudflare Tunnel only need Docker network access (warmbox:3000). No host port is required.
Verify your compose file on the server
cd /srv/docker-configs/warmbox
grep -n ports docker-compose.yml docker-compose.npm.yml 2>/dev/null
docker compose -f docker-compose.yml -f docker-compose.npm.yml config | grep -A2 published
- Correct: no
published/portsfor thewarmboxservice (empty grep for warmbox ports). - Wrong: lines like
"3000:3000"or"127.0.0.1:3000:3000"underwarmbox— delete the entireports:block for that service.
Pull the latest repo (or remove ports: manually), then:
docker compose -f docker-compose.yml -f docker-compose.npm.yml down
docker compose -f docker-compose.yml -f docker-compose.npm.yml up -d --build
Optional: direct LAN access on a different host port
Only if you want to hit Warmbox without NPM (debugging). This uses host 3001, not 3000:
ports:
- "127.0.0.1:3001:3000"
NPM still uses forward port 3000 (container port), not 3001.
Find what uses host port 3000
docker ps --format '{{.Names}} {{.Ports}}' | grep '0.0.0.0:3000'
In your case homepage owns host 3000 — leave it alone; Warmbox does not need that port.
Option B — Nginx Proxy Manager (recommended for your home server)
Traffic path:
Internet → warmbox.jonnyn.com (DNS A → home IP) → NPM :443 → warmbox:3000 (Docker)
Step 1 — Start Warmbox on npm_network
docker compose -f docker-compose.yml -f docker-compose.npm.yml up -d --build
Verify:
docker compose -f docker-compose.yml -f docker-compose.npm.yml ps
docker compose -f docker-compose.yml -f docker-compose.npm.yml logs -f warmbox
curl -s http://warmbox:3000/health # run from any container on npm_network, or skip
Step 2 — NPM proxy host
In Nginx Proxy Manager UI:
| Field | Value |
|---|---|
| Domain names | warmbox.jonnyn.com |
| Scheme | http |
| Forward hostname / IP | warmbox |
| Forward port | 3000 (container port — not your host port 3000 used by homepage) |
| Cache assets | off |
| Block common exploits | on |
| Websockets support | on |
SSL tab:
- Request a new SSL Certificate (Let's Encrypt)
- Force SSL + HTTP/2
If you use Cloudflare orange cloud (proxied) on the A record, set Cloudflare SSL mode to Full or Full (strict) so NPM’s certificate works.
Step 3 — Login
- Open
https://warmbox.jonnyn.com - API key = your
API_KEYfrom.env - Leave Base URL blank
Step 4 — OAuth redirect URIs
| Integration | Redirect URI |
|---|---|
| Google Postmaster | https://warmbox.jonnyn.com/api/integrations/google/callback |
| Microsoft Outlook | https://warmbox.jonnyn.com/api/integrations/microsoft/callback |
Option A — Cloudflare Tunnel
Use this if you prefer no inbound ports on your router (tunnel only).
Traffic path:
Browser → Cloudflare edge → cloudflared (Docker) → warmbox:3000
Important DNS note
For tunnel-only access, let the tunnel own the hostname. In Cloudflare Zero Trust → your tunnel → Public Hostname:
| Field | Value |
|---|---|
| Subdomain | warmbox |
| Domain | jonnyn.com |
| Type | HTTP |
| URL | warmbox:3000 |
Cloudflare usually creates a CNAME for you. You can remove the A record for warmbox if everything goes through the tunnel (avoids split routing).
Step 1 — .env
CLOUDFLARE_TUNNEL_TOKEN=eyJh... # from Zero Trust → Tunnels → your tunnel → Docker install
PUBLIC_BASE_URL=https://warmbox.jonnyn.com
Step 2 — Start (no NPM override file)
docker compose --profile cloudflare up -d --build
cloudflared and warmbox share the warmbox_internal network — hostname warmbox resolves inside that network.
Step 3 — Verify
docker compose --profile cloudflare ps
docker compose --profile cloudflare logs -f cloudflared
docker compose --profile cloudflare logs -f warmbox
curl -s https://warmbox.jonnyn.com/health
Tunnel + NPM together?
Pick one public entry for warmbox.jonnyn.com:
- NPM only: A record → home IP, no tunnel hostname for the same subdomain.
- Tunnel only: tunnel hostname, no A record to home IP for that subdomain.
Running both for the same hostname causes confusing SSL or routing issues.
Option C — Caddy (standalone, no NPM)
Only if NPM is not using 80/443 on this host.
- Edit
deploy/Caddyfile— setwarmbox.jonnyn.com - DNS A record → home public IP; forward router 80/443 to the server
- Start:
docker compose --profile caddy up -d --build
Skip this if Nginx Proxy Manager is already on the server.
Migrate local database (optional)
# Mac
scp prisma/dev.db user@home-server:/tmp/warmbox.db
# Server — stack stopped
docker compose -f docker-compose.yml -f docker-compose.npm.yml down
docker run --rm -v warmbox_warmbox-data:/data -v /tmp:/backup alpine \
cp /backup/warmbox.db /data/warmbox.db
docker compose -f docker-compose.yml -f docker-compose.npm.yml up -d
Use the same ENCRYPTION_KEY as on your Mac.
Day-to-day commands
NPM deployment:
docker compose -f docker-compose.yml -f docker-compose.npm.yml logs -f warmbox
docker compose -f docker-compose.yml -f docker-compose.npm.yml up -d --build
Cloudflare deployment:
docker compose --profile cloudflare logs -f warmbox
docker compose --profile cloudflare up -d --build
Backup:
docker compose exec warmbox sh -c 'cp /data/warmbox.db /data/warmbox-$(date +%F).db'
Troubleshooting
| Symptom | Fix |
|---|---|
port 3000 already allocated |
Use updated compose (no host ports); docker compose down; find conflict with docker ps | grep 3000 |
| NPM 502 Bad Gateway | Warmbox not on npm_network; wrong forward host (warmbox not localhost); check docker compose logs warmbox |
| Tunnel 502 / error | Public hostname URL must be http://warmbox:3000; both containers on warmbox_internal; check logs cloudflared |
| Login fails | API_KEY in .env must match login; restart after .env change |
| OAuth redirect error | PUBLIC_BASE_URL=https://warmbox.jonnyn.com exactly; update Google/Microsoft redirect URIs |
| SSL errors with Cloudflare proxy | Cloudflare SSL mode Full or Full (strict) when NPM terminates TLS |
See also self-host-hardening.md.