Files
linearvoid/docs/COOLIFY.md
T
zwitschi 9f60087a4c
CI / test (push) Successful in 1m39s
CI / docker (push) Successful in 5m25s
feat(docs): add Coolify deployment guide and integrate into setup documentation
feat(scripts): add environment verification scripts for deployment checks
2026-08-30 20:00:28 +02:00

12 KiB

Coolify Deployment

Last Updated: 2026-08-28

This guide describes an alternative deployment path for Linear Void using Coolify (v4.3.14) — an open-source, self-hostable PaaS that lets you manage servers, applications, and databases over SSH without vendor lock-in.

Coolify is a drop-in alternative to the two existing deployment flows described in SETUP.md:

  1. Local development (docker-compose.yml) — builds from source on your workstation.
  2. Test-lab / production (docker-compose.prod.yml) — pulls prebuilt images from the Gitea registry and is driven by the Gitea Actions CD workflow.

With Coolify, the same Docker Compose stack is deployed through a web UI and Coolify's own reverse proxy (Traefik), giving you automatic HTTPS, domain routing, and one-click redeploys without managing DOCKER_HOST manually.

Why use Coolify?

  • Single control plane: manage the playout, chat, nginx, redis, and db services (plus exporters and monitoring) from one dashboard.
  • Automatic TLS: Coolify provisions and renews Let's Encrypt certificates per application/domain, replacing the manual Cloudflare + nginx origin TLS dance for the app hostname.
  • No vendor lock-in: all configuration lives on your server. If you stop using Coolify, the running containers keep working; you only lose the automation.
  • Simpler cutover: when the Hetzner VPS is provisioned, point Coolify at the new server and redeploy instead of editing CD DOCKER_HOST values.

Note

: Coolify replaces the deployment orchestration layer only. Cloudflare DNS, the R2 media bucket, cache rules, and the edge Worker described in SETUP.md still apply and are unchanged.

Prerequisites

  • A Linux server running Docker Engine (any distro Coolify supports).
  • SSH access (root or a user with passwordless sudo) from the machine where you run the Coolify install — Coolify uses SSH to provision the target server.
  • At least 2 GB RAM / 20 GB disk for the Coolify instance itself (plus additional resources for the Linear Void stack).
  • A domain name pointed at the server for HTTPS (e.g. linearvoid.tv).
  • Ports 80 and 443 reachable on the server for Traefik.
  • The Gitea registry credentials (CI_REGISTRY_USER / CI_REGISTRY_TOKEN) if you intend to pull the prebuilt playout, chat, and nginx images.

Installation

Install Coolify on a dedicated server (or alongside your stack — but a dedicated Coolify server is the recommended layout):

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. Open the dashboard in your browser and complete first-run setup.

For non-interactive or offline installs, see the installation docs and the install script source.

Add the Linear Void server

  1. In the Coolify dashboard, go to Servers → Add.
  2. Choose your provider (or Localhost if Coolify runs on the same machine as the stack).
  3. Provide the SSH details. For a remote host, Coolify validates the connection and installs its agent.
  4. Verify the server shows as Reachable before creating resources.

Current topology (verified 2026-08-30)

The active test-lab setup is two-tier:

Component Host Environment Exposes Docker TCP Notes
Coolify dashboard 192.168.88.18 Coolify; docker container; coolify dashboard at 8000 https://coolify.allucanget.biz via Cloudflare tunnel (coolify-cloudflared) No Coolify manages target servers over SSH
Deployment target 192.168.88.40 Only Linear Void deploys here coolify-proxy = Traefik on 80/443, Port 8000 on this host is bound by Portainer, not Coolify. Yes Contains Coolify's per-server helpers (coolify-proxy, coolify-sentinel, coolify-cloudflared) plus the stack via docker-compose.prod.yml (the linearvoid compose project, 11 containers).

The main coolify app container does not run on 192.168.88.40; it lives on the dashboard host 192.168.88.18. When a Coolify deployment creates a Compose resource, the app services land on the target server 192.168.88.40.

Useful checks:

# Environment / deployment health on the target 192.168.88.40 (containers, helpers, health)
./scripts/check-env.ps1

# Credential / token access (Gitea registry, Cloudflare, R2, admin login)
./scripts/check-tokens.ps1

