Operación

Desplegar la nightly

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.

EspecificadoNo implementado
track/release-engineeringtrack/release-engineering/channelsdec-0123dec-0133track/release-engineering:RE5track/release-engineering:RE14

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 deploy de .github/workflows/nightly.yml y el script scripts/ci/coolify-deploy.ts (recurso nightly), 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 escribe styx render --runtime coolify; ninguno de los dos lee todavía STYX_IMAGE_* (ver Lo que falta). Los nombres de la UI y de la API son los de Coolify v4 y pueden cambiar.

Qué se despliega

PiezaQué es
CuándoTras el promote verde de cada nightly (nightly.yml: preflight → build → gates → verdict → promote → deploy). Un nightly en rojo no despliega nada
Qué bytesLos digests que exportó release-build.yml para ese candidato, los mismos que pasaron los gates. Nunca el tag móvil :nightly
VersiónEl 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
Registroghcr.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
AccesoPrivado 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
ComposeEl 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 llegaEl 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

El contrato con la aplicación de Coolify

Antes del webhook, el job fija en la aplicación:

  1. El commit que clona: 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.
  2. Las variables (PATCH /api/v1/applications/<uuid>/envs/bulk):
VariableValor
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_TRAINel tren del candidato
STYX_COMMITel 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.

Antes de empezar

  • El sitio de docs desplegado en Coolify (Desplegar la documentación): mismo Coolify, mismo proyecto styx, mismo token de API si quieres reutilizarlo.
  • La pila preparada como en Desplegar Styx en Coolify: secretos del host con 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.
  • Acceso de administrador a MKS2508/styx en GitHub (environments y secretos).
  • Acceso SSH al servidor de Coolify (para la credencial de pull).
  • Un compose de Coolify que lea 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.

1. Imágenes privadas en GHCR

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.

  • No cambies su visibilidad a pública mientras dure la alfa: el job deploy lo comprueba en cada nightly para cada imagen, su -bin y el árbol styx-release, en dos pasos:
    1. existe: con el 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?);
    2. es privado: pide a GHCR un token anónimo; si lo da y el manifiesto se sirve, el paquete es público y corta el despliegue con … se baja sin credenciales: el nightly es privado hasta la AppSec seca.
  • Los paquetes que publica 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.
  • Si algún día se decide publicarlas (decisión de waxin, tras la AppSec seca), la comprobación se levanta con la variable STYX_ALLOW_PUBLIC_IMAGES=1 en el paso del job.

2. Credencial de pull en el servidor de Coolify

Coolify baja las imágenes con el Docker del servidor, así que el login va en el servidor:

  1. 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.

  2. 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 bajar
  3. El token no va nunca en Coolify ni en el repositorio. Para rotarlo, repite el paso 2 con el nuevo.

3. Aplicación de Coolify

