278 lines
7.9 KiB
Markdown
278 lines
7.9 KiB
Markdown
# 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
|
||
|
||
```bash
|
||
cd ~/warmbox # or wherever you cloned the repo
|
||
cp .env.example .env
|
||
nano .env
|
||
```
|
||
|
||
Required in `.env`:
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
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` / `ports` for the `warmbox` service (empty grep for warmbox ports).
|
||
- **Wrong:** lines like `"3000:3000"` or `"127.0.0.1:3000:3000"` under `warmbox` — **delete the entire `ports:` block** for that service.
|
||
|
||
Pull the latest repo (or remove `ports:` manually), then:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```yaml
|
||
ports:
|
||
- "127.0.0.1:3001:3000"
|
||
```
|
||
|
||
NPM still uses forward port **3000** (container port), not 3001.
|
||
|
||
### Find what uses host port 3000
|
||
|
||
```bash
|
||
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:
|
||
|
||
```text
|
||
Internet → warmbox.jonnyn.com (DNS A → home IP) → NPM :443 → warmbox:3000 (Docker)
|
||
```
|
||
|
||
### Step 1 — Start Warmbox on `npm_network`
|
||
|
||
```bash
|
||
docker compose -f docker-compose.yml -f docker-compose.npm.yml up -d --build
|
||
```
|
||
|
||
Verify:
|
||
|
||
```bash
|
||
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
|
||
|
||
1. Open `https://warmbox.jonnyn.com`
|
||
2. API key = your `API_KEY` from `.env`
|
||
3. 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:
|
||
|
||
```text
|
||
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`
|
||
|
||
```bash
|
||
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)
|
||
|
||
```bash
|
||
docker compose --profile cloudflare up -d --build
|
||
```
|
||
|
||
`cloudflared` and `warmbox` share the `warmbox_internal` network — hostname `warmbox` resolves inside that network.
|
||
|
||
### Step 3 — Verify
|
||
|
||
```bash
|
||
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.
|
||
|
||
1. Edit `deploy/Caddyfile` — set `warmbox.jonnyn.com`
|
||
2. DNS A record → home public IP; forward router 80/443 to the server
|
||
3. Start:
|
||
|
||
```bash
|
||
docker compose --profile caddy up -d --build
|
||
```
|
||
|
||
**Skip this** if Nginx Proxy Manager is already on the server.
|
||
|
||
---
|
||
|
||
## Migrate local database (optional)
|
||
|
||
```bash
|
||
# 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:**
|
||
|
||
```bash
|
||
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:**
|
||
|
||
```bash
|
||
docker compose --profile cloudflare logs -f warmbox
|
||
docker compose --profile cloudflare up -d --build
|
||
```
|
||
|
||
**Backup:**
|
||
|
||
```bash
|
||
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](./self-host-hardening.md).
|