☁️ Cloud, DevOps & Infraestructura

GitOps con Git como fuente de verdad: guía práctica

GitOps no es 'usar Git para desplegar'. Es un modelo concreto donde un controlador dentro del clúster tira de los cambios en vez de que un pipeline externo los empuje. La diferencia importa para la seguridad y la fiabilidad.

📅 15 de marzo de 2026 ⏱️ 8 min de lectura ✍️ Equipo ProgramacionWebs

“Usamos Git para desplegar” es la frase que más confusión genera alrededor de GitOps. Cualquier pipeline de CI/CD moderno usa Git como disparador: haces push, algo se ejecuta, algo se despliega. Eso no es GitOps, es simplemente CI/CD con Git como origen. GitOps es un modelo más estricto y más concreto: Git no es solo el disparador, es la única fuente de verdad del estado deseado del sistema, y algo que vive dentro del entorno de destino se encarga de comprobar continuamente que la realidad coincide con lo que dice el repositorio, y de corregirla si no coincide.

Esa diferencia —quién tira de quién— es la que separa un pipeline de despliegue convencional de un sistema GitOps real, y tiene consecuencias directas en seguridad, en capacidad de auditoría y en qué pasa cuando algo se desvía sin que nadie lo autorice.

Los cuatro principios (y por qué no son opcionales)

El proyecto OpenGitOps, bajo el paraguas de la CNCF, formalizó cuatro principios que definen si un sistema es GitOps o no lo es:

  1. Declarativo: el estado deseado se describe de forma declarativa (qué debe existir), no imperativa (qué comandos ejecutar). Un manifiesto de Kubernetes en YAML es declarativo; un script que hace una secuencia de kubectl create, kubectl patch y kubectl scale no lo es, aunque viva en Git.
  2. Versionado e inmutable: el estado deseado se guarda de una forma que garantiza historial completo, versionado e inmutabilidad — en la práctica, un repositorio Git con su historial de commits, sus pull requests y su capacidad de volver a cualquier punto anterior.
  3. Extraído automáticamente (pull, no push): un agente de software dentro del entorno de destino extrae las declaraciones de estado deseado desde la fuente, en vez de que un sistema externo las empuje hacia el entorno.
  4. Reconciliado continuamente: ese mismo agente observa el estado real del sistema de forma continua e intenta converger hacia el estado deseado, sin esperar a que alguien dispare un despliegue.

Los tres primeros principios son relativamente fáciles de cumplir con cualquier pipeline moderno. El cuarto —reconciliación continua— es el que de verdad distingue a GitOps: un pipeline tradicional despliega una vez y se olvida; un controlador GitOps sigue vigilando después.

graph LR
Dev[Developer] -->|"pull request"| Repo[(Repositorio Git
manifiestos)]
Repo -->|"merge a main"| RepoMain[Estado deseado]
Controller[Controlador GitOps
ArgoCD / Flux] -->|"pull periódico"| RepoMain
Controller -->|"compara"| Cluster[Estado real del clúster]
Controller -->|"reconcilia"| Cluster
Cluster -.->|"drift detectado"| Controller

Push vs. pull: la diferencia que importa para seguridad

En un pipeline de CI/CD tradicional tipo “push”, es el propio sistema de CI —GitHub Actions, GitLab CI, Jenkins— el que se conecta activamente al clúster de destino y aplica los cambios, típicamente con kubectl apply o helm upgrade ejecutados desde el runner. Para que eso funcione, el runner necesita credenciales con permisos de escritura sobre el clúster, casi siempre almacenadas como secretos en el propio sistema de CI.

En un modelo “pull” —el que usan Argo CD y Flux—, el runner de CI ya no necesita esas credenciales en absoluto. Su trabajo termina en construir la imagen, pasar los tests y actualizar el manifiesto en Git (por ejemplo, cambiando el tag de la imagen). El controlador GitOps, que vive dentro del propio clúster, es quien detecta ese cambio y lo aplica, usando credenciales que nunca salen del clúster.

Esto tiene implicaciones concretas:

  • Superficie de ataque menor: ningún sistema externo guarda credenciales privilegiadas de escritura sobre producción. Si el sistema de CI se ve comprometido, el atacante no hereda acceso directo al clúster.
  • Tráfico de red más simple de asegurar: el controlador solo necesita salida (egress) hacia el repositorio Git; no hace falta abrir entrada hacia el clúster desde una red externa.
  • Detección de drift: si alguien —o algo— modifica un recurso directamente en el clúster con kubectl edit, fuera del flujo de Git, el controlador lo detecta en el siguiente ciclo de reconciliación y, según la configuración, lo revierte automáticamente al estado declarado en Git. Esto convierte a Git en una fuente de verdad real, no solo nominal: los cambios manuales dejan de sobrevivir.
  • Rollback trivial: revertir un despliegue problemático es un git revert sobre el commit que lo introdujo, no una secuencia de comandos manuales contra el clúster bajo presión.