La aplicación es la de Desplegar Styx en Coolify, con estas diferencias:

  1. Proyecto styx → + New Environment nightly (separado del de docs: comparte host, no datos).
  2. El compose no construye: cada servicio con image: ${STYX_IMAGE_<COMPONENTE>:?} y ninguno con build: (el validate() de Coolify ya rechaza un build:).
  3. Raw Compose Deployment, como en ese runbook (DS8-01, deploy/README.md): en el modo normal Coolify mete su red <uuid> en todos los servicios y deshace las redes internas.
  4. Dominios con .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.
  5. Advanced → Auto Deploy: desactivado. Despliega el job, no los pushes.
  6. Recurso → Webhooks: copia el Deploy Webhook (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.

4. Token de API y environment de GitHub

  1. Coolify → Keys & Tokens → API Tokens → Create. Nombre: github-actions-nightly. Permisos: deploy, write (para fijar las variables) y read (para seguir el despliegue).
  2. GitHub → MKS2508/styx → Settings → Environments → New environment coolify-nightly:
    • Deployment branches and tags → Selected branches: master. El job sólo corre desde la nightly de master.
    • Environment secrets:
      • COOLIFY_TOKEN: el token del paso 1.
      • COOLIFY_NIGHTLY_WEBHOOK: el webhook del paso 3.
  3. Sin esos dos secretos el job termina en verde con COOLIFY_TOKEN / COOLIFY_NIGHTLY_WEBHOOK no configurados: no se despliega.

5. Primer despliegue

  1. Actions → nightly → Run workflow sobre master (con force si ya hubo un nightly verde de ese commit).
  2. El job 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>;
    • una línea STYX_IMAGE_…=ghcr.io/…@sha256:… por componente, más STYX_TRAIN y STYX_COMMIT;
    • despliegue <id> encolado, los estados de Coolify y finished.
  3. En Coolify, la aplicación tiene que tener ese commit, esas variables con esos valores, y los contenedores Running (healthy).

6. Verificación

Desde una máquina dentro de la tailnet:

curl -s https://styx.vpn.<tu-dominio>/version     # el tren y el commit del run

El 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.

Rollback

  • A un nightly anterior: GitHub → Actions → nightly → el run del nightly bueno → Re-run jobs → deploy. Un re-run de un job reutiliza las salidas de los jobs de los que depende en ese run, así que vuelve a fijar el commit y los digests de ese nightly (compose y bytes del mismo commit) y redespliega. Sigue pasando las comprobaciones de GHCR. Sin probar todavía, y GitHub sólo deja re-ejecutar runs recientes (la ventana la pone GitHub): para uno más viejo, el camino a mano.
  • A mano, sin GitHub: en la aplicación de Coolify, pon el commit (Git Commit SHA) y cada STYX_IMAGE_* en los valores que imprimió el job del nightly bueno (están en su log) y pulsa Redeploy.
  • El siguiente nightly verde vuelve a fijar sus digests. Para quedarse en uno, desactiva el job quitando COOLIFY_NIGHTLY_WEBHOOK del environment.

Dónde están los logs

QuéDónde
Por qué CI desplegó o noGitHub → Actions → nightly → el run → job deploy
Qué digests se fijaronEl mismo job: una línea STYX_IMAGE_… por componente
Pull y arranque de cada despliegueCoolify → aplicación → Deployments → la entrada
Salida de los serviciosCoolify → aplicación → Logs → el servicio
Por qué el nightly no llegó al promoteEl issue rodante nightly-red y el resumen del job verdict
Un deploy fallido tras el promoteEl mismo issue nightly-red: dice que el canal sí se movió

Problemas frecuentes

SíntomaCausa
deploy en verde sin desplegarFaltan COOLIFY_TOKEN / COOLIFY_NIGHTLY_WEBHOOK en el environment coolify-nightly
deploy no aparece en el runEl promote no terminó en verde (el nightly está en rojo: mira nightly-red)
… se baja sin credencialesAlguna imagen se ha hecho pública en GHCR (paso 1)
PATCH envs/bulk → HTTP 401/403El 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 publicadoEl build no llegó a publicar ese paquete (mira el job build / publish)
GHCR token … con GITHUB_TOKEN → HTTP 403El 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 saltaEse commit ya llegó al promote: re-run del job deploy de su run, no un nightly nuevo
PATCH envs/bulk → HTTP 404El 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ágenesFalta el docker login ghcr.io en el servidor o el token caducó (paso 2)
Coolify: required variable STYX_IMAGE_… is missingUn componente nuevo en la plantilla que este nightly no publicó, o variables borradas a mano
El tren no es nightly / no es de este commitEl build no salió de la nightly de master: el job lo rechaza antes de tocar Coolify

Lo que falta

  • Que el compose de Coolify lea las imágenes de 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.
  • Ningún nightly ha corrido: ni el build publicado, ni los gates, ni este despliegue (ticket #18).
  • Los runners de §9 Q8 de dec-0123 para las filas por runtime de la matriz.