Runbook de la nightly privada de master en Coolify — imágenes privadas en GHCR, credencial de pull en el host, aplicación de Coolify que fija las imágenes por digest, secretos del environment coolify-nightly, verificación, rollback y logs.
Especificado. El camino está en el repositorio, pero ningún nightly ha corrido todavía y la aplicación de Coolify no existe. Lo que hay: el job
deployde.github/workflows/nightly.ymly el scriptscripts/ci/coolify-deploy.ts(recursonightly), probado con un Coolify y un GHCR falsos y, en local, con la comprobación de privacidad contra GHCR real. Falta una pieza para el extremo a extremo: el compose de Coolify de Desplegar Styx en Coolify (ticket #24) construye las imágenes desde el clon, o lleva los digests literales que escribestyx render --runtime coolify; ninguno de los dos lee todavíaSTYX_IMAGE_*(ver Lo que falta). Los nombres de la UI y de la API son los de Coolify v4 y pueden cambiar.
| Pieza | Qué es |
|---|---|
| Cuándo | Tras el promote verde de cada nightly (nightly.yml: preflight → build → gates → verdict → promote → deploy). Un nightly en rojo no despliega nada |
| Qué bytes | Los digests que exportó release-build.yml para ese candidato, los mismos que pasaron los gates. Nunca el tag móvil :nightly |
| Versión | El tren nightly de dec-0133: <base>.<YYYYMMDD>.<run>+g<sha12>.clean (o un 0.0.0-alpha.N cortado, que viaja por la nightly). Va en STYX_TRAIN y en el /version de cada componente |
| Registro | ghcr.io/<owner>/styx-<componente>, privado: el job comprueba antes de desplegar que cada paquete del candidato (imagen, su -bin y el árbol styx-release) existe y no se baja sin credenciales |
| Acceso | Privado hasta la AppSec seca (dec-0133): dominio con .vpn. (el hook de Coolify de waxin lo limita a Tailscale), igual que el sitio de docs |
| Compose | El del mismo commit que el nightly: el job fija el commit que clona la aplicación (git_commit_sha), no la cabeza de master del momento |
| Cómo llega | El job fija el commit y las variables en la aplicación de Coolify y dispara su webhook; Coolify baja las imágenes por digest y levanta el compose |
Antes del webhook, el job fija en la aplicación:
PATCH /api/v1/applications/<uuid> con git_commit_sha=<STYX_COMMIT>. La
aplicación es Git (compose en /deploy); sin esto Coolify clonaría la cabeza de master en el
momento del webhook, y si master se movió tras el preflight, los digests de este nightly
arrancarían con el compose, las redes y las migraciones de otro commit.PATCH /api/v1/applications/<uuid>/envs/bulk):| Variable | Valor |
|---|---|
STYX_IMAGE_<COMPONENTE> | ghcr.io/<owner>/styx-<componente>@sha256:…, una por componente publicado. El nombre sale del componente en mayúsculas con - → _: catalog-svc → STYX_IMAGE_CATALOG_SVC, media-daemon → STYX_IMAGE_MEDIA_DAEMON |
STYX_TRAIN | el tren del candidato |
STYX_COMMIT | el commit de master (40 hex) |
El compose de la aplicación usa image: ${STYX_IMAGE_CATALOG_SVC:?} y así para cada servicio: sin
la variable, compose se niega a arrancar en vez de usar otra imagen. Si el job no puede fijar el
commit o las variables (token sin permiso write, campo rechazado), falla y no dispara el
webhook: desplegar sin ellos repetiría el nightly anterior, o mezclaría commits, en silencio. El
campo git_commit_sha y el formato de envs/bulk salen de la API de Coolify v4 y no se han
probado contra una instancia.
styx, mismo token de API si quieres reutilizarlo.deploy:coolify-secrets, Base Directory /deploy, styx-edge con el proxy en
10.254.254.2 y el cortafuegos del host. Este runbook sólo añade lo propio de la nightly.MKS2508/styx en GitHub (environments y secretos).STYX_IMAGE_* (ver Lo que falta). Hasta
entonces todo lo de abajo se puede preparar, y el job sale en verde con aviso mientras falten
los secretos.release-build.yml publica en ghcr.io/<owner>/ con el GITHUB_TOKEN. Con el repositorio
privado, cada paquete nuevo nace privado y enlazado al repositorio.
deploy lo comprueba en
cada nightly para cada imagen, su -bin y el árbol styx-release, en dos pasos:
GITHUB_TOKEN del job (packages: read) el manifiesto se sirve. GHCR
contesta 403 DENIED igual a un paquete privado que a uno que no existe, así que sin este
paso "anónimo denegado" no prueba nada. Si falla: … no se sirve con credenciales: no publicado, o HTTP 403 (¿falta packages: read o el acceso del repo al paquete?);… se baja sin credenciales: el nightly es privado hasta la AppSec seca.release-build.yml de este repositorio ya dan acceso de lectura a sus
Actions. Si uno se creó a mano, añade el repositorio en Package settings → Manage Actions
access.STYX_ALLOW_PUBLIC_IMAGES=1 en el paso del job.Coolify baja las imágenes con el Docker del servidor, así que el login va en el servidor:
GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic) →
Generate new token (classic). Nombre: coolify-ghcr-pull. Scope: sólo read:packages.
Caducidad: la que uses para tus otras credenciales. GHCR no acepta los tokens fine-grained.
En el servidor, como el usuario con el que Coolify habla con Docker (normalmente root):
echo '<token>' | docker login ghcr.io -u <tu-usuario-de-github> --password-stdin
docker pull ghcr.io/<owner>/styx-catalog-svc:nightly # tras el primer promote: tiene que bajarEl token no va nunca en Coolify ni en el repositorio. Para rotarlo, repite el paso 2 con el nuevo.
La aplicación es la de Desplegar Styx en Coolify, con estas diferencias:
styx → + New Environment nightly (separado del de docs: comparte host, no
datos).image: ${STYX_IMAGE_<COMPONENTE>:?} y ninguno con
build: (el validate() de Coolify ya rechaza un build:).deploy/README.md): en el modo normal
Coolify mete su red <uuid> en todos los servicios y deshace las redes internas..vpn. en las labels de Traefik (STYX_PUBLIC_HOST, por ejemplo
styx.vpn.<tu-dominio>): el hook de Tailscale los limita a la tailnet. Ningún puerto de
Postgres, Valkey o NATS se publica.https://<tu-coolify>/api/v1/deploy?uuid=<uuid>&force=false). El uuid es el de la
aplicación: el job lo usa también para fijar sus variables.github-actions-nightly. Permisos:
deploy, write (para fijar las variables) y read (para seguir el despliegue).MKS2508/styx → Settings → Environments → New environment coolify-nightly:
master. El job sólo corre desde la
nightly de master.COOLIFY_TOKEN: el token del paso 1.COOLIFY_NIGHTLY_WEBHOOK: el webhook del paso 3.COOLIFY_TOKEN / COOLIFY_NIGHTLY_WEBHOOK no configurados: no se despliega.master (con force si ya hubo un nightly verde de
ese commit).deploy tiene que imprimir, en este orden:
deploy-nightly: N paquetes publicados y privados (con credenciales se sirven, sin ellas no);git_commit_sha=<commit del nightly>;STYX_IMAGE_…=ghcr.io/…@sha256:… por componente, más STYX_TRAIN y STYX_COMMIT;despliegue <id> encolado, los estados de Coolify y finished.Desde una máquina dentro de la tailnet:
curl -s https://styx.vpn.<tu-dominio>/version # el tren y el commit del runEl tren y el commit tienen que ser los de STYX_TRAIN y STYX_COMMIT del job. En el servidor,
tras cada despliegue, el guard del endurecimiento vivo:
bun run check:infra-live -- --project <proyecto compose de la aplicación>Desde fuera de la tailnet la URL no tiene que servir nada. Los runners de GitHub tampoco
llegan (el dominio es privado): por eso el job sólo verifica lo que Coolify le dice
(finished), no el /version.
STYX_IMAGE_* en los valores que imprimió el job del nightly bueno (están en su log) y pulsa
Redeploy.COOLIFY_NIGHTLY_WEBHOOK del environment.| Qué | Dónde |
|---|---|
| Por qué CI desplegó o no | GitHub → Actions → nightly → el run → job deploy |
| Qué digests se fijaron | El mismo job: una línea STYX_IMAGE_… por componente |
| Pull y arranque de cada despliegue | Coolify → aplicación → Deployments → la entrada |
| Salida de los servicios | Coolify → aplicación → Logs → el servicio |
| Por qué el nightly no llegó al promote | El issue rodante nightly-red y el resumen del job verdict |
| Un deploy fallido tras el promote | El mismo issue nightly-red: dice que el canal sí se movió |
| Síntoma | Causa |
|---|---|
deploy en verde sin desplegar | Faltan COOLIFY_TOKEN / COOLIFY_NIGHTLY_WEBHOOK en el environment coolify-nightly |
deploy no aparece en el run | El promote no terminó en verde (el nightly está en rojo: mira nightly-red) |
… se baja sin credenciales | Alguna imagen se ha hecho pública en GHCR (paso 1) |
PATCH envs/bulk → HTTP 401/403 | El token no tiene permiso write, o API Access está desactivado (paso 4) |
PATCH application git_commit_sha → HTTP … | Igual que la fila anterior; un 422 es que esta versión de Coolify no acepta el campo |
… no se sirve con credenciales: no publicado | El build no llegó a publicar ese paquete (mira el job build / publish) |
GHCR token … con GITHUB_TOKEN → HTTP 403 | El paquete no da acceso de lectura a las Actions del repositorio (paso 1) |
el tren 0.0.0-alpha.N es del commit … | El tag v0.0.0-alpha.N no apunta al commit del nightly |
deploy falló y el siguiente nightly se salta | Ese commit ya llegó al promote: re-run del job deploy de su run, no un nightly nuevo |
PATCH envs/bulk → HTTP 404 | El uuid del webhook no es el de una aplicación (¿un servicio de Coolify?) |
HTTP 599 red: … | El runner no llega a Coolify: IPs de la API restringidas o dominio que no resuelve |
Coolify: unauthorized / denied al bajar imágenes | Falta el docker login ghcr.io en el servidor o el token caducó (paso 2) |
Coolify: required variable STYX_IMAGE_… is missing | Un componente nuevo en la plantilla que este nightly no publicó, o variables borradas a mano |
| El tren no es nightly / no es de este commit | El build no salió de la nightly de master: el job lo rechaza antes de tocar Coolify |
STYX_IMAGE_*. Hoy styx render --runtime coolify escribe los digests del manifiesto en literal y deploy/docker-compose.apps.yml
construye. La propuesta es que el render emita
image: ${STYX_IMAGE_<COMPONENTE>:-<repo>@<digest del manifiesto>}: el mismo compose sirve
pegado a mano (digests del manifiesto) y desde la nightly (digests del build). Es del adapter
Coolify (ticket #24) y está sin decidir.dec-0123 para las filas por runtime de la matriz.Desplegar styx en Coolify
Runbook de la pila completa de styx como aplicación Raw Compose de Coolify — compose de un solo fichero, secretos como ficheros del host con un one-shot y su smoke, Base Directory, dominios .vpn. con TLS del proxy y acceso sólo desde la tailnet (proxy y QUIC), infraestructura sin exponer, verificación, actualización y rollback.
Seguir una reproducción por todos los servicios
Cómo encontrar la traza de una reproducción y saltar a los logs de cada servicio (navegador, BFF, playback-svc, catalog-svc por el bus, daemon por el socket), qué hace el collector con lo que recibe (autenticación, entorno, redacción), el visor local otel-lgtm y la prueba e2e trace-proof que lo demuestra con una reproducción real.