ArgoCD vs. Flux: dos formas de implementar lo mismo

Ambos son proyectos graduados de la CNCF, ambos implementan el mismo modelo pull-based, y ambos son razonablemente maduros para producción en 2026. La diferencia está en la filosofía:

Argo CDFlux
EnfoqueCentrado en aplicación, con un servidor y una UI web propiaConjunto de controladores pequeños y componibles, sin UI propia
InterfazDashboard visual con vista de árbol de recursos, diffs y sincronización manual/automáticaSe opera vía CLI y CRDs; la observabilidad se delega en herramientas externas (Grafana, Weave GitOps)
Multi-tenencyRBAC y SSO propios, pensado para que varios equipos compartan una instanciaCada controlador es más ligero; el aislamiento se apoya más en namespaces y en la estructura del repositorio
Curva de entradaMás rápida de ver “funcionando” gracias a la UIRequiere más familiaridad con Kubernetes y sus CRDs desde el principio

Ninguna es objetivamente superior: equipos que valoran una interfaz visual para que developers no familiarizados con Kubernetes puedan ver el estado de sus despliegues tienden a preferir Argo CD; equipos que ya operan con una filosofía muy centrada en Kubernetes puro y prefieren piezas pequeñas y componibles tienden a preferir Flux. Ambos se pueden combinar con Kubernetes sin fricción porque los dos actúan como controladores nativos del clúster.

Montarlo paso a paso en un proyecto real

Un flujo GitOps mínimo pero real separa dos repositorios (o dos carpetas claramente diferenciadas dentro de uno): el repositorio de código de la aplicación y el repositorio de configuración/estado deseado.

1. El repositorio de aplicación sigue haciendo CI normal

# .github/workflows/build.yml
name: build-and-publish
on:
  push:
    branches: [main]

permissions:
  contents: read
  packages: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/build-push-action@v6
        with:
          push: true
          tags: ghcr.io/miorg/miapp:${{ github.sha }}

Este pipeline construye y publica la imagen. Termina ahí: no hace kubectl apply, no toca el clúster.

2. Un paso final actualiza el repositorio de configuración

      - name: Actualizar manifiesto de despliegue
        run: |
          git clone https://github.com/miorg/config-repo.git
          cd config-repo
          sed -i "s|image: ghcr.io/miorg/miapp:.*|image: ghcr.io/miorg/miapp:${{ github.sha }}|" apps/miapp/deployment.yaml
          git commit -am "chore: actualiza miapp a ${{ github.sha }}"
          git push

Este es el único punto de contacto entre CI y GitOps: CI escribe en Git, no toca el clúster.

3. El controlador GitOps vigila ese segundo repositorio

# Application de Argo CD
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: miapp
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/miorg/config-repo.git
    targetRevision: main
    path: apps/miapp
  destination:
    server: https://kubernetes.default.svc
    namespace: miapp
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

selfHeal: true es la reconciliación continua en acción: si alguien cambia algo manualmente en el clúster, Argo CD lo revierte al estado declarado en config-repo. prune: true elimina del clúster cualquier recurso que ya no exista en el repositorio.

4. Promoción entre entornos, sin duplicar lógica de despliegue

La forma habitual de gestionar staging y producción en GitOps no es tener pipelines distintos, sino carpetas o ramas distintas dentro del mismo repositorio de configuración (environments/staging, environments/production), con herramientas como Kustomize para expresar las diferencias como overlays sobre una base común. Promocionar a producción se convierte en un pull request que copia un cambio de una carpeta a otra — auditable, revisable y reversible como cualquier otro cambio de código.

Cuándo NO merece la pena

GitOps añade piezas: un controlador que mantener, un segundo repositorio o estructura de carpetas que sincronizar, una curva de aprendizaje sobre reconciliación y drift. Para un proyecto pequeño con un único entorno y despliegues poco frecuentes, un pipeline de CI/CD tradicional que haga kubectl apply directamente puede ser perfectamente razonable y mucho más simple de operar. GitOps demuestra su valor cuando hay varios clústeres o entornos que mantener consistentes, cuando la auditoría de “qué cambió y quién lo aprobó” importa de verdad (regulación, compliance), o cuando el equipo ya ha crecido lo suficiente como para que la coordinación manual de despliegues empiece a fallar.

Esto conecta directamente con Platform Engineering: GitOps es, en la práctica, la tubería de despliegue que casi todos los Internal Developer Platforms usan por debajo, precisamente porque le da al developer una experiencia simple (un pull request) mientras mantiene el control y la auditoría en manos de la plataforma. Si además el pipeline de CI que alimenta ese repositorio de configuración necesita reforzarse en seguridad y velocidad, es la continuación natural de este artículo revisar los patrones de CI/CD en producción con GitHub Actions.

Compartir