From d920649da78a5298fbe2e20dd8c575e76b763526 Mon Sep 17 00:00:00 2001 From: xpltd_admin Date: Fri, 3 Apr 2026 22:42:10 -0600 Subject: [PATCH] Create Deployment wiki page with Docker, nginx, health checks, and backup docs --- Deployment.md | 232 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 Deployment.md diff --git a/Deployment.md b/Deployment.md new file mode 100644 index 0000000..37efdda --- /dev/null +++ b/Deployment.md @@ -0,0 +1,232 @@ +# Deployment + +| Meta | Value | +|------|-------| +| **Repo** | `xpltdco/tubearr` | +| **Page** | `Deployment` | +| **Audience** | developers, agents, newcomers | +| **Last Updated** | 2026-04-04 | +| **Status** | current | + +## Docker Image + +Tubearr uses a multi-stage Docker build to produce a minimal production image. + +### Build Stages + +| Stage | Base | Purpose | +|-------|------|---------| +| **deps** | `node:22-alpine` | Install all npm dependencies | +| **build** | inherits deps | Compile TypeScript backend + build React SPA | +| **runtime** | `node:22-alpine` | Production image with yt-dlp, ffmpeg, compiled app | + +### Runtime Image Contents + +The final image includes: +- Compiled JavaScript (`dist/`) +- Built React SPA (`dist/frontend/`) +- Drizzle migration files (`drizzle/`) +- Production npm dependencies only +- **System packages:** Python 3, pip, yt-dlp, ffmpeg +- **tsx** — for ESM module loading at runtime +- **Directories:** `/config` (database, cookies, settings), `/media` (downloaded files) + +### Build the Image + +```bash +docker build -t tubearr:latest . +``` + +## Docker Compose (Production) + +**File:** `docker-compose.yml` + +```yaml +services: + tubearr: + build: . + container_name: tubearr + ports: + - "8989:8989" + volumes: + - tubearr-config:/config + - ./media:/media + environment: + - NODE_ENV=production + - TUBEARR_PORT=8989 + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8989/ping"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 15s + restart: unless-stopped + +volumes: + tubearr-config: +``` + +### xpltd Infrastructure Deployment + +On the xpltd infrastructure, Tubearr runs as part of the Docker Compose stack on **ub01** (10.0.0.10): + +- **Compose project:** Located in `/vmPool/r/compose/` on ub01 +- **Container name:** `tubearr` +- **Internal port:** 8989 +- **Media storage:** Bind-mounted to a path on the ub01 filesystem + +## Port Mappings + +| Port | Protocol | Purpose | +|------|----------|---------| +| 8989 | HTTP | Fastify server (API + SPA + WebSocket) | + +Only one port is needed — the Fastify server handles everything (REST API, static frontend files, WebSocket upgrade). + +## Volume Mounts + +| Mount | Container Path | Purpose | Backup Priority | +|-------|---------------|---------|-----------------| +| `tubearr-config` (named volume) | `/config` | SQLite database (`tubearr.db`), cookie files, settings | **High** — contains all application state | +| Bind mount | `/media` | Downloaded media files | **Medium** — re-downloadable but large | + +### Config Volume Contents + +``` +/config/ +├── tubearr.db # SQLite database (all channels, content, queue, settings) +├── tubearr.db-wal # WAL journal (auto-managed by SQLite) +├── tubearr.db-shm # Shared memory file (auto-managed by SQLite) +└── cookies/ # Per-platform cookie files for authenticated downloads + ├── youtube.txt + └── soundcloud.txt +``` + +## Nginx / Reverse Proxy + +Tubearr is exposed externally via **nginx01** (10.0.0.9) which handles TLS termination. + +### Traffic Flow + +``` +Client → Cloudflare (DNS: tubearr.xpltd.co) + → nginx01:443 (TLS termination) + → ub01:8989 (Tubearr container) +``` + +### Nginx Configuration + +The nginx vhost for Tubearr proxies all requests including WebSocket upgrades: + +```nginx +server { + listen 443 ssl; + server_name tubearr.xpltd.co; + + # TLS certs managed by nginx01 + ssl_certificate /path/to/cert.pem; + ssl_certificate_key /path/to/key.pem; + + location / { + proxy_pass http://10.0.0.10:8989; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} +``` + +### DNS Configuration + +| Layer | Record | Value | +|-------|--------|-------| +| **Cloudflare** (external) | `tubearr.xpltd.co` CNAME | `dyndns.xpltd.co` | +| **AdGuard Home** (internal) | `*.xpltd.co` rewrite | `10.0.0.9` (nginx01) | + +## Health Checks + +### Docker Health Check + +The compose file includes a health check that polls the `/ping` endpoint: + +```yaml +healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8989/ping"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 15s +``` + +- **Start period:** 15 seconds grace period for startup (migrations, yt-dlp update check) +- **Interval:** Check every 30 seconds +- **Retries:** Mark unhealthy after 3 consecutive failures + +### Application Health Endpoint + +`GET /api/v1/health` returns component-level status: +- Database connectivity +- yt-dlp availability and version +- Scheduler state +- Disk space + +### Liveness Probe + +`GET /ping` — Lightweight, no auth required. Returns 200 if the server is accepting requests. + +## Monitoring & Logging + +### Logging + +Tubearr uses Fastify's built-in Pino logger: +- **Format:** JSON (structured) in production, pretty-printed in development +- **Level:** Controlled by `TUBEARR_LOG_LEVEL` (default: `info`) +- **Output:** stdout/stderr — captured by Docker logging driver + +View logs: +```bash +docker logs -f tubearr +``` + +### Key Log Events + +| Level | Event | +|-------|-------| +| `info` | Server started, channel scanned, download completed | +| `warn` | Rate limited, retry queued, yt-dlp update available | +| `error` | Download failed, database error, notification dispatch failure | + +## Backup Considerations + +### What to Back Up + +| Data | Location | Strategy | +|------|----------|----------| +| **SQLite database** | `/config/tubearr.db` | Copy while using `.backup` or with WAL checkpoint. Most critical. | +| **Cookie files** | `/config/cookies/` | Simple file copy. Contains platform auth cookies. | +| **Media files** | `/media/` | Optional — large, can be re-downloaded. Use rsync for incremental. | + +### SQLite Backup + +For a consistent backup of a WAL-mode database: + +```bash +# From host, exec into container +docker exec tubearr sqlite3 /config/tubearr.db ".backup /config/tubearr-backup.db" + +# Then copy the backup file out +docker cp tubearr:/config/tubearr-backup.db ./tubearr-backup.db +``` + +### Recovery + +1. Stop the container: `docker compose down` +2. Replace `/config/tubearr.db` with backup +3. Remove WAL/SHM files (they'll be recreated): `rm -f /config/tubearr.db-wal /config/tubearr.db-shm` +4. Start the container: `docker compose up -d` \ No newline at end of file