Refactor load testing scripts to support Bash and PowerShell
- Updated README.md to reflect changes in load testing commands and added usage examples for Bash scripts. - Converted PowerShell load testing scripts to Bash equivalents for better cross-platform compatibility. - Added new Bash scripts: run-loadtests.sh, summarize-artillery.sh, and summarize-k6.sh to mirror existing PowerShell functionality. - Updated admin-api.js and ws-chat.yml for consistency and improved performance metrics reporting. - Enhanced error handling and output formatting in summary scripts for better readability.
This commit is contained in:
1 parent
9f60087a4c
commit
33b6296c5e
82 files changed
+19682
-21133
No files matched your search
+79
-160
@@ -1,229 +1,148 @@
|
||||
# Coolify Deployment
|
||||
|
||||
**Last Updated**: 2026-08-28
|
||||
*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.
|
||||
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`).
|
||||
|
||||
Coolify is a drop-in alternative to the two existing deployment flows described
|
||||
in [SETUP.md](./SETUP.md):
|
||||
## Why Coolify
|
||||
|
||||
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.
|
||||
- **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`.
|
||||
|
||||
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.
|
||||
> 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
|
||||
|
||||
- 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.
|
||||
- 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
|
||||
|
||||
Install Coolify on a dedicated server (or alongside your stack — but a dedicated
|
||||
Coolify server is the recommended layout):
|
||||
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. 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).
|
||||
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. 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.
|
||||
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)
|
||||
|
||||
The active test-lab setup is **two-tier**:
|
||||
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). |
|
||||
| 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 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`.
|
||||
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`:
|
||||
|
||||
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
|
||||
```bash
|
||||
./scripts/check-env.sh # env/deployment health on target (containers, helpers, health)
|
||||
./scripts/check-tokens.sh # Gitea registry, Cloudflare, R2, admin credential access
|
||||
```
|
||||
|
||||
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.
|
||||
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 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.
|
||||
- **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
|
||||
|
||||
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`):
|
||||
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` | 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` |
|
||||
| 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`.
|
||||
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:
|
||||
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 and segments |
|
||||
| `data/chat` | Chat service working data |
|
||||
| `data/slate` | Slate media |
|
||||
| `redis-data` | Redis append-only persistence |
|
||||
| `postgres-data` | PostgreSQL data directory |
|
||||
| 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 |
|
||||
|
||||
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`).
|
||||
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. 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.
|
||||
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:-8080}: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
|
||||
|
||||
After the first deploy, confirm:
|
||||
|
||||
- `docker compose ps` (on the server) shows all services healthy.
|
||||
- `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`.
|
||||
- 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 the configured credentials.
|
||||
- Admin login works at `/admin` with 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.
|
||||
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
|
||||
|
||||
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.
|
||||
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**: 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.
|
||||
- **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): <https://github.com/coollabsio/coolify/tree/v4.3.14>
|
||||
- Coolify docs: <https://coolify.io/docs>
|
||||
- Linear Void deployment: [DEPLOYMENT.md](./DEPLOYMENT.md)
|
||||
- Linear Void setup: [SETUP.md](./SETUP.md)
|
||||
- [DEPLOYMENT.md](./DEPLOYMENT.md) · [SETUP.md](./SETUP.md)
|
||||
Reference in new issue
Block a user