# 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 ```bash 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: ```yaml 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) ```bash 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 ```bash 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 ```bash 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 -n -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`.