The Coolify dashboard runs on the 192.168.88.18 container host (port 8000, publicly https://coolify.allucanget.biz) and exposes no Docker TCP socket, so the environment/token scripts are intended to run against the deployment target 192.168.88.40, not 192.168.88.18.

Deploy the stack

Linear Void is a multi-service Docker Compose stack, so deploy it as a Compose (Docker Compose) resource rather than individual applications.

  1. Projects → Add → Docker Compose.
  2. Select the target server.
  3. In Build Settings, set the compose file source:
    • Git Repository — point Coolify at https://git.allucanget.biz/zwitschi/linearvoid.git and use docker-compose.prod.yml as the compose file. Coolify pulls on each redeploy.
    • Raw Compose — paste the contents of docker-compose.prod.yml directly if you prefer to manage it manually.
  4. If using the Git source, add the Gitea registry login as a Deployment Key / environment secret so the playout, chat, and nginx images can be pulled. Alternatively, switch those services to build: blocks (as in docker-compose.yml) so Coolify builds from source on the server.

Environment variables

Coolify injects a secret store into the compose resource. Map the Linear Void environment to Coolify's Environment Variables tab (these correspond to the values normally placed in .env):

Variable Purpose
DATABASE_URL postgres://linearvoid:<password>@db:5432/linearvoid?sslmode=disable
REDIS_URL redis://redis:6379/0
POSTGRES_PASSWORD Database password (used by the 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

The compose file uses named volumes (redis-data, postgres-data) and bind mounts under ./data (playlist, media, hls, chat, slate). Configure these under Persistent Storage so data survives redeploys:

Mount Purpose
data/playlist Playout playlist file
data/media Normalized source videos (read-only to playout)
data/hls Generated HLS manifests and segments
data/chat Chat service working data
data/slate Slate media
redis-data Redis append-only persistence
postgres-data PostgreSQL data directory

Create the data subdirectories before the first deploy if Coolify does not auto-create them (mkdir -p data/playlist data/media data/hls data/chat).

Domains and proxy

  1. Open the compose resource and assign the nginx service a public domain (e.g. linearvoid.tv). Coolify's Traefik reverse proxy then terminates TLS and forwards to the nginx container.
  2. Set the Ports Exposes mapping so Traefik targets the nginx service port (80 inside the container, mapped from ${NGINX_PORT:-8080}:80).
  3. Do not also expose the playout (8081) or chat (3000) health ports publicly — keep those internal to the compose network and let Traefik route through nginx.
  4. For the media domain (media.linearvoid.tv), keep serving HLS from the R2 custom domain (per SETUP.md) rather than routing it through Coolify, so segments stay on Cloudflare's edge cache.

Post-deploy verification

After the first deploy, confirm:

  • docker compose ps (on the server) shows all services healthy.
  • Playout health: https://<host>/health via nginx (internal :8081/health).
  • Chat health: internal :3000/health.
  • HLS is written to data/hls and served from media.linearvoid.tv.
  • Chat WebSocket connects over wss://linearvoid.tv/ws.
  • Admin login works at /admin with the configured credentials.

Monitoring

Coolify exposes container logs and basic metrics per resource. The stack also ships Prometheus + Grafana (monitoring/) and nginx/redis exporters. If you want those reachable through Coolify, expose Grafana on a restricted subdomain (e.g. grafana.linearvoid.tv) and keep Prometheus internal, mirroring the access guidance in SETUP.md.

Cutover from Gitea CD

Coolify can fully replace the Gitea Actions CD step. To switch:

  1. Deploy the stack as a Coolify compose resource (above).
  2. Point Cloudflare DNS for linearvoid.tv / www at the server IP (proxied).
  3. Disable (or keep as a fallback) the cd job in the Gitea workflow so the two orchestrators don't fight over the same DOCKER_HOST.
  4. Keep the Gitea CI (build + push images) job so Coolify continues pulling :latest images from the registry.

Troubleshooting

  • Image pull fails: ensure the Gitea registry credentials are set in the compose resource's secret store, or switch the services to build: blocks.
  • Healthchecks fail: confirm curl is available in the built images (the compose healthchecks rely on curl).
  • TLS errors: check the domain's DNS points at the Coolify server and ports 80/443 are open.
  • HLS not updating: verify data/hls is writable and the playout service restarts cleanly; see the normalization troubleshooting in SETUP.md.
  • Ports conflict: Coolify's Traefik binds 80/443; do not also map those to the host in the compose file.

Further reading