Files
linearvoid/DEPLOYMENT.md
T
zwitschi 5b5cdc4cd8
CI / test (push) Successful in 9m11s
CI / docker (push) Failing after 2m39s
CI / release (push) Skipped
Release / release (push) Canceled after 2m11s
chore: release v1.0.7
Playout: slate fallback, strict ffprobe validation, Prometheus metrics
Chat: history on join, DM offline delivery, env-config rate limits,
  Redis auto-reconnect, heartbeat, ban/mute enforcement, DM metrics
Viewer: presence indicators, schedule overlay, public /api/schedule
Admin: playlist drag-reorder, schedule overrides CRUD, user actions,
  inline mod buttons, CSV export, config editor, restart buttons,
  upload normalization gate, analytics time range
nginx: CORS preflight, JSON health, stub_status
Docs: test-lab vs production distinction across all configs/docs
Infra: Redis maxmemory, CD health IP-agnostic, slate volume mount
2026-08-06 17:41:52 +02:00

6.8 KiB
Raw Blame History

Deployment Guide

Environments

Linear Void runs in two separate environments:

Environment Host Docker Target Purpose
Test Lab 192.168.88.40 (internal) tcp://192.168.88.40:2375 Integration testing, staging, CI/CD validation
Production Hetzner VPS (TBD) tcp://<hetzner-ip>:2375 Public linearvoid.tv serving real viewers

Current state (2026-08-06): Test lab is active and receives automated CD deploys. Production Hetzner VPS is not yet provisioned. The CD workflow (cd.yml) and docker-compose.prod.yml currently target the test lab. Switching to production requires only changing DOCKER_HOST — see Production Deployment below.

Production Target (Hetzner)

Status: Not provisioned. The production deployment plan is documented and the pipeline is ready — only the VPS needs to be created.

Planned Infrastructure

Resource Spec
Provider Hetzner Cloud
Region Ashburn, VA (us-east)
Instance CX22 (2 vCPU, 4 GB RAM, 40 GB SSD) or equivalent
OS Ubuntu 24.04 LTS or Debian 12
Bandwidth 20 TB included traffic/month
Estimated cost ~€7–10/month base

Cutover Plan

  1. Provision Hetzner VPS (see TODO.md #92)
  2. Add production secrets to Gitea (CI_PROD_* prefix)
  3. Update DOCKER_HOST in CD workflow to Hetzner public IP
  4. Trigger deploy → validate health checks
  5. Update Cloudflare A record linearvoid.tv → Hetzner IP
  6. Enable HTTPS, WAF, cache rules on production zone
  7. Decommission test lab as public target

Firewall / Security Group

Port Source Purpose
22 Admin IPs SSH management
80 0.0.0.0/0 HTTP (redirect to HTTPS)
443 0.0.0.0/0 HTTPS (nginx)
2375 Gitea runner IP only Docker remote API (TLS)
9090 Admin IPs or internal Prometheus (or tunnel via SSH)
3001 Admin IPs or internal Grafana (or tunnel via SSH)

Pipeline Readiness

The Gitea Actions CD workflow (cd.yml) already supports DOCKER_HOST as a configurable variable. To switch targets:

# Test lab (current)
DOCKER_HOST=tcp://192.168.88.40:2375

# Production (when provisioned)
DOCKER_HOST=tcp://<hetzner-public-ip>:2375

No code changes needed in the pipeline. See TODO.md #91 and #92–#99 for full production deployment checklist.

Target Topology

  • Host: Linux server with Docker Engine
  • Reverse proxy: nginx container (linearvoid-nginx)
  • Core services: playout, chat, redis, postgres
  • Observability: prometheus, grafana
  • CDN/edge: Cloudflare

Prerequisites

  • Docker Engine + Docker Compose plugin
  • Network access to git.allucanget.biz
  • A valid .env file with production values
  • DNS configured for linearvoid.tv (and media.linearvoid.tv if split media domain is used)

Environment File

Use .env with at least:

  • CI_REGISTRY_URL
  • CI_REGISTRY_NAMESPACE
  • CHAT_PORT
  • NGINX_PORT
  • DATABASE_URL (if overridden)
  • REDIS_URL (if overridden)
  • ADMIN_USERNAME
  • ADMIN_PASSWORD
  • VITE_PUBLIC_APP_URL
  • VITE_PUBLIC_WS_URL
  • VITE_PUBLIC_HLS_URL
  • Cloudflare/R2 variables when media sync is enabled

See .env.example and SETUP.md for full variable reference.

Pull-and-Run Deployment

Images are built and pushed automatically by the CI and release workflows (see CONTRIBUTING.md for the release procedure).

Every push to main produces :main and :latest tags. Semantic version tags (v*) produce :X.Y.Z, :X.Y, and :latest tags.

From repository root:

docker login git.allucanget.biz

docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d --remove-orphans

First-Time or Recovery Provisioning

  1. Ensure host paths exist:
mkdir -p data/chat data/playlist data/media data/hls
  1. Confirm volumes and networks:
docker volume ls
docker network ls
  1. Start stack:
docker compose -f docker-compose.prod.yml up -d

Health Verification

After startup, check service status:

docker compose -f docker-compose.prod.yml ps

Endpoint checks:

  • App: http://{host}:8080/
  • Nginx health: http://{host}:8080/health
  • Chat health: http://{host}:3000/health
  • Playout health: http://{host}:8081/health
  • Prometheus: http://{host}:9090
  • Grafana: http://{host}:3001

Domain checks:

Zero/Low-Downtime Update Pattern

  1. Pull newest images.
  2. Recreate changed services only:
docker compose -f docker-compose.prod.yml pull

docker compose -f docker-compose.prod.yml up -d nginx chat playout
  1. Re-run health checks.

Rollback

If new images are unhealthy:

  1. Pin previous tags in docker-compose.prod.yml.
  2. Pull pinned tags.
  3. Restart impacted services.
docker compose -f docker-compose.prod.yml pull

docker compose -f docker-compose.prod.yml up -d nginx chat playout

Operational Tasks

View logs

docker compose -f docker-compose.prod.yml logs -f nginx chat playout

Restart one service

docker compose -f docker-compose.prod.yml restart chat

Stop stack

docker compose -f docker-compose.prod.yml down
# keep data
docker compose -f docker-compose.prod.yml down

# destructive (remove data)
docker compose -f docker-compose.prod.yml down -v

Nginx and Proxy Considerations

  • If an external proxy (for example Nginx Proxy Manager) fronts the stack, align request body limits with internal nginx.
  • For resumable admin uploads, ensure client_max_body_size supports chunk requests in both proxy layers.

Security Baseline

  • Use strong ADMIN_PASSWORD and rotate regularly.
  • Keep Docker host access restricted.
  • Prefer HTTPS/WSS through Cloudflare.
  • Restrict exposed ports if only proxy access is needed.

Common Deployment Pitfalls

  • Missing frontend build artifacts in local-only compose workflows.
  • Wrong registry credentials or missing login.
  • Misaligned domain env vars (app/ws/hls) causing mixed-origin errors.
  • Proxy-layer body size limits returning 413 on upload.
  • Health checks failing before service warmup is complete.