# Coolify Deployment *Last Updated: 2026-08-28* Alternative deployment path for Linear Void using [Coolify](https://github.com/coollabsio/coolify/tree/v4.3.14) (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](./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](./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 `80` and `443` reachable for Traefik. - Gitea registry credentials (`CI_REGISTRY_USER` / `CI_REGISTRY_TOKEN`) if pulling prebuilt `playout`, `chat`, `nginx` images. ## Installation Recommended on a dedicated server: ```bash 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](https://coolify.io/docs/installation) and [install script source](https://github.com/coollabsio/coolify/blob/v4.3.14/scripts/install.sh). ## Add the Linear Void server 1. **Servers → Add**. 2. Choose your provider (or **Localhost** if Coolify runs on the same host as the stack). 3. Provide SSH details; for a remote host Coolify validates and installs its agent. 4. 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`: ```bash ./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): 1. **Projects → Add → Docker Compose**. 2. Select the target server. 3. In **Build Settings**, set the compose file source: - **Git Repository** → point at `https://git.allucanget.biz/zwitschi/linearvoid.git`, compose file `docker-compose.prod.yml` (Coolify pulls on each redeploy). - **Raw Compose** → paste `docker-compose.prod.yml` directly to manage manually. 4. For the Git source, add the Gitea registry login as a **Deployment Key / environment secret** so `playout`, `chat`, `nginx` images can be pulled. Alternatively switch those services to `build:` blocks (as in `docker-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:@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: ```bash mkdir -p data/playlist data/media data/hls data/chat ``` ### Domains and proxy 1. Assign the **nginx** service a public domain (e.g. `linearvoid.tv`); Traefik terminates TLS and forwards to `nginx`. 2. Set **Ports Exposes** so Traefik targets the nginx port (`80` inside container, mapped from `${NGINX_PORT:-8082}:80`). 3. Do **not** publicly expose playout (`8081`) or chat (`3000`) health ports — keep them internal and route through nginx. 4. 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 ps` on the server shows all services healthy. - Playout health: `https:///health` via nginx (internal `:8081/health`). - Chat health: internal `:3000/health`. - HLS written to `data/hls` and served from `media.linearvoid.tv`. - Chat WebSocket connects over `wss://linearvoid.tv/ws`. - Admin login works at `/admin` with 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 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 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 keeps pulling `:latest` from the registry. ## Troubleshooting - **Image pull fails**: set Gitea registry credentials in the compose secret store, or switch services to `build:` blocks. - **Healthchecks fail**: confirm `curl` exists in the built images (healthchecks rely on it). - **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 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): - Coolify docs: - [DEPLOYMENT.md](./DEPLOYMENT.md) · [SETUP.md](./SETUP.md)