# Coolify Deployment **Last Updated**: 2026-08-28 This guide describes an 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 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](./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](./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): ```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. Open the dashboard in your browser and complete first-run setup. For non-interactive or offline installs, see the [installation docs](https://coolify.io/docs/installation) and the [install script source](https://github.com/coollabsio/coolify/blob/v4.3.14/scripts/install.sh). ## 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: ```powershell # 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:@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:///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 - Coolify repo (v4.3.14): - Coolify docs: - Linear Void deployment: [DEPLOYMENT.md](./DEPLOYMENT.md) - Linear Void setup: [SETUP.md](./SETUP.md)