Files
devops a3c022c024 docs: READMEs completos, estructura final de arquetipos
Mueve los reusable workflows a .gitea/workflows/ (restriccion real de
Gitea: workflow_call no puede vivir en una ruta arbitraria, confirmado
en vivo). Corrige todos los 'uses:' cross-repo a URL absoluta
(https://gitea.cruzcloud.net/...) -- la forma corta de GitHub se
resuelve contra github.com en esta instancia, confirmado en vivo con un
test real (reusable workflow + composite action ejecutando gitleaks de
punta a punta). Limpia los archivos de prueba.
2026-08-17 00:23:24 -05:00

8.7 KiB

platform-templates

Patrones reutilizables para dar de alta una app nueva en el lab CruzCloud (ZimaOS + k3d + Gitea + Argo CD) sin escribir el pipeline y los manifiestos K8s desde cero cada vez. Copiás una plantilla, completás unos pocos valores, y tenés: pipeline de CI con el gate completo de DevSecOps (Gitleaks, Trivy, Semgrep, Syft, Cosign) + manifiestos K8s con buenas prácticas por default (resource limits, probes, non-root).

Para quién es esto: si estás dando de alta una app nueva en el lab, seguí el ejemplo de abajo. Si estás migrando una app existente, mirá el punto "Migración de apps existentes" más abajo, y andá con más cuidado — tocás algo que ya está en producción.

Mapa del repo

pipelines/           -- documentación de cada arquetipo de pipeline
                         (el archivo real que se invoca vive en
                         .gitea/workflows/, ver por qué más abajo)
.gitea/workflows/     -- los reusable workflows reales (workflow_call)
dockerfiles/          -- Dockerfiles genéricos, un ARG por cosa a cambiar
k8s-manifests/base/   -- Kustomize base: Deployment+Service+Ingress
                         genéricos, con securityContext/probes/resources
                         ya puestos
k8s-manifests/overlays-ejemplo/ -- el ejemplo de abajo, ya armado
devsecops-snippets/   -- los 6 composite actions que arman cada pipeline

Hallazgo importante sobre Gitea Actions (leer antes de copiar nada)

Este repo se armó verificando en vivo, no asumiendo que Gitea Actions es 100% igual a GitHub Actions. Dos diferencias reales, confirmadas corriendo pipelines de verdad contra esta instancia:

  1. Los reusable workflows (workflow_call) tienen que vivir bajo .gitea/workflows/ — no en una carpeta cualquiera. Por eso pipelines/archetype-*/ solo tiene un README.md, el archivo invocable real está en .gitea/workflows/archetype-*.yaml.
  2. Toda referencia cross-repo en un uses: necesita la URL completa (https://gitea.cruzcloud.net/owner/repo/path@ref), no la forma corta de GitHub (owner/repo/path@ref) — esa forma corta se resuelve contra github.com por default en esta instancia (confirmado: intentar usarla hace que el runner intente clonar github.com/devops/platform-templates, que no existe, y falle con "Repository not found"). actions/checkout@v3 sigue andando con la forma corta porque ESE sí existe en github.com de verdad.

Ejemplo end-to-end: dar de alta "mi-app-ejemplo"

Pensado para seguirse sin conocer nada más del lab que "existe Gitea en gitea.cruzcloud.net y un cluster k3d con Argo CD sincronizando desde ahí". Asumimos una API Node (arquetipo node-api) — para Next.js o un sitio estático es el mismo procedimiento, cambiando el arquetipo.

1. Creá el repo de tu app en Gitea

Vía la UI (https://gitea.cruzcloud.net/repo/create) o gh/API si tenés token con el scope correcto. Nombre: mi-app-ejemplo.

2. Copiá el Dockerfile

curl -o Dockerfile \
  https://gitea.cruzcloud.net/devops/platform-templates/raw/branch/main/dockerfiles/node-api.Dockerfile

Ajustá los ARG del inicio del archivo a tu caso (puerto real, comando de arranque) — están documentados inline, no hace falta adivinar.

3. Agregá el pipeline

.gitea/workflows/build.yaml en tu repo:

name: Build and Push

on:
  push:
    branches: [main]
    paths:
      - 'src/**'
      - 'package.json'
      - 'package-lock.json'
      - 'Dockerfile'
      - '.gitea/workflows/build.yaml'

jobs:
  build:
    uses: https://gitea.cruzcloud.net/devops/platform-templates/.gitea/workflows/archetype-node-api.yaml@main
    with:
      app_dir: .
      image_name: gitea.cruzcloud.net/devops/mi-app-ejemplo
      manifest_file: k8s/deployment.yaml
    secrets:
      REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
      REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
      COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
      COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}

Los 4 secrets tienen que existir en tu repo (Settings → Actions → Secrets). Si tu lab ya los tiene a nivel de organización/compartidos, puede que no haga falta redeclararlos — depende de cómo esté armado tu Gitea.

4. Generá tu par de llaves de Cosign (una vez)

cosign generate-key-pair
# genera cosign.key (privada) y cosign.pub (pública)
  • cosign.pub → commiteala en tu repo (es pública a propósito).
  • cosign.key → no la commitees. Subila como secret COSIGN_PRIVATE_KEY (contenido del archivo) y la password que elegiste como secret COSIGN_PASSWORD.

5. Manifiestos K8s: copiá el base y armá tu overlay

mkdir -p k8s/patches
curl -o k8s/kustomization.yaml \
  https://gitea.cruzcloud.net/devops/platform-templates/raw/branch/main/k8s-manifests/overlays-ejemplo/mi-app-ejemplo/kustomization.yaml
curl -o k8s/patches/deployment.yaml \
  https://gitea.cruzcloud.net/devops/platform-templates/raw/branch/main/k8s-manifests/overlays-ejemplo/mi-app-ejemplo/patches/deployment.yaml
curl -o k8s/patches/service.yaml \
  https://gitea.cruzcloud.net/devops/platform-templates/raw/branch/main/k8s-manifests/overlays-ejemplo/mi-app-ejemplo/patches/service.yaml
curl -o k8s/patches/ingress.yaml \
  https://gitea.cruzcloud.net/devops/platform-templates/raw/branch/main/k8s-manifests/overlays-ejemplo/mi-app-ejemplo/patches/ingress.yaml

Esto ya viene armado con el patrón real (ver k8s-manifests/overlays-ejemplo/mi-app-ejemplo/ en este mismo repo) — solo tenés que:

  1. Cambiar mi-app-ejemplo por el nombre real de tu app en los 3 patches/*.yaml y en el kustomization.yaml (nombre del Deployment/Service/Ingress, host:, namespace:).
  2. Ajustar imagen, puerto, y ruta del healthcheck a los reales de tu app.
  3. Leer los comentarios de patches/deployment.yaml sobre $patch: replace/JSON6902 antes de tocar ports — Kustomize NO reemplaza esa lista con un patch de merge normal, la duplica (confirmado en vivo armando este repo). Si tu puerto real es distinto al 8080 del base, el rename+ports va en un patch JSON6902 aparte en tu kustomization.yaml, exactamente como en el ejemplo.
  4. Si tu app escucha en un puerto <1024 (80, por ejemplo): el base saca TODAS las capabilities del contenedor por default — necesitás agregar capabilities.add: [NET_BIND_SERVICE] en tu patch, o el bind falla. Ver el comentario en k8s-manifests/overlays-ejemplo/mi-app-ejemplo/kustomization.yaml si tu imagen corre como root en vez de un usuario propio (necesitás además runAsNonRoot: false a nivel pod).

6. Validá el resultado ANTES de commitear

kubectl kustomize k8s/

Revisá que el YAML que sale tenga sentido — nombre, imagen, puerto, host correctos, sin recursos duplicados. Si algo se ve raro (dos ports:, dos contenedores), revisá el punto 3 de arriba.

7. Creá la Application de Argo CD

Sigue el patrón de este lab: una Application de workload (syncPolicy con prune: true, selfHeal: true) apuntando al k8s/ de tu repo. Preguntale a quien mantiene apps-registry cómo se registra — no es parte de este repo.

8. Push, mirá el pipeline correr, y listo

Un git push a main dispara Gitleaks → build → Trivy → Semgrep → Syft → Cosign → push al registry → Argo CD sincroniza. Si algo bloquea (CRITICAL de Trivy, firma de Cosign inválida), el log del job te dice exactamente cuál.

Migración de apps existentes

Si estás moviendo una app que ya está corriendo en producción hacia este patrón base+overlay: no es lo mismo que dar de alta una nueva. Antes de tocar nada:

  1. Guardá el manifest actual desplegado (kubectl get deploy <nombre> -n <ns> -o yaml) para comparar.
  2. Armá el overlay apuntando al mismo nombre que ya existe (usando los patches JSON6902 de rename, como en el ejemplo) — no dejes que Kustomize invente un nombre nuevo, rompe el tracking de Argo CD y la resolución DNS de otros servicios que dependan del nombre viejo.
  3. Revisá con qué usuario corre tu imagen actual. Si no tiene un USER no-root en su Dockerfile, el runAsNonRoot: true del base rompe el pod (CreateContainerConfigError) — hace falta un override explícito hasta que la imagen se actualice aparte. Pasó exactamente esto migrando docs-portal en este mismo lab.
  4. Corré kubectl kustomize sobre tu overlay y compará el resultado contra el manifest guardado en el paso 1 antes de dejar que Argo CD sincronice nada.

Documentación ampliada

docs.cruzcloud.net tiene una sección dedicada a este repo con un diagrama del flujo completo y el detalle de la migración real que se hizo sobre ecommerce-frontend, commerce-backend y docs-portal.