8.3 KiB
Coolify Deployment
Last Updated: 2026-08-28
Alternative deployment path for Linear Void using Coolify (v4.3.14), an open-source self-hostable PaaS that manages servers, apps, and databases over SSH. Drop-in alternative to the flows in SETUP.md: local dev (docker-compose.yml) and test-lab/prod (docker-compose.prod.yml via Gitea Actions CD). Coolify deploys the same Compose stack through a web UI and its own Traefik reverse proxy for automatic HTTPS, domain routing, and one-click redeploys (no manual DOCKER_HOST).
Why Coolify
- Single control plane: manage playout, chat, nginx, redis, db (plus exporters/monitoring) from one dashboard.
- Automatic TLS: Let's Encrypt provisioning/renewal per app/domain, replacing the manual Cloudflare + nginx origin TLS setup for the app hostname.
- No vendor lock-in: config lives on your server; containers keep running if you stop using Coolify — you only lose automation.
- Simpler cutover: on Hetzner VPS provisioning, point Coolify at the new server and redeploy instead of editing CD
DOCKER_HOST.
Replaces the deployment orchestration layer only. Cloudflare DNS, R2 media bucket, cache rules, and the edge Worker from SETUP.md are unchanged.
Prerequisites
- Linux server with Docker Engine (any supported distro).
- SSH access (root or passwordless
sudo) from the Coolify install machine — Coolify provisions the target over SSH. - 2 GB RAM / 20 GB disk minimum for Coolify itself (plus resources for the stack).
- A domain pointed at the server for HTTPS (e.g.
linearvoid.tv). - Ports
80and443reachable for Traefik. - Gitea registry credentials (
CI_REGISTRY_USER/CI_REGISTRY_TOKEN) if pulling prebuiltplayout,chat,nginximages.
Installation
Recommended on a dedicated server:
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
The installer boots a Docker-based Coolify instance and prints the URL and default credentials. Complete first-run setup in the dashboard. For non-interactive/offline installs, see the installation docs and install script source.
Add the Linear Void server
- Servers → Add.
- Choose your provider (or Localhost if Coolify runs on the same host as the stack).
- Provide SSH details; for a remote host Coolify validates and installs its agent.
- Confirm the server shows Reachable before creating resources.
Current topology (verified 2026-08-30)
Active test-lab setup is two-tier:
| Component | Host | Exposes | Docker TCP | Notes |
|---|---|---|---|---|
| Coolify dashboard | 192.168.88.18 |
https://coolify.allucanget.biz via Cloudflare tunnel (coolify-cloudflared); dashboard at 8000 |
No | Manages target servers over SSH |
| Deployment target | 192.168.88.40 |
coolify-proxy = Traefik on 80/443; port 8000 bound by Portainer, not Coolify |
Yes | Per-server helpers (coolify-proxy, coolify-sentinel, coolify-cloudflared) + the stack via docker-compose.prod.yml (linearvoid compose project, 11 containers) |
The main coolify app container runs only on the dashboard host 192.168.88.18; Compose-resource services land on the target 192.168.88.40. The dashboard host exposes no Docker TCP socket, so run the checks below against the target 192.168.88.40:
./scripts/check-env.sh # env/deployment health on target (containers, helpers, health)
./scripts/check-tokens.sh # Gitea registry, Cloudflare, R2, admin credential access
Deploy the stack
Deploy as a Docker Compose resource (not individual apps):
- Projects → Add → Docker Compose.
- Select the target server.
- In Build Settings, set the compose file source:
- Git Repository → point at
https://git.allucanget.biz/zwitschi/linearvoid.git, compose filedocker-compose.prod.yml(Coolify pulls on each redeploy). - Raw Compose → paste
docker-compose.prod.ymldirectly to manage manually.
- Git Repository → point at
- For the Git source, add the Gitea registry login as a Deployment Key / environment secret so
playout,chat,nginximages can be pulled. Alternatively switch those services tobuild:blocks (as indocker-compose.yml) to build from source on the server.
Environment variables
Map Linear Void's .env values into Coolify's Environment Variables tab:
| Variable | Purpose |
|---|---|
DATABASE_URL |
postgres://linearvoid:<password>@db:5432/linearvoid?sslmode=disable |
REDIS_URL |
redis://redis:6379/0 |
POSTGRES_PASSWORD |
DB password (used by db service) |
ADMIN_USERNAME / ADMIN_PASSWORD |
Admin panel credentials |
ADMIN_SESSION_TTL_HOURS |
Admin session lifetime |
R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY |
Cloudflare R2 credentials |
R2_BUCKET_NAME / R2_ENDPOINT / R2_REGION |
R2 bucket config (R2_REGION=auto) |
MEDIA_DOMAIN |
media.linearvoid.tv |
VITE_PUBLIC_APP_URL |
https://linearvoid.tv |
VITE_PUBLIC_WS_URL |
wss://linearvoid.tv/ws |
VITE_PUBLIC_HLS_URL |
https://media.linearvoid.tv/hls/playlist.m3u8 |
Keep internal service URLs on compose hostnames (redis, db) exactly as in docker-compose.prod.yml.
Persistent storage
Named volumes (redis-data, postgres-data) and bind mounts under ./data must be configured under Persistent Storage to survive redeploys:
| Mount | Purpose |
|---|---|
data/playlist |
Playout playlist file |
data/media |
Normalized source videos (read-only to playout) |
data/hls |
Generated HLS manifests/segments |
data/chat |
Chat service working data |
data/slate |
Slate media |
redis-data |
Redis append-only persistence |
postgres-data |
PostgreSQL data directory |
If Coolify doesn't auto-create the dirs, create them before first deploy:
mkdir -p data/playlist data/media data/hls data/chat
Domains and proxy
- Assign the nginx service a public domain (e.g.
linearvoid.tv); Traefik terminates TLS and forwards tonginx. - Set Ports Exposes so Traefik targets the nginx port (
80inside container, mapped from${NGINX_PORT:-8082}:80). - Do not publicly expose playout (
8081) or chat (3000) health ports — keep them internal and route through nginx. - For
media.linearvoid.tv, keep serving HLS from the R2 custom domain (per SETUP.md) rather than routing through Coolify, so segments stay on Cloudflare's edge cache.
Post-deploy verification
docker compose pson the server shows all services healthy.- Playout health:
https://<host>/healthvia nginx (internal:8081/health). - Chat health: internal
:3000/health. - HLS written to
data/hlsand served frommedia.linearvoid.tv. - Chat WebSocket connects over
wss://linearvoid.tv/ws. - Admin login works at
/adminwith configured credentials.
Monitoring
Coolify exposes container logs/basic metrics per resource. The stack ships Prometheus + Grafana (monitoring/) plus nginx/redis exporters. To reach them via Coolify, expose Grafana on a restricted subdomain (e.g. grafana.linearvoid.tv) and keep Prometheus internal, mirroring SETUP.md access guidance.
Cutover from Gitea CD
- Deploy the stack as a Coolify compose resource (above).
- Point Cloudflare DNS for
linearvoid.tv/wwwat the server IP (proxied). - Disable (or keep as fallback) the
cdjob in the Gitea workflow so the two orchestrators don't fight over the sameDOCKER_HOST. - Keep the Gitea CI (build + push images) job so Coolify keeps pulling
:latestfrom the registry.
Troubleshooting
- Image pull fails: set Gitea registry credentials in the compose secret store, or switch services to
build:blocks. - Healthchecks fail: confirm
curlexists in the built images (healthchecks rely on it). - TLS errors: check the domain's DNS points at the Coolify server and ports
80/443are open. - HLS not updating: verify
data/hlsis writable and playout restarts cleanly; see SETUP.md normalization troubleshooting. - Ports conflict: Coolify's Traefik binds
80/443; do not also map those to the host in the compose file.
Further reading
- Coolify repo (v4.3.14): https://github.com/coollabsio/coolify/tree/v4.3.14
- Coolify docs: https://coolify.io/docs
- DEPLOYMENT.md · SETUP.md