TPTito Pinardo Gutiérrez
← Volver al rack
English
1U · GITForgejo · CI · despliegue En producción

Posición 19 de 25 en el rack · En producción

Unidad 1U · Código y entrega

Mi propio Git, con CI y despliegue

De push a la web: sin tocar nada

La infraestructura vivía en un repositorio sin interfaz y nadie revisaba nada antes de que entrara: un fichero mal formateado o un secreto en claro se copiaban tal cual. Ahora todo pasa por Forgejo. Cada push lo comprueba un runner sin secretos, cada cambio de infraestructura entra por PR con su plan a la vista, y esta web se despliega sola cuando su CI sale en verde.

Dónde vive el código
En casa. GitHub queda como escaparate: la copia saneada de la infraestructura y esta web, que solo recibe lo que ya está desplegado.
La regla
Nada entra sin CI en verde, y nada de infraestructura se aplica sin ver antes el plan. El paso final lo apruebo yo con un clic.
Los avisos
Cada ejecución, bien o mal, llega a Element cifrada. El fallo sale en cuanto falla el primer trabajo.
  • 0secretos en el runner del CI
  • 1 clicpara aplicar un cambio de infraestructura, con su plan
  • 8 de 8avisos entregados en Element el primer día
  • ~1,3 GBde memoria al compilar esta web en el CI

El origen del código por dentro

Por aquí entra cada cambio de la infraestructura y de esta web, y aquí se decide si sale. Elige un recorrido.

Fuera Forgejo y sus runners · contenedores en Proxmox el servidor Git, un runner sin secretos y uno que despliega Forgejo Runner del CI: comprueba Runner de despliegue: aplica Alrededor Mis equipos Element Respaldo Esta web GitHub Git por SSH Web con 2FA PR y ramas Webhooks Runner CI TF + lint Rust + PG gitleaks Plan del PR Un clic Plan guardado Fugas Avisos Relay cifrado Renovate Copia diaria
Hermesde visita en la unidad

Pulsa una pieza o elige un recorrido. Si vienes de una habilidad, ya están encendidas las piezas donde la uso.

      Un cambio de infraestructura, por PR
      1. Mis equipos — Desde cualquiera de mis equipos creo una rama: la principal no admite pushes directos.
      2. Git por SSH — La empujo a Forgejo por SSH.
      3. PR y rama protegida — Abro el PR.
      4. Runner del CI — El runner del CI lo recoge: no tiene ni un secreto ni acceso al homelab.
      5. Terraform, lint y Trivy — Formato y validación de Terraform, y ansible-lint.
      6. gitleaks — gitleaks sobre todo el historial y comprobación de que cada vault está cifrado.
      7. Plan del PR — Mientras, el runner de despliegue calcula el plan: qué toca Terraform y qué playbooks, en ensayo.
      8. PR y rama protegida — El plan queda como comentario del PR: veo qué se toca antes de fusionar.
      9. Aprobar con un clic — Fusiono. Sale el plan de la rama principal y lo apruebo con un clic.
      10. Plan guardado — Se aplica ese plan guardado. Si algo cambió entretanto, Terraform se niega.
      11. Receptor de avisos — El resultado sale por el webhook.
      12. Relay de Matrix — Cifrado, hacia la sala del CI/CD.
      13. Element — Y me llega al móvil.
      Esta web, de un push a internet
      1. Mis equipos — Empujo un cambio de esta web.
      2. Git por SSH — Llega a Forgejo, el origen del repositorio.
      3. Runner del CI — El runner del CI lo recoge.
      4. Rust y PostgreSQL — Formato, clippy, pruebas contra PostgreSQL, compilación, contenido válido y JS minificado válido.
      5. gitleaks — Y gitleaks sobre todo el historial.
      6. Control de fugas — En verde, el runner de despliegue revisa lo que aún no es público.
      7. Esta web — Compila, despliega con Ansible y espera a que la web responda.
      8. GitHub, la copia pública — Solo entonces empuja ese commit a GitHub.
      9. Receptor de avisos — El webhook avisa del resultado.
      10. Element — Y lo veo en el móvil.
      Dos commits en rojo
      1. Mis equipos — Doy de alta en la configuración las claves SSH de mis equipos.
      2. Git por SSH — Push.
      3. Runner del CI — El CI lo recoge.
      4. Terraform, lint y Trivy — ansible-lint falla: líneas de más de 160 caracteres. Son las claves.
      5. Receptor de avisos — El aviso sale en cuanto falla el primer trabajo, sin esperar al resto.
      6. Element — ❌ en Element, con el commit y el enlace a la ejecución.
      7. Mis equipos — Lo corrijo: permiso para esas líneas y solo esas.
      8. Runner del CI — Verde.
      9. Element — ✅ en Element. Ese día salieron ocho avisos y llegaron los ocho.
      Las versiones se suben solas, pero no se aplican solas
      1. Renovate — Lunes por la mañana: Renovate encuentra una versión nueva de Traefik.
      2. PR y rama protegida — Abre un PR con el número nuevo y su suma SHA-256, sacada del fichero de sumas oficial. Si la release no la publica, no hay PR.
      3. Runner del CI — El CI lo comprueba como cualquier otro.
      4. Terraform, lint y Trivy — Formato, lint y secretos.
      5. Plan del PR — El plan del PR dice qué playbook se lanzaría y cuántas tareas cambiarían.
      6. Aprobar con un clic — Fusiono y apruebo con un clic.
      7. Plan guardado — Se aplica ese plan.
      8. Receptor de avisos — El resultado sale por el webhook.
      9. Element — Y llega a Element. Las de versión mayor esperan a que las apruebe en un issue antes de abrir siquiera el PR.
      Cada noche: ¿alguien tocó algo a mano?
      1. Plan guardado — De madrugada, el runner de despliegue parte de lo último desplegado.
      2. Plan del PR — Calcula el plan de Terraform. Si sale algo, alguien ha cambiado algo fuera del código: es deriva de verdad.
      3. Receptor de avisos — Avisa. También si la rama principal tiene cambios sin desplegar.
      4. Element — Y si todo cuadra, silencio: no llega nada.
      Qué se copia y qué se recrea
      1. Git por SSH — Los repositorios y su historial viven en Forgejo.
      2. Copia diaria — Cada noche, copia del contenedor a un disco aparte.
      3. Runner del CI — El runner del CI no se copia: no guarda nada.
      4. Terraform, lint y Trivy — Se recrea con su playbook, con las herramientas en versiones fijadas y sus sumas comprobadas.
      5. Rust y PostgreSQL — Y lo mismo Rust y PostgreSQL para esta web.
      1. Mis equipos

        El PC, el portátil y el nodo de control: los únicos con clave para empujar.

      2. Element

        Una sala propia para el CI/CD, aparte de las incidencias: un mensaje por ejecución, con el commit y el enlace.

      3. Repositorio de respaldo

        El de siempre: cada equipo le sigue empujando cada commit. Tiene su propia unidad.

        • Git
      4. Esta web

        Un binario de Rust: lo que despliega el runner cuando todo sale en verde.

        • Rust
      5. GitHub, la copia pública

        Solo recibe commits que ya han pasado el CI, el control de fugas y el despliegue.

        • Git
      6. Git por SSH

        Un puerto propio para Git, separado del SSH de administración. Solo entran las claves dadas de alta.

        • Git
        • Forgejo · Actions
      7. Interfaz web

        Solo desde casa o la VPN, detrás de la puerta interna con doble factor y luego el login de Forgejo.

        • Linux / SSH hardening
      8. PR y rama protegida

        La rama principal de la infraestructura no admite pushes: solo PR con el CI en verde.

        • Forgejo · Actions
      9. Webhooks

        Al terminar cada ejecución, Forgejo avisa con una petición firmada.

        • Forgejo · Actions
      10. Runner del CI

        En su propio contenedor, sin un solo secreto ni acceso al homelab. Si tiene que leer un vault, lee uno falso a propósito.

        • Forgejo · Actions
        • LXC
      11. Terraform, lint y Trivy

        Formato y validación de Terraform sin estado ni credenciales, tflint, ansible-lint (solo falla con avisos nuevos) y Trivy, que busca fallos graves en la configuración y en las dependencias.

        • Terraform
        • Ansible
      12. Rust y PostgreSQL

        Para esta web: formato, clippy, pruebas contra un PostgreSQL de verdad, compilación y contenido válido.

        • Rust
        • PostgreSQL
      13. gitleaks

        Sobre todo el historial, no solo el último commit: lo borrado sigue en el repositorio.

        • Linux / SSH hardening
        • Git
      14. Plan del PR

        Terraform plan y los playbooks afectados en modo ensayo. Queda como comentario del PR, antes de fusionar.

        • Terraform
        • Ansible
      15. Aprobar con un clic

        Fusionar no aplica nada: sale el plan de la rama principal y lo apruebo yo.

        • Forgejo · Actions
      16. Plan guardado

        Se aplica exactamente ese plan. Si el estado cambió después, Terraform se niega en vez de hacer otra cosa.

        • Terraform
      17. Control de fugas

        Antes de que nada salga de casa: gitleaks, solo mi identidad pública como autor y ni direcciones ni nombres internos.

        • Linux / SSH hardening
        • Git
      18. Receptor de avisos

        Comprueba la firma del webhook y resume la ejecución: repositorio, rama, commit, duración y enlace.

      19. Relay de Matrix

        Publica el aviso cifrado de extremo a extremo en la sala del CI/CD.

      20. Renovate

        Cada lunes busca versiones nuevas de todo lo que está fijado y abre un PR por cada una, con su suma SHA-256 oficial.

        • Renovate
      21. Copia diaria

        El contenedor de Forgejo, cada noche a un disco aparte: siete diarias y cuatro semanales.

        • LXC

      Lo que uso de mi árbol

      Pulsa una habilidad: se encienden en el mapa las piezas donde la uso y te cuento para qué.

      Terraform
      Formato y validación en el CI sin estado; plan del PR y plan guardado que se aplica tal cual.
      Ansible
      ansible-lint con línea base, playbooks afectados en ensayo y despliegue de esta web con su rol.
      LXC
      Forgejo y el runner, cada uno en su contenedor sin privilegios; el runner se recrea desde cero.
      Forgejo · Actions
      Servidor Git con PR, ramas protegidas, Actions y webhooks; runners en modo host, cada uno con lo justo, y actualizaciones de la flota con un botón.
      Renovate
      PR semanales de versiones nuevas con su suma oficial, agrupadas cuando tienen que ir juntas y las mayores con aprobación previa.
      Git
      Origen en casa, respaldo en cada commit, central de antes en solo lectura y GitHub como copia pública.
      Linux (Debian, Alpine, Arch)
      Contenedores Debian con versiones fijadas y sumas comprobadas de cada binario.
      Linux / SSH hardening
      Runner sin secretos, gitleaks en todo el historial y control de fugas antes de publicar.
      PostgreSQL
      Un PostgreSQL solo por socket en el runner: cada prueba crea su propia base.
      Rust
      El CI de esta web: formato, clippy, pruebas y compilación con caché entre ejecuciones.

      Bitácora de incidencias

      Averías reales. Lee el síntoma, elige la causa que creas y mira cómo se arregló.

      1. #1

        Trabajos que no arrancaban nunca

        14-10-2026 · Forgejo

        El runner estaba conectado y sin errores, pero los trabajos del CI se quedaban «en espera» para siempre.

        ¿Qué crees que pasaba?

        Ver la solución
        Causa
        El runner se registró sin etiquetas, así que no casaba con ningún trabajo. Al ponérselas siguió igual: solo vuelve a pedir trabajo cuando cambia la versión de tareas del servidor, y los trabajos ya estaban en cola.
        Solución
        Registrarlo con su etiqueta desde el playbook y reiniciarlo una vez. Quedó escrito en la documentación.
        Lección
        «En espera» sin errores casi siempre es un problema de emparejamiento, no de conexión. Las etiquetas van desde el primer registro.
      2. #2

        El primer CI salió en rojo, y era lo esperado

        14-10-2026 · Terraform

        terraform validate fallaba en el runner y en mi terminal no. No usa estado ni credenciales.

        ¿Qué crees que pasaba?

        Ver la solución
        Causa
        El proveedor lee la clave pública del usuario con file(), y validate evalúa esa función. En el runner, que no tiene secretos, el fichero no existía.
        Solución
        Un fichero falso a propósito en el runner: deja validar y no abre nada. Lo mismo para la contraseña del vault que pide ansible-lint.
        Lección
        Un CI sin secretos destapa todo lo que el código da por hecho de la máquina. Mejor descubrirlo en rojo que tenerlo escondido.
      3. #3

        Un secreto para algo apagado

        14-10-2026 · Forgejo

        Forgejo no arrancaba: pedía un secreto de OAuth2, y OAuth2 estaba desactivado.

        ¿Qué crees que pasaba?

        Ver la solución
        Causa
        La versión instalada comprueba ese secreto al arrancar aunque la función esté apagada.
        Solución
        Generarlo en el propio contenedor la primera vez, junto a los otros secretos internos. No sale de ahí.
        Lección
        Los secretos internos se generan en la máquina que los usa y nunca viajan.
      4. #4

        Un aviso que en mi terminal no salía

        14-10-2026 · ansible-lint

        ansible-lint daba en el runner un aviso nuevo en un rol que en el nodo de control pasaba limpio. Misma versión.

        ¿Qué crees que pasaba?

        Ver la solución
        Causa
        El nodo de control no tenía instalada la colección de Kubernetes que usa ese rol, y ansible-lint lo saltaba sin decirlo. El runner instala todas las del fichero de requisitos.
        Solución
        Arreglar el aviso. Desde entonces la referencia es el CI, que parte de una instalación limpia.
        Lección
        «En mi máquina pasa» no vale si mi máquina tiene huecos. Por eso el CI parte de cero.
      5. #5

        Una subida de versión con cero cambios

        15-10-2026 · Renovate

        Renovate abrió el PR de una versión nueva de Qdrant. El CI salió en verde, pero el plan decía que el playbook no cambiaría ni una tarea.

        ¿Qué crees que pasaba?

        Ver la solución
        Causa
        El rol bajaba el binario a una ruta temporal fija y se saltaba la descarga si ya había uno. Con el de la versión anterior allí, la nueva nunca se bajaba. Lo mismo le pasaba a Traefik, y la herramienta de Ansible del nodo de control tampoco se actualizaba si ya había una instalada.
        Solución
        Cada versión en su propio directorio, como ya hacían otros roles, y la herramienta de Ansible comparando la versión instalada con la pedida. Después, los planes ya enseñaban los cambios.
        Lección
        Una subida de versión que no cambia nada es una pista, no una buena noticia. Hasta Renovate, esos números se subían a mano y nadie lo había notado.
      6. #6

        Renovate no veía ninguna versión

        15-10-2026 · Renovate

        Las primeras pasadas de Renovate no encontraban versiones nuevas de nada que viniera de GitHub, aunque las había.

        ¿Qué crees que pasaba?

        Ver la solución
        Causa
        Renovate solo consulta las releases de GitHub sin token cuando corre en GitHub. En casa, esas consultas fallaban.
        Solución
        Usar como fuente las imágenes oficiales de Docker y los paquetes de PyPI, que publican las mismas versiones. Sin tokens de más que guardar.
        Lección
        Antes de añadir un token, buscar una fuente que no lo necesite.
      7. #7

        Lo primero que encontró Trivy

        15-10-2026 · CI

        Añadí Trivy al CI para vigilar dependencias con fallos conocidos. Lo primero que marcó como grave no fue ninguna dependencia.

        ¿Qué crees que pasaba?

        Ver la solución
        Causa
        La API que abre un navegador sin pantalla para leer páginas de terceros se ejecutaba como root. Si una de esas páginas consiguiera explotar el navegador, tendría root en el contenedor.
        Solución
        Que corra con un usuario sin privilegios, el mismo que ya era dueño de sus carpetas. Comprobado que el navegador arranca igual.
        Lección
        Un escáner nuevo encuentra deudas viejas. Mejor que lo encuentre él que otro.

      Cómo se construyó

      1. 14-10-2026 · mañana

        Forgejo y el CI

        • Forgejo en su contenedor, con su base de datos SQLite y la interfaz detrás de la puerta interna.
        • Runner sin secretos: Terraform, ansible-lint, vaults cifrados y gitleaks.
        • Avisos de cada ejecución en Element, cifrados.
      2. 14-10-2026 · tarde

        Forgejo como origen

        • Los equipos empujan a Forgejo; el central de antes, de solo lectura.
        • Monitorización y seguridad como el resto de la flota, y copia diaria.
        • El plan completo de Terraform, limpio por primera vez.
      3. 14-10-2026 · noche

        Despliegue por PR

        • Rama principal protegida: solo PR con el CI en verde.
        • Plan del PR como comentario y plan guardado al fusionar.
        • Aplicar con un clic, probado de punta a punta.
      4. 14-10-2026 · noche

        Esta web, también

        • Su origen pasa a Forgejo y su CI al mismo runner: Rust, PostgreSQL y Node.
        • Despliegue continuo con control de fugas.
        • GitHub, la copia pública que solo recibe lo ya desplegado.
      5. 15-10-2026

        Versiones y deriva

        • Renovate: un PR por versión nueva, con su suma oficial; las mayores, solo si las apruebo.
        • Los primeros PR destaparon tres roles que no actualizaban de verdad, ya arreglados.
        • Deriva de Terraform cada noche y aviso de cambios sin desplegar.
        • Las llaves maestras salen del disco y pasan a OpenBao, con su propia unidad.
      6. 15 al 18-10-2026

        Más comprobaciones y menos terminal

        • tflint y Trivy en el CI: falla con cualquier hallazgo grave.
        • Actualizar la flota sin abrir una terminal: un botón en Forgejo, y los paquetes solos cada miércoles de madrugada.
        • Un runner propio para los plugins de la red de juegos, con sus pruebas contra servidores de verdad.

      Decisiones (y por qué)

      • Dónde vive el código

        En casa; GitHub como escaparate

        Lo interno no tiene por qué salir. GitHub enseña lo que quiero enseñar: una copia saneada y esta web, siempre detrás de lo que ya funciona.

      • Qué servidor Git

        Forgejo

        Un binario y SQLite: arranca con un gigabyte de memoria. Un GitLab completo pide varios para lo mismo, y aquí la memoria tiene otros usos.

      • Los runners

        Dos, separados por lo que pueden tocar

        El que comprueba todo no tiene nada que robar: ni secretos ni acceso. El que despliega vive en el nodo de control, que ya podía tocarlo todo, y solo atiende a su repositorio.

      • Aplicar infraestructura

        Con un clic, y el plan exacto

        Un plan que recrea un contenedor con datos se lee antes. Cuando haya confianza, los planes que solo actualizan podrían ir solos; los que destruyen, nunca.

      • Las versiones

        PR automáticos, aplicar con un clic

        Renovate quita el trabajo de buscar y de calcular sumas, pero cada subida pasa por el mismo plan y la misma aprobación que un cambio mío. Las versiones mayores esperan a que las apruebe, y las bases de datos se quedan fuera: subirlas exige migrar datos.

      • Esta web

        Despliegue continuo

        Si algo sale mal, se vuelve al commit anterior en minutos y no hay datos en juego. El control de fugas va antes de desplegar, no después.

      Lo que aprendí

      • Un CI sin secretos descubre lo que el código daba por hecho.
      • Ver el plan antes de fusionar cambia cómo se revisa.
      • Lo público va detrás de lo desplegado, nunca delante.

      Lo siguiente

      • Que los planes que solo actualizan se apliquen solos.
      • Renovate también para las dependencias de esta web.
      • Copias de verdad para todo el servidor, no solo para Forgejo.

      ¿Hablamos?

      Puedo contar cualquiera de estas decisiones en detalle en una entrevista.

      Descargar CV Contactar ← Volver al rack

      Reconstrucción a partir de mi documentación: sin direcciones, puertos ni nombres internos.