Runbook para publicar este sitio en Coolify, privado por Tailscale con un dominio .vpn., con basic auth opcional, despliegue continuo detrás de CI verde, rollback y dónde mirar los logs.
Especificado. Todo lo que este runbook usa está en el repositorio: la imagen
docker/docs.Dockerfile, el composedeploy/coolify/docs.compose.ymly el workflow.github/workflows/deploy-docs.yml. La imagen se ha construido y sondeado en local y en CI (portada, búsqueda y healthcheck), pero el despliegue en Coolify no se ha hecho todavía: los pasos de la UI de Coolify son los de su v4 actual y pueden cambiar de nombre. Es un despliegue provisional del sitio dedec-0124. El adapter de Coolify detrack/release-engineering(#24) lo absorberá.
| Pieza | Qué es |
|---|---|
| Imagen | docker/docs.Dockerfile: SSR de Fumadocs sobre Bun en el puerto 3010, sin node_modules, usuario no root, HEALTHCHECK contra GET /health |
| Credenciales de build | Ninguna. El bun install de la imagen se limita a @styx/docs (--filter), que no depende de ningún SDK privado: Coolify construye desde el checkout sin SDK_READ_TOKEN |
| Compose de Coolify | deploy/coolify/docs.compose.yml: un servicio docs sin estado, read_only, sin capabilities y con tmpfs en /tmp. SERVICE_FQDN_DOCS_3010 le dice a Coolify a qué puerto enruta el dominio |
| Acceso | Privado. alpha.0 no se publica hasta que AppSec esté seco, y el sitio incluye los ADR internos. Lo da el dominio: con .vpn. en el nombre, el hook de Coolify de waxin limita el acceso a Tailscale. Basic auth, opcional |
| Despliegue continuo | deploy-docs.yml: cuando el workflow guard de un push a master termina en verde, llama al webhook de despliegue de Coolify y espera al resultado |
Hacer el sitio público es una decisión de waxin. Está en Hacerlo público.
docs.vpn.<tu-dominio>: el .vpn. activa el
hook de Coolify que restringe el acceso a Tailscale (dec-0133, P3).MKS2508/styx en GitHub, para instalar la GitHub App y crear secretos
de Actions.htpasswd (paquete apache2-utils) o Bun, para
generar el hash de la contraseña.styx. Si el proyecto ya existe, úsalo.production que Coolify crea, o crea uno con
+ New Environment (por ejemplo docs). La aplicación de la documentación no comparte red ni
datos con nada más, así que el entorno sólo sirve para ordenar.El repositorio es privado: Coolify lo clona con una GitHub App.
coolify-mks2508) y sigue el
asistente: Register Now te lleva a GitHub para crear la app.MKS2508/styx. No hace
falta ningún otro repositorio: la imagen no baja los SDK privados.MKS2508/styx.master.Docker Compose./deploy/coolify. Docker Compose Location: /docs.compose.yml.
/ como Base Directory. Coolify ejecuta compose con --project-directory
apuntando a la Base Directory, y compose resuelve build.context: ../.. contra ese
directorio. Con /, el contexto saldría del checkout y el build fallaría. Con
/deploy/coolify, ../.. es la raíz del repo, que es donde está docker/docs.Dockerfile.docs.docs.vpn.<tu-dominio> igual que los de tus otros dominios
.vpn. (si un comodín *.vpn.<tu-dominio> ya lo cubre, no hay nada que hacer).docs → Domains: https://docs.vpn.<tu-dominio>. Coolify puede haber
rellenado un dominio sslip.io a partir de SERVICE_FQDN_DOCS_3010: sustitúyelo. No añadas
:3010, porque la variable ya fija el puerto interno.https://, Traefik pide el certificado en la primera petición, como para el
resto de tus dominios .vpn..El dominio docs.vpn.<tu-dominio> del paso 4 ya basta: el hook de Coolify de waxin restringe a
Tailscale todo dominio con .vpn., así que fuera de la tailnet el sitio no responde. Cubre todo el
dominio, también /health y /api/search. No hay credenciales que crear ni variables que poner.
Lo único obligatorio en el recurso es esto:
styx-docs-auth con una etiqueta que lleva ${DOCS_BASIC_AUTH_USERS…}; si el escape
sigue marcado, Coolify convierte el $ en $$, compose no la sustituye y Traefik recibe un
middleware roto que tumba el router del dominio.Sin la variable DOCS_BASIC_AUTH_USERS, ese middleware es una cabecera X-Robots-Tag: noindex, nofollow inocua y no hay basic auth. Existe siempre porque Coolify lo engancha a sus routers
leyendo la etiqueta sin interpolar: si desapareciera, el router apuntaría a un middleware que no
existe.
Para una capa más encima de Tailscale, o si algún día el sitio va en un dominio sin .vpn..
Coolify trae un interruptor HTTP Basic Authentication, pero sólo genera las etiquetas para las
aplicaciones que no son Docker Compose; aquí va por la variable del compose. Las credenciales
nunca entran en git.
Genera el usuario y el hash bcrypt (no reutilices una contraseña):
htpasswd -nbB waxin '<contraseña>'
# sin htpasswd:
bun -e 'console.log("waxin:" + await Bun.password.hash(process.argv[1], { algorithm: "bcrypt" }))' '<contraseña>'La salida tiene la forma waxin:$2y$05$... (o $2b$). Para varios usuarios, sepáralos con
comas.
Recurso → Environment Variables → + Add:
DOCS_BASIC_AUTH_USERS. Value: la línea completa usuario:hash..env y
compose no interpreta los $ del hash.Redespliega. Con la variable, el middleware styx-docs-auth pasa a ser basicauth. Para
quitarla, borra la variable y redespliega.
Coolify añade al contenedor todas las variables del recurso (las vuelca en un
env_file). Aquí como mucho hay un hash bcrypt, que no es la contraseña. No añadas al recurso ningún token.
Con el auto-deploy de la GitHub App, Coolify desplegaría cada push a master aunque CI esté en rojo. El despliegue continuo va por CI, así que:
Deploy. El build tarda unos minutos: instala las dependencias del sitio y genera ~2.500 páginas MDX con Vite. El log se ve en vivo en Deployments.
Cuando termine, el contenedor tiene que estar Running (healthy). El healthcheck es el de la
imagen, GET /health cada 30 s.
Desde una máquina dentro de la tailnet:
curl -s https://docs.vpn.<tu-dominio>/health # {"status":"ok"}
curl -s -o /dev/null -w '%{http_code}\n' https://docs.vpn.<tu-dominio>/docs # 200
curl -s 'https://docs.vpn.<tu-dominio>/api/search?query=styx' | head -c 200 # JSON con resultadosCon basic auth, añade -u 'waxin:<contraseña>'; sin credenciales la respuesta es 401.
Desde una máquina fuera de la tailnet (por ejemplo, el móvil con Tailscale apagado), la misma URL no tiene que servir el sitio: la conexión no llega o el hook la rechaza.
En el navegador, desde la tailnet, https://docs.vpn.<tu-dominio> tiene que servir el sitio con
un certificado válido (y pedir usuario y contraseña si activaste la basic auth).
.github/workflows/deploy-docs.yml se dispara cuando termina el workflow guard de un push a
master. Sólo despliega si ha terminado en verde, y guard incluye el job docs-site, que
construye esta misma imagen y la sondea. El workflow hace tres cosas:
finished o failed. Si falla, el job
se pone en rojo.github-actions-docs. Permisos: deploy y
read. Con read, el workflow puede seguir el despliegue. Sin él, sólo lo encola y avisa.
Copia el token: Coolify no lo vuelve a mostrar.https://<tu-coolify>/api/v1/deploy?uuid=<uuid>&force=false.MKS2508/styx → Settings → Secrets and variables → Actions → New repository
secret:
COOLIFY_TOKEN: el token del paso 2.COOLIFY_DOCS_WEBHOOK: la URL del paso 3.master. El job tiene que terminar en
verde después de un deploy-docs: finished, y en Coolify aparece un despliegue nuevo.Con el Auto Deploy del paso 6 activado y sin secretos en GitHub, Coolify despliega en cuanto
recibe el push. Es más simple y más rápido, pero no espera a CI: un push que rompe el build o la
referencia generada llega igual a producción, y el fallo se ve en Coolify en vez de en el PR. Por
eso el despliegue por defecto es el de CI. Si algún día se cambia, desactiva deploy-docs.yml
(quita los secretos) para no desplegar dos veces.
git revert del commit malo y push a master. Cuando guard esté verde, CI
despliega la versión revertida.HEAD) → pon el SHA del último commit bueno y pulsa Redeploy. Mientras el campo tenga un
SHA, todos los despliegues, también los de CI, construyen ese commit. Cuando master tenga el
arreglo, devuélvelo a HEAD.| Qué | Dónde |
|---|---|
| Build y arranque de cada despliegue | Coolify → recurso → Deployments → la entrada |
| Salida del servidor (SSR, errores 5xx) | Coolify → recurso → Logs → servicio docs |
| Por qué CI desplegó o no | GitHub → Actions → deploy-docs → el run, paso «Webhook de despliegue de Coolify» |
| Router, certificado o 401 inesperado | Logs del contenedor coolify-proxy (Coolify → Servers → el servidor → Proxy → Logs) |
| Síntoma | Causa |
|---|---|
failed to read dockerfile o un contexto vacío | La Base Directory no es /deploy/coolify (paso 3) |
| El sitio responde fuera de la tailnet | El dominio no lleva .vpn. y el hook de Tailscale no se aplica (pasos 4 y 5) |
404 de Traefik o error del middleware styx-docs-auth | Escape special characters in labels sigue marcado (paso 5) |
| Siempre 401, también con la contraseña buena | Con basic auth: la variable no es Is Literal, o el escape de etiquetas sigue marcado (paso 5) |
| 404 de Traefik o certificado autofirmado | Dominio mal puesto en el servicio docs, o DNS que aún no resuelve (paso 4) |
deploy-docs en verde sin desplegar | Faltan COOLIFY_TOKEN/COOLIFY_DOCS_WEBHOOK, o master ya avanzó (el aviso del run lo dice) |
deploy-docs en rojo con HTTP 401/403 | Token caducado o sin permiso deploy, o API Access desactivado (paso 8) |
deploy-docs en rojo con HTTP 405 | El webhook no es el /api/v1/deploy?uuid=… del recurso |
Es una decisión de waxin, que depende de que AppSec esté seco para alpha.0. Para hacerlo: cambia
el dominio del servicio docs por uno sin .vpn. (por ejemplo docs.<tu-dominio>, con su DNS
apuntando al VPS y los puertos 80 y 443 abiertos para Let's Encrypt) y borra la variable
DOCS_BASIC_AUTH_USERS si la pusiste. El siguiente despliegue deja el sitio abierto; la etiqueta
del compose se queda, inocua. Ten en cuenta que el sitio incluye el texto íntegro de los ADR y la
referencia interna del data plane.
Actualizar y versiones
Cómo se versiona Styx (tren global y versión por componente), los canales nightly y stable, y cómo se actualiza y se revierte.
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.