Repository navigation
ci cd y despliegue
Esta página acompaña a la guía de estudio; allí están el mapa general y la ruta recomendada. Aquí no se estudia Angular: se explica cómo llega todo lo que se escribe hasta internet (las tres aplicaciones y esta misma wiki) y cómo está montado. Sirve para repasar y para contarlo en una entrevista o en una charla técnica.
Hay dos repositorios, tres aplicaciones, una wiki privada, otra pública y un sitio web de la wiki. Si no se ve el conjunto, parece magia: se edita un archivo y minutos después cambia una página en internet. Este módulo pone todas las piezas en un solo dibujo.
| Repositorio | Visibilidad | Qué contiene |
|---|---|---|
Reskyon/angular-ufh |
Privado | El código de los proyectos (01-… a 04-…), la GitHub Action mirror-wiki.yml y su wiki privada, que es la fuente de verdad del contenido |
Reskyon/angular-wiki |
Público | Una copia de sólo lectura de la wiki, más lo necesario para construir el sitio web: Dockerfile, wiki-ref y la carpeta site/
|
Reskyon/angular-ufh (privado)
├── 01-typescript-intro/ no se despliega (se ve en la consola)
├── 02-bases/ ──► angular-basics.reskyon.com
├── 03-gifs-app/ ──► angular-gifs.reskyon.com
├── 04-country-app/ ──► angular-country.reskyon.com
├── .github/workflows/
│ └── mirror-wiki.yml copia la wiki y avisa al sitio
└── (wiki privada) Reskyon/angular-ufh.wiki ← se edita aquí
Reskyon/angular-wiki (público)
├── (wiki pública) copia de sólo lectura
├── Dockerfile lo usa Coolify
├── wiki-ref commit de la wiki que hay que publicar
└── site/
├── mkdocs.yml configuración del sitio y del tema
├── nginx.conf cómo se sirve
└── scripts/prepare.py convierte la wiki al formato de MkDocs
──► angular-wiki.reskyon.com
┌──────────────────────────────┐
tú (VS Code, git) ──► │ Reskyon/angular-ufh (main) │
└───────┬──────────────┬───────┘
│ │
push a main │ │ editas la wiki privada
▼ ▼
┌─────────────┐ ┌──────────────────────┐
│ Coolify │ │ GitHub Action │
│ (3 apps) │ │ mirror-wiki.yml │
└──────┬──────┘ └─────┬──────────┬─────┘
│ │ │
│ ▼ ▼
│ wiki pública wiki-ref en
│ (copia, sólo Reskyon/angular-wiki
│ lectura) (commit a main)
│ │
▼ ▼
angular-basics / -gifs / ┌─────────────┐
-country .reskyon.com │ Coolify │
│ (sitio wiki)│
└──────┬──────┘
▼
angular-wiki.reskyon.com
Dos flujos independientes, que sólo comparten dónde viven los archivos:
-
El código (
pushamain) lo despliega Coolify directamente. -
El contenido (editar la wiki) lo copia una GitHub Action y, al
cambiar
wiki-ref, Coolify reconstruye el sitio.
| Término | Qué es | Dónde ocurre en este proyecto |
|---|---|---|
| CI (integración continua) | Verificar cada cambio automáticamente (compilar, probar, revisar estilo) |
El build en Coolify compila la app y la wiki (--strict). No hay todavía una etapa de pruebas ni de lint automática |
| CD (despliegue continuo) | Publicar solo, sin pasos manuales, lo que está en la rama principal |
Coolify reconstruye y publica en cada push a main
|
| Automatización de contenido | Mover y transformar archivos entre repositorios |
La GitHub Action mirror-wiki.yml y el script prepare.py
|
Ser honesto con esta tabla es lo que más se valora en una entrevista: el CD existe y funciona; la parte de CI se apoya en que el build falla si algo está mal, y todavía no corre pruebas.
Respuesta:
Por visibilidad y por responsabilidad. El de estudio es privado y contiene el código y la fuente de la wiki. El público existe para exponer el contenido (copia de sólo lectura) y para alojar lo que se necesita para construir el sitio web. Así se publica la teoría sin abrir el código de los ejercicios.
Respuesta:
En la wiki privada de Reskyon/angular-ufh. La pública se sobrescribe con
cada copia (push --force), por lo que cualquier edición hecha directamente en
ella se pierde. Una sola fuente de verdad evita que dos copias diverjan.
Quieres escribir en la wiki de siempre (Markdown, desde el editor) y que el sitio web se actualice solo, sin entrar a ningún panel. Además, sin que la copia pública pueda quedar desactualizada ni que un enlace roto llegue a producción.
1 Editas una página de la wiki privada
│ (git push a Reskyon/angular-ufh.wiki, o desde la web de GitHub)
▼
2 GitHub dispara la Action "Mirror wiki"
│ por: evento gollum | ejecución manual | cron diario 06:17 UTC
▼
3 Job "mirror" (ubuntu-latest)
├─ a) clona la wiki privada (token GITHUB_TOKEN)
├─ b) push --force a la wiki pública (secreto WIKI_MIRROR_TOKEN)
└─ c) escribe el commit en wiki-ref de Reskyon/angular-wiki
├─ si wiki-ref ya tiene ese commit → no hace nada
└─ si es distinto → commit "chore: publish wiki <sha7>" a main
▼
4 Coolify detecta el push a main de Reskyon/angular-wiki
▼
5 docker build (Dockerfile del repositorio público)
├─ etapa 1: python:3.13-slim + git + mkdocs 1.6.1 + mkdocs-material 9.7.7
│ git clone de la wiki pública ──► git checkout <wiki-ref>
│ python scripts/prepare.py /wiki docs
│ mkdocs build --strict
└─ etapa 2: nginx:alpine ◄── copia sólo el HTML generado
▼
6 Contenedor nuevo ──► angular-wiki.reskyon.com
(si el build falla, queda publicada la versión anterior)
| Pieza | Para qué sirve |
|---|---|
on: gollum |
Evento de GitHub cuando se crea o edita una página de la wiki |
workflow_dispatch |
Permite lanzar la Action a mano desde la pestaña Actions |
schedule (cron) |
Red de seguridad diaria: por si una edición por git push no disparó gollum
|
concurrency con cancel-in-progress: false
|
La ejecución en curso no se cancela: la nueva espera a que termine |
WIKI_MIRROR_TOKEN |
Secreto con permiso de escritura sobre el repositorio público; GITHUB_TOKEN sólo alcanza para leer el privado |
wiki-ref |
Un archivo con un solo commit: la versión exacta de la wiki que debe tener el sitio |
El sitio se construye a partir de la wiki, pero Coolify sólo sabe reaccionar
a cambios en el repositorio (no en su wiki). wiki-ref es el puente:
la Action escribe ahí el commit de la wiki, eso es un push a main del
repositorio, y Coolify lo ve y reconstruye.
Dos ventajas:
- Es idempotente: si el archivo ya contiene ese commit, la Action no crea ningún commit nuevo y no hay un despliegue de más.
-
Es reproducible: el sitio siempre se construye desde un commit concreto
de la wiki (
git checkout <wiki-ref>), no desde "lo último que haya".
Se puede ver funcionando: el wiki-ref actual de Reskyon/angular-wiki
coincide con el último commit de la wiki privada.
La wiki de GitHub y MkDocs no hablan el mismo idioma. El script los traduce, y sólo corre en la etapa de construcción:
/wiki (Markdown de la wiki) docs/ (lo que lee MkDocs)
────────────────────────── ─────────────────────────
Home.md ──► index.md
study-guide.md ──► study-guide.md
01-typescript-intro.md ──► 01-typescript-intro.md
...
_Sidebar.md, _Footer.md ──► (no se publican)
(02-bases#ancla) ──► (02-bases.md#ancla)
Además, reescribe el bloque nav: de mkdocs.yml:
nav:
- Inicio ← fijo (index.md)
- Guía de estudio ← fijo
- CI/CD y despliegue ← página extra declarada en el script (EXTRA)
- <H1 de 01-…> ┐
- <H1 de 02-…> │ una entrada por cada página NN-nombre.md,
- <H1 de 03-…> │ ordenada por número, con su primer "# "
- ... ┘ como título
- Hoja de atajos ← fijo (al final)
Consecuencias prácticas:
-
Una página nueva con nombre
NN-nombre.mdaparece sola en el menú, ordenada por su número. -
Las páginas que no son una parte del curso (como esta,
ci-cd-y-despliegue) se declaran en una lista del script (EXTRA) y salen entre la Guía de estudio y la Parte 1. Si la página no existe en la wiki, simplemente no entra. - Una página con otro nombre, que no está en esa lista, hace fallar el build (el script lo detiene a propósito para que nadie publique una página que no sale en el menú).
- Una página sin título
#también falla: no tendría texto para el menú. - El contenido de las páginas no se modifica; sólo cambian los enlaces entre páginas.
mkdocs build --strict convierte cualquier aviso en error: un enlace a una
página que no existe, o a un ancla que no existe, rompe el build. El
resultado práctico es que un error en la wiki nunca llega a producción: el
despliegue falla y Coolify deja la versión anterior.
Esto ya ocurrió: un enlace con un ancla mal escrita hizo fallar el despliegue, y el sitio siguió sirviendo la versión previa hasta corregirlo.
-
Editar la wiki pública a mano: se pierde en la siguiente copia
(
push --force). -
Crear una página con un nombre fuera del patrón (
notas.md): el build falla con la lista de páginas que no están en el menú. -
Un ancla mal escrita: las anclas siguen las reglas de GitHub (minúsculas,
sin signos de puntuación, espacios a guiones).
input.requiredpasa ainputrequired. -
Cambiar la versión de MkDocs sin revisar Material: MkDocs 2 todavía no es
compatible con el tema, por eso el
Dockerfilefijamkdocs==1.6.1. - Esperar un cambio al instante: son varios pasos (copia, commit, build); tarda unos minutos.
Editar la wiki privada dispara una Action que la copia a la pública y escribe el
commit en wiki-ref. Ese cambio en main hace que Coolify construya una imagen:
descarga la wiki en ese commit, prepare.py la adapta y regenera el menú,
mkdocs build --strict la convierte en HTML y Nginx la sirve. Si algo no
cuadra, el build falla y el sitio anterior sigue en pie.
Respuesta:
Una GitHub Action (disparada por la edición de la wiki, por ejecución manual o
por un cron diario) clona la wiki privada, la sube a la pública con
push --force y escribe el commit resultante en el archivo wiki-ref del
repositorio público. Ese commit a main dispara el redespliegue en Coolify, que
construye el sitio a partir de ese commit exacto.
Respuesta:
Es el puente entre dos mundos: Coolify sólo reacciona a cambios en el
repositorio, no en su wiki. Al guardar el commit de la wiki en un archivo del
repositorio, un cambio en el contenido se convierte en un push que Coolify
entiende. Además hace el build reproducible (siempre sale de un commit
concreto) e idempotente (si no cambió, no hay commit nuevo).
Respuesta:
Convierte la wiki de GitHub al formato que necesita MkDocs: renombra
Home.md a index.md, reescribe los enlaces entre páginas para que incluyan
.md y regenera el bloque nav: de mkdocs.yml a partir de las páginas
NN-nombre.md. Hace falta porque la wiki enlaza sin extensión y MkDocs necesita
el archivo para resolver el enlace; y para que el menú no se mantenga a mano.
Respuesta:
mkdocs build --strict falla, el despliegue no se completa y Coolify mantiene
la versión anterior del sitio. El error aparece en el log de Coolify con el
enlace o el ancla concreta, y se arregla editando la wiki, lo que dispara un
nuevo intento.
Respuesta:
Porque la ejecución en curso puede estar a medias (copiada la wiki pero sin
escribir wiki-ref). Cancelarla dejaría la copia pública y el sitio
inconsistentes. Con false, la ejecución en curso termina y la nueva espera su
turno.
Coolify es la plataforma que construye y publica. Aunque se configura con formularios, cada campo tiene una razón. Entenderlas es lo que permite reproducir el montaje o arreglarlo cuando falla.
| Aplicación | Repositorio | Base directory | Dominio |
|---|---|---|---|
02-bases |
Reskyon/angular-ufh |
/02-bases |
angular-basics.reskyon.com |
03-gifs-app |
Reskyon/angular-ufh |
/03-gifs-app |
angular-gifs.reskyon.com |
04-country-app |
Reskyon/angular-ufh |
la carpeta del proyecto | angular-country.reskyon.com |
| Sitio de la wiki | Reskyon/angular-wiki |
raíz del repositorio | angular-wiki.reskyon.com |
Campos comunes de cada aplicación:
| Campo en Coolify | Valor | Para qué |
|---|---|---|
| Repositorio / rama |
Reskyon/angular-ufh, main
|
De dónde se clona |
| Build pack | Dockerfile | Usa el Dockerfile del proyecto en vez de adivinar |
| Base directory | La carpeta del proyecto | Hace que COPY y npm ci trabajen dentro de esa carpeta |
| Dockerfile location | /Dockerfile |
Relativa al Base directory |
| Puerto expuesto | 80 |
El de Nginx dentro del contenedor |
| Dominio | https://angular-….reskyon.com |
Coolify configura el proxy y el certificado |
| Watch paths | Vacío | Cualquier push a main redespliega |
El repositorio tiene varios proyectos independientes, cada uno con su
package.json. Un solo Dockerfile en la raíz tendría que decidir cuál
compilar. Con un Dockerfile por carpeta y Base directory, un mismo
repositorio publica varias aplicaciones, cada una con su propia
configuración en Coolify.
etapa 1 node:24-alpine
├─ npm ci instala exactamente lo del lockfile
└─ npm run build genera dist/<proyecto>/browser
etapa 2 nginx:alpine
├─ nginx.conf fallback de SPA (try_files … /index.html)
└─ copia sólo dist/<proyecto>/browser
-
Dos etapas: la imagen final no tiene Node, ni
node_modules, ni el código fuente: sólo archivos estáticos y Nginx. -
nginx.confcontry_files … /index.html: sin él, recargar una ruta de Angular (/country) daría 404, porque en el servidor esa ruta no es un archivo. -
Por qué
node:24-alpiney no el modo por defecto de Coolify: el generador automático (Nixpacks) instala un Node más viejo que el mínimo que pide Angular 22.
La clave de Giphy no se sube a git. Se crea en Coolify como variable de
build con el prefijo NG_APP_; un script (set-env.mjs) la convierte en un
archivo que la app importa. La explicación completa, con su porqué, está en el
Módulo G5 y el despliegue de
esa app en el
Módulo G8.
navegador ──► Cloudflare (DNS + proxy, HTTPS)
│ SSL Full (strict)
▼
servidor (VPS)
│
▼
Traefik (proxy de Coolify, certificado Let's Encrypt)
│ según el dominio
▼
contenedor de la app (Nginx, puerto 80)
-
Cloudflare: registro
Acon proxy hacia el servidor. - Traefik: lo gestiona Coolify; enruta cada dominio a su contenedor.
- Un error 526 de Cloudflare casi siempre significa que el despliegue falló y no hay contenedor: el certificado que ve Cloudflare es el de Traefik por defecto. Se revisa el log de Coolify, no Cloudflare.
push a main
│
▼
Coolify clona el repositorio en esa rama
│
▼
docker build (Dockerfile del Base directory)
│ falla ──► se descarta el contenedor nuevo, queda el anterior
▼
arranca el contenedor nuevo ──► el proxy cambia a él
Conocer los límites del montaje es parte de la respuesta:
| Hoy | Mejora posible |
|---|---|
Cualquier push a main redespliega las tres apps (Watch paths vacíos) |
Poner Watch paths por carpeta (03-gifs-app/**) |
No hay pruebas ni lint automáticos antes de publicar |
Una GitHub Action en cada pull request con ng test, npm run lint y npm run format:check
|
Se publica directo desde main
|
Ramas de preview o un entorno de staging |
| Sin health check | Activarlo, para que Coolify sólo cambie al contenedor nuevo cuando responda |
| Claves en variables de build | Para secretos reales, un backend (en el front, todo es público) |
-
Poner la URL con
tree/main/...como repositorio: es el repositorio completo; la subcarpeta se indica en Base directory. - Olvidar el puerto: Coolify no sabe a qué puerto del contenedor enviar el tráfico.
-
No marcar una variable como disponible en build: existe, pero no llega
al
npm run build. -
Cambiar la carpeta de salida de
ng build(el nombre del proyecto de Angular) y no elCOPYdelDockerfile: el build se corta porque no encuentra la carpeta.
Un repositorio con varios proyectos publica varias aplicaciones gracias a un
Dockerfile por carpeta y a Base directory en Coolify. Cada push a
main construye una imagen en dos etapas (Node compila, Nginx sirve), y si el
build falla, queda la versión anterior. El dominio pasa por Cloudflare y por
Traefik hasta el contenedor.
Respuesta:
Con un Dockerfile dentro de cada carpeta de proyecto y una aplicación de
Coolify por cada una, indicando en cada caso el Base directory. Coolify
clona el mismo repositorio, entra en esa subcarpeta y construye con su
Dockerfile. Cada aplicación tiene su dominio, su puerto y sus variables.
Respuesta:
La primera etapa tiene todo lo necesario para compilar (Node, dependencias, código fuente). La segunda sólo lleva el resultado (archivos estáticos) y un servidor ligero. La imagen final es mucho más pequeña, arranca antes y tiene menos superficie de ataque, porque no incluye herramientas de desarrollo.
Respuesta:
La ruta /country existe sólo en el navegador (la define el enrutador de
Angular); en el servidor no hay un archivo con ese nombre. Hay que decirle a
Nginx que, si no encuentra el archivo, entregue index.html y deje que Angular
resuelva la ruta: try_files $uri $uri/ /index.html.
Respuesta:
El log de despliegue en Coolify, no Cloudflare. Ese error casi siempre indica que el despliegue falló y no hay contenedor corriendo: el proxy responde con su certificado por defecto, que Cloudflare (en modo Full strict) rechaza. Se arregla el build, no el DNS.
Respuesta:
Añadiría una etapa de CI antes de publicar (pruebas, lint y comprobación de
formato en cada pull request), Watch paths por proyecto para no
redesplegar todo con cada cambio, un health check para que sólo se cambie de
contenedor cuando el nuevo responda, y un entorno de preview o staging. Y
dejaría claro que las claves del front son públicas, y que los secretos reales
necesitan un backend.
Saber que algo funciona no es lo mismo que poder explicarlo en pocos minutos. Este módulo es un guion para contar el montaje completo, a distintos niveles de detalle.
Tengo un repositorio privado con tres aplicaciones Angular y su teoría en una wiki. Cada
pushamainlo despliega Coolify con un Dockerfile por proyecto. La wiki se publica sola: una GitHub Action la copia a un repositorio público y deja anotado el commit en un archivo, lo que hace que Coolify reconstruya el sitio con MkDocs.
- Qué hay: dos repositorios (privado con el código y la wiki fuente; público con la copia y el sitio) y cuatro sitios publicados.
-
El código: un
pushamain→ Coolify clona, usa elDockerfilede la carpeta (Base directory), construye en dos etapas y publica detrás de Traefik y Cloudflare. -
El contenido: editar la wiki → GitHub Action → copia a la wiki pública y
wiki-ref→ Coolify →prepare.py+mkdocs --strict→ Nginx. - Qué protege: el build estricto (nada roto llega a producción) y que el sitio sale de un commit concreto de la wiki.
- Qué mejoraría: pruebas en CI, Watch paths por proyecto y health check.
Código push a main ──► Coolify ──► docker build ──► app publicada
Contenido editar wiki ──► Action ──► copia + wiki-ref ──► Coolify ──► sitio
Dentro del build wiki @ wiki-ref ──► prepare.py ──► docs/ ──► mkdocs --strict ──► HTML ──► Nginx
| Pregunta | Idea de la respuesta |
|---|---|
| "¿Qué es CI/CD en tu proyecto?" | CD: Coolify publica en cada push. CI: el build falla si algo está mal; falta añadir pruebas automáticas |
| "¿Cómo manejas los secretos?" | En el front no hay secretos: todo es público. Lo que no se quiere en git entra por variables de build; un secreto real necesita un backend |
| "¿Qué pasa si falla un despliegue?" | Coolify descarta el contenedor nuevo y sigue sirviendo el anterior; el log dice qué falló |
| "¿Por qué Docker y no Nixpacks?" | Para fijar la versión de Node que exige Angular 22 y controlar cómo se construye |
| "¿Cómo evitas que la copia de la wiki quede vieja?" | La Action se dispara con cada edición, y hay un cron diario como red de seguridad |
| "¿Cómo escalaría esto?" | Watch paths por proyecto, pruebas en pull requests, entornos de preview y health checks |
- Empieza por el problema, no por las herramientas: «quería escribir la teoría en Markdown y que se publicara sola».
- Enseña el dibujo del panorama (Módulo P1) y deja que el público lo mire unos segundos.
- Haz una demostración corta: editar una frase en la wiki y mostrar la Action y el despliegue en Coolify.
- Cuenta un error real, como el del ancla rota que detuvo un despliegue: muestra que el build estricto cumple su función.
- Cierra con lo que mejorarías. Demuestra criterio.
El montaje se explica con dos flujos (código y contenido), un archivo puente
(wiki-ref) y un build estricto que protege producción. Lo que más suma en una
entrevista es contarlo con un dibujo, reconocer las limitaciones (todavía no
hay pruebas automáticas) y saber qué se mejoraría.
Respuesta:
Hay dos flujos. El código vive en un repositorio privado y cada push a main
lo despliega Coolify con un Dockerfile por proyecto: Node compila y Nginx
sirve. El contenido vive en una wiki; una GitHub Action la copia a un
repositorio público y anota el commit en un archivo, lo que hace que Coolify
reconstruya el sitio con MkDocs. El build es estricto: si hay un enlace roto,
no se publica y queda la versión anterior.
Respuesta:
La falta de pruebas automáticas antes de publicar: ahora el único control es
que el build compile. Añadiría una Action en cada pull request que ejecute
pruebas, lint y la comprobación de formato, y activaría los health checks
en Coolify. También acotaría los Watch paths para que un cambio en un
proyecto no redespliegue los demás.