Create Deployment wiki page with Docker, nginx, health checks, and backup docs

xpltd_admin 2026-04-03 22:42:10 -06:00
parent d22e0d89a5
commit d920649da7

232
Deployment.md Normal file

@ -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`