CI/CD Pipeline
DiseΓ±o e implementaciΓ³n de un pipeline CI/CD completo con GitHub Actions, Container Registry, ArgoCD y Kubernetes. Cubre desde el commit hasta el despliegue automΓ‘tico en producciΓ³n con estrategias GitOps.
Pipeline Architecture
El pipeline sigue el modelo GitOps: el estado deseado de la infraestructura se define en Git, y ArgoCD se encarga de sincronizar el cluster con el repositorio automaticamente.
Flujo completo CI/CD
Developer CI (GitHub Actions) CD (ArgoCD)
β β β
β git push β β
βββββββββββββββββββββββββββββββΆβ β
β β 1. Lint + Test β
β β 2. Build Docker image β
β β 3. Push to Registry β
β β 4. Update K8s manifests β
β β (image tag) β
β βββββββββββββββββββββββββββββββββΆβ
β β β 5. Detect change
β β β 6. Sync to cluster
β β β 7. Health check
β β β
ββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββ€
β Deploy notification β β
Repositorios:
app-repo/ -> codigo de la aplicacion + Dockerfile + CI workflows
infra-repo/ -> Helm charts / K8s manifests (fuente de verdad para ArgoCD) Por que GitOps?
Con GitOps, Git es la fuente de verdad. Toda la configuracion del cluster
esta versionada. Los rollbacks son un simple git revert. Auditabilidad total
de quien cambio que y cuando.
GitHub Actions
El workflow de CI se ejecuta en cada push a main
y en cada Pull Request. Incluye testing, build de imagen Docker y push al registry.
# .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Lint (Ruff)
run: ruff check . --output-format=github
- name: Type check (mypy)
run: mypy app/ --ignore-missing-imports
- name: Unit tests
run: pytest tests/ -v --cov=app --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
file: coverage.xml
build-and-push:
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max Container Registry
El registry almacena las imagenes Docker construidas por CI. GitHub Container Registry (ghcr.io) se integra nativamente con GitHub Actions.
| Registry | URL | Ventaja |
|---|---|---|
| GitHub (GHCR) | ghcr.io | Integracion nativa con Actions + permisos GITHUB_TOKEN |
| Docker Hub | docker.io | Registry publico mas grande, rate limits en gratuito |
| AWS ECR | *.ecr.*.amazonaws.com | Integracion IAM, scanning de vulnerabilidades |
| Google AR | *-docker.pkg.dev | Integracion GKE, vulnerability scanning |
Estrategia de tagging
# Tags recomendados por imagen:
ghcr.io/org/backend:abc123f # SHA del commit (inmutable)
ghcr.io/org/backend:1.4.2 # Semver (releases)
ghcr.io/org/backend:latest # Ultimo build de main
# NUNCA usar :latest en produccion -> siempre SHA o semver ArgoCD & GitOps
ArgoCD monitorea el repositorio de infraestructura y sincroniza automaticamente los manifiestos con el cluster de Kubernetes. Si detecta drift entre el estado del cluster y Git, puede corregirlo automaticamente.
# argocd-application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: backend-production
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/org/infra-repo.git
targetRevision: main
path: charts/backend
helm:
valueFiles:
- values-production.yaml
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # Eliminar recursos huerfanos
selfHeal: true # Corregir drift automaticamente
syncOptions:
- CreateNamespace=true Auto-update de image tag
Usa GitHub Actions para hacer un commit automatico al infra-repo actualizando el image tag despues de cada build exitoso. ArgoCD detecta el cambio y despliega automaticamente.
Step de CI que actualiza infra-repo
# Paso adicional en CI despues del build
update-manifest:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: Checkout infra repo
uses: actions/checkout@v4
with:
repository: org/infra-repo
token: ${{ secrets.INFRA_REPO_TOKEN }}
- name: Update image tag
run: |
cd charts/backend
sed -i "s|tag:.*|tag: ${{ github.sha }}|" values-production.yaml
git config user.name "CI Bot"
git config user.email "ci@org.com"
git add .
git commit -m "chore: update backend to ${{ github.sha }}"
git push Environments
La separacion de entornos asegura que los cambios se validen progresivamente antes de llegar a produccion. Cada entorno tiene su propio namespace y configuracion en K8s.
| Entorno | Trigger | Namespace | Replicas | Proposito |
|---|---|---|---|---|
| Dev | Push a branch | dev | 1 | Testing rapido de features |
| Staging | PR -> main | staging | 2 | Validacion pre-produccion |
| Production | Merge a main | production | 3-10 | Trafico real |
Flujo de promocion entre environments
feature-branch main
β β
β PR created β
ββββββββββββββββββββββΆβ
β β
[ Dev ] [ Staging ] [ Production ]
Auto-deploy Auto-deploy Auto-deploy
on push on PR merge on manifest update
β β β
βΌ βΌ βΌ
1 replica 2 replicas 3-10 replicas
No monitoring Alertas test Full observability Rollback Strategies
Cuando un deploy falla, necesitas volver al estado anterior de forma rapida y confiable.
GitOps rollback (recomendado)
# En el infra-repo: revertir el commit que actualizo el tag
git revert HEAD
git push
# ArgoCD detecta el cambio y sincroniza al tag anterior K8s rollback directo
# Ver historial de rollouts
kubectl rollout history deployment/backend -n production
# Rollback a la revision anterior
kubectl rollout undo deployment/backend -n production
# Rollback a una revision especifica
kubectl rollout undo deployment/backend --to-revision=3 -n production
# Verificar estado
kubectl rollout status deployment/backend -n production Rollback K8s vs GitOps
kubectl rollout undo causa drift entre el cluster y Git.
ArgoCD intentara re-sincronizar al estado de Git. Siempre prefiere el rollback
via Git para mantener la consistencia.
Branch Protection & Releases
Una estrategia solida de proteccion de ramas y releases automatizados garantiza que el codigo que llega a produccion sea estable, revisado y versionado correctamente. Combinar branch protection rules con semantic versioning y releases automatizados reduce errores humanos y acelera el ciclo de entrega.
Branch protection rules en GitHub
Configuracion recomendada para la rama main
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Branch Protection: main β
β β
β β Require pull request before merging β
β βββ Required approvals: 1 (minimo) β
β βββ Dismiss stale reviews on new commits β
β βββ Require review from code owners β
β β
β β Require status checks to pass β
β βββ CI Pipeline / test β
β βββ CI Pipeline / lint β
β βββ Require branches to be up to date β
β β
β β Require signed commits β
β β Require linear history (squash or rebase) β
β β Allow force pushes: NEVER β
β β Allow deletions: NEVER β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ # Configurar branch protection via GitHub CLI
gh api repos/{owner}/{repo}/branches/main/protection \
--method PUT \
--field required_status_checks='{"strict":true,"contexts":["test","lint"]}' \
--field enforce_admins=true \
--field required_pull_request_reviews='{"required_approving_review_count":1,"dismiss_stale_reviews":true}' \
--field restrictions=null Conventional Commits y Semantic Versioning
El versionado semantico (SemVer) sigue el formato MAJOR.MINOR.PATCH.
Combinado con Conventional Commits, las herramientas pueden determinar
automaticamente el tipo de release.
| Prefijo del commit | Tipo de cambio | Version bump | Ejemplo |
|---|---|---|---|
fix: | Correccion de bug | PATCH (1.0.0 -> 1.0.1) | fix: resolver timeout en conexion DB |
feat: | Nueva funcionalidad | MINOR (1.0.0 -> 1.1.0) | feat: agregar endpoint de exportacion CSV |
feat!: o BREAKING CHANGE: | Cambio incompatible | MAJOR (1.0.0 -> 2.0.0) | feat!: migrar auth a OAuth2 |
chore: | Mantenimiento | Sin release | chore: actualizar dependencias |
docs: | Documentacion | Sin release | docs: actualizar README de la API |
ci: | Pipeline CI/CD | Sin release | ci: agregar job de security scanning |
Validar commits automaticamente
Usa commitlint con un hook de commit-msg (via Husky o
pre-commit) para rechazar commits que no sigan la convencion. En CI, usa la
action wagoid/commitlint-github-action para validar los commits del PR.
Release automatizado con release-please
release-please de Google analiza los commits desde la ultima release, genera un PR con el changelog y bump de version, y al mergearlo crea automaticamente el GitHub Release con tag.
# .github/workflows/release.yml
name: Release
on:
push:
branches: [main]
permissions:
contents: write
pull-requests: write
jobs:
release:
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
version: ${{ steps.release.outputs.version }}
steps:
- name: Run release-please
id: release
uses: googleapis/release-please-action@v4
with:
release-type: python # o node, go, etc.
token: ${{ secrets.GITHUB_TOKEN }}
# ββ Build y deploy solo cuando se crea un release ββ
deploy:
needs: release
if: needs.release.outputs.release_created == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push release image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ needs.release.outputs.version }}
ghcr.io/${{ github.repository }}:latest
- name: Update infra-repo with release version
run: |
git clone https://x-access-token:${{ secrets.INFRA_REPO_TOKEN }}@github.com/org/infra-repo.git
cd infra-repo/charts/backend
sed -i "s|tag:.*|tag: ${{ needs.release.outputs.version }}|" values-production.yaml
git config user.name "Release Bot"
git config user.email "release@org.com"
git add .
git commit -m "release: deploy backend ${{ needs.release.outputs.version }}"
git push Generacion de changelog
release-please genera automaticamente un CHANGELOG.md con el siguiente formato
basandose en los conventional commits:
## [1.5.0](https://github.com/org/backend/compare/v1.4.2...v1.5.0) (2026-02-20)
### Features
* agregar endpoint de exportacion CSV ([#142](https://github.com/org/backend/pull/142))
* soporte para filtros avanzados en busqueda ([#138](https://github.com/org/backend/pull/138))
### Bug Fixes
* resolver timeout en conexion DB bajo carga ([#140](https://github.com/org/backend/pull/140))
* corregir paginacion en listado de usuarios ([#139](https://github.com/org/backend/pull/139))
### Documentation
* actualizar guia de la API con nuevos endpoints ([#141](https://github.com/org/backend/pull/141)) Alternativa: semantic-release
# .github/workflows/release-semantic.yml β Alternativa con semantic-release
name: Semantic Release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Necesario para analizar historial
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install semantic-release
run: |
npm install -g semantic-release \
@semantic-release/changelog \
@semantic-release/git \
conventional-changelog-conventionalcommits
- name: Run semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npx semantic-release // .releaserc.json β Configuracion de semantic-release
{
"branches": ["main"],
"plugins": [
["@semantic-release/commit-analyzer", {
"preset": "conventionalcommits"
}],
["@semantic-release/release-notes-generator", {
"preset": "conventionalcommits"
}],
["@semantic-release/changelog", {
"changelogFile": "CHANGELOG.md"
}],
["@semantic-release/git", {
"assets": ["CHANGELOG.md", "package.json"],
"message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
}],
"@semantic-release/github"
]
} release-please vs semantic-release
release-please: crea un PR de release que debes mergear manualmente,
dando control sobre cuando publicar. Ideal para equipos que quieren revisar el changelog.
semantic-release: publica automaticamente en cada push a main si hay
commits relevantes. Ideal para proyectos con CI/CD completamente automatizado.