Create Deployment wiki page with Docker, nginx, health checks, and backup docs
parent
d22e0d89a5
commit
d920649da7
1 changed files with 232 additions and 0 deletions
232
Deployment.md
Normal file
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`
|
||||||
Loading…
Add table
Reference in a new issue