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
docker build -t tubearr:latest .
Docker Compose (Production)
File: docker-compose.yml
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:
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:
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:
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:
# 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
- Stop the container:
docker compose down - Replace
/config/tubearr.dbwith backup - Remove WAL/SHM files (they'll be recreated):
rm -f /config/tubearr.db-wal /config/tubearr.db-shm - Start the container:
docker compose up -d