CI/CD: Pipelines, Ambientes
y Estrategia de Deploy
Documentación completa del pipeline de integración continua y despliegue de raise-commons — estado al 2026-08-21. Base para la migración del backend de Fly.io a GCP Cloud Run.
Modelo de Branches
Flujo de tres niveles. Las ramas de historia/bug se crean desde release/3.1.0 y vuelven a ella via MR. La rama main solo recibe merges de release.
flowchart TD
S["story/sN.M/slug\nbug/RAISE-N/slug"]
R["release/3.1.0\ndesarrollo activo · MR target"]
M["main\nestable · solo merges de release"]
S -->|"MR → merge"| R
R -->|"merge en release"| M
M -.->|"hotfix"| R
| Branch | Rol | Pipeline disparado | Merge target |
|---|---|---|---|
main |
Estable — tag de producción vive aquí | Full suite (legacy config) | — |
release/3.1.0 |
Desarrollo activo. Todos los MRs apuntan aquí | Fast MR + Full + Deploy a Dev | main |
story/sN.M/slug |
Feature / historia de sprint | Fast MR al crear MR | release/3.1.0 |
bug/RAISE-N/slug |
Bugfix | Fast MR al crear MR | release/3.1.0 |
ambiente-qa-gcp |
Branch dedicado a QA en GCP Cloud Run | Build + deploy a GCP QA | release/3.1.0 |
Perfiles de CI
Dos modos de operación según el contexto: un fast path para MRs (~3–5 min) y una suite completa para pre-release. Los jobs de seguridad están temporalmente deshabilitados.
Corre en todo MR hacia release/*. Excluye tests lentos, de integración, ML y E2E. Usa -n 4 (paralelo) y --no-cov.
| Job | Duración | Shards |
|---|---|---|
test:raise-cli-fast | ~3.5 min | ×3 |
test:raise-core | ~1 min | ×1 |
test:raise-server-fast | ~1 min | ×1 |
test:raise-cli-no-xdist | ~1 min | ×1 |
check-migrations | ~30 s | ×1 |
test:docs-guards | ~1 min | ×1 |
Corre en push directo a release/3.1.0, schedules o manual desde release/*. Incluye tests lentos e integración.
| Job | Markers incluidos |
|---|---|
test:raise-cli-full | Todo excepto ml, e2e, no_xdist |
test:raise-server-full | Todo excepto tests/e2e/ |
test:raise-cli-recall-gate | Solo ml — serial, ONNX model |
Markers de pytest y cobertura
| Marker | Qué contiene | Fast MR | Full |
|---|---|---|---|
slow |
Tests de carga, ciclos largos, init de grafo | EXCLUIDO | INCLUIDO |
integration |
Tests que requieren Jira/Confluence real o SQLite con datos | EXCLUIDO | INCLUIDO |
ml |
Tests que requieren modelo ONNX multilingual-e5 | EXCLUIDO | SEPARADO recall-gate |
e2e |
Flujos CLI completos y E2E del servidor | EXCLUIDO | SOLO deployed |
no_xdist |
Tests que no pueden correr en paralelo (xdist incompatible) | JOB PROPIO | JOB PROPIO |
Estrategia de Cache
| Perfil | Policy | Key | Paths |
|---|---|---|---|
| Fast MR | pull |
$PYTHON_ABI-$JOB_NAME + uv.lock |
.uv-cache/ |
| Full pre-release | pull-push |
Mismo prefix que fast (compartido) | .uv-cache/ |
test:raise-cli-fast y test:raise-cli-full comparten el prefix test:raise-cli. Un rename sin mantener el prefix crea una cache nueva y provoca regresiones por cache miss.
Dev — Fly.io (raise-server.fly.dev)
Primer ambiente de integración real. Se despliega al crear un tag dev. Corre smoke + E2E deployed antes de poder promover a producción.
git tag v3.1.0-dev.20260821 git push origin v3.1.0-dev.20260821
El tag dispara el job deploy:dev que usa fly.staging.toml. Requiere que pasen test:raise-server-full y guard:tag-ancestry primero.
Configuración Fly.io (fly.staging.toml)
app = 'raise-server'
primary_region = 'lax'
[deploy]
release_command = "sh -c 'cd /app/packages/raise-server && alembic upgrade head'"
# Migración en máquina efímera ANTES del corte (ADR-093)
# Sin esto → drift schema↔código (patrón de error RAISE-11462)
[env]
RAI_ENV = 'staging'
RAI_LOG_LEVEL = 'INFO'
RAI_PORT = '8080'
# RAI_DATABASE_URL — Fly secret (fly postgres attach)
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = 'off' # nunca hibernar en staging
min_machines_running = 1
[[http_service.checks]]
interval = '15s'
timeout = '5s'
grace_period = '45s' # buffer para alembic en cold start
method = 'GET'
path = '/health'
[[vm]]
size = 'shared-cpu-1x'
memory = '512mb'
Smoke Check en Deploy
| Check | Endpoint | Criterio | Timeout |
|---|---|---|---|
| Health | GET /health | {"status":"ok"} | 90 s |
| Database | GET /health | {"database":"connected"} | — |
| Version | GET /health | version presente | — |
| API v2 | GET /api/v2/projects | HTTP ≠ 000 | — |
| Migration drift | flyctl ssh console | alembic current == heads | — |
e2e:dev corre contra raise-server.fly.dev con tests marcados @pytest.mark.deployed en packages/raise-server/tests/e2e/deployed/. Falla → bloquea promoción a producción.
QA — GCP Cloud Run
Ambiente de validación en Google Cloud Platform. Ya está parcialmente operativo — es la base del plan de migración. Trigger: push a la branch dedicada ambiente-qa-gcp.
build:gcp-qa, deploy:gcp-qa y smoke:gcp-qa están activos en .gitlab-ci.yml. El QA de Cloud Run ya existe y ya funciona — esta sección describe exactamente cómo.
git push origin ambiente-qa-gcp
Automático en push. No requiere tag. La migración de BD (migrate:gcp-qa) es manual — se ejecuta desde GitLab CI.
Flujo de Build — Kaniko
# Autenticación en Artifact Registry (sin Docker daemon)
echo '{"auths":{"northamerica-south1-docker.pkg.dev": {"auth":"..."}}}' \
> /kaniko/.docker/config.json
# Build multi-stage (target=server) + push con dos tags
/kaniko/executor \
--context "${CI_PROJECT_DIR}" \
--dockerfile "${CI_PROJECT_DIR}/Dockerfile" \
--target server \
--build-arg "GIT_SHA=${CI_COMMIT_SHA}" \
--build-arg "BUILD_DATE=$(date -u +%F)" \
--destination "northamerica-south1-docker.pkg.dev/vaultwarden-server-485620/raise-docker-qa/raise-server:${CI_COMMIT_SHORT_SHA}" \
--destination "northamerica-south1-docker.pkg.dev/vaultwarden-server-485620/raise-docker-qa/raise-server:latest" \
--cache=true \
--skip-unused-stages
Migración de Base de Datos (Cloud Run Job)
migrate:gcp-qa tiene when: manual. Hay que ejecutarlo explícitamente desde el pipeline de GitLab antes (o después) del deploy, según sea necesario. Esto es intencional para QA — en el ambiente de producción se deberá automatizar con ordenamiento de jobs (como lo hace Fly.io con release_command).
# Autenticar con service account echo "$GCP_SA_KEY_QA" | gcloud auth activate-service-account --key-file=- gcloud config set project vaultwarden-server-485620 # Actualizar imagen del migration job gcloud run jobs update raise-migrate-qa \ --image "$GCP_REGISTRY/raise-server:$CI_COMMIT_SHORT_SHA" \ --command "sh,-c,cd /app/packages/raise-server && alembic upgrade head" \ --region northamerica-south1 --quiet # Ejecutar y esperar a que termine gcloud run jobs execute raise-migrate-qa \ --region northamerica-south1 --wait --quiet
Deploy a Cloud Run
gcloud run deploy raise-server-qa \ --image "$GCP_REGISTRY/raise-server:$CI_COMMIT_SHORT_SHA" \ --region northamerica-south1 \ --platform managed \ --quiet
Producción — Fly.io (raise-server-prod.fly.dev)
Ambiente de producción. Solo se despliega con tags de release (RC o final) y requiere aprobación manual en GitLab antes de ejecutar el deploy.
# Release Candidate git tag v3.1.0-rc.1 # Release Final git tag v3.1.0
El job tiene when: manual. El tag crea el pipeline, pero un humano debe aprobar el deploy desde la UI de GitLab.
fly.production.toml define strategy = 'bluegreen' en la sección [deploy], pero el job CI pasa --strategy rolling en la línea de comando. El flag CLI tiene precedencia — el deploy efectivo de producción usa rolling, no bluegreen. Esto debe resolverse antes de la migración: decidir cuál es la estrategia real deseada.
Configuración Fly.io (fly.production.toml)
app = 'raise-server-prod'
primary_region = 'lax'
[deploy]
release_command = "sh -c 'cd /app/packages/raise-server && alembic upgrade head'"
strategy = 'bluegreen' # ⚠ SOBREESCRITO por --strategy rolling en CI
[env]
RAI_ENV = 'production'
RAI_LOG_LEVEL = 'WARNING'
RAI_PORT = '8080'
RAI_FEATURE_CARTRIDGES = 'off' # kill-switch de feature flags (ADR-093)
# RAI_DATABASE_URL → Fly secret
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = 'off'
min_machines_running = 1
[[http_service.checks]]
interval = '15s'
timeout = '5s'
grace_period = '45s'
method = 'GET'
path = '/health'
[[vm]]
size = 'shared-cpu-1x'
memory = '2048mb' # 4× más que staging
Admin — Cloudflare Pages
El frontend raise-admin (SPA Vite/React) se despliega a Cloudflare Pages. Existen tres variantes: dev automático, QA (GCP) y producción manual.
| Variante | URL | Trigger | Backend | Gate |
|---|---|---|---|---|
| Dev | raise-admin-dev.pages.dev |
Push release/3.1.0 con cambios en packages/raise-admin/ |
raise-server.fly.dev |
AUTO |
| QA | Cloudflare Pages QA project | Push ambiente-qa-gcp con cambios en admin |
raise-server-qa-333...run.app |
AUTO |
| Producción | raise.sh |
Push release/3.1.0 con cambios en admin |
api.raise.sh |
MANUAL |
Diagramas de Deploy
Secuencia completa de jobs según el trigger. Cada flujo es independiente — los triggers no se solapan.
Flujo 1 — MR Fast Path
Corre en todo MR hacia release/*. Sólo test stage — sin build ni deploy.
flowchart LR
trigger["MR → release/*"]
trigger --> t1["test:raise-cli-fast\n×3 shards paralelos"]
trigger --> t2["test:raise-core"]
trigger --> t3["test:raise-server-fast"]
trigger --> t4["test:raise-cli-no-xdist"]
trigger --> t5["check-migrations"]
trigger --> t6["test:docs-guards"]
t1 & t2 & t3 & t4 & t5 & t6 --> done["MR verde\n~3–5 min"]
Flujo 2 — Push a release/3.1.0
Corre la suite full + si hay cambios en archivos relevantes, despliega a Dev (Fly.io).
flowchart TD
push["push → release/3.1.0"]
push --> full["test:raise-cli-full\ntest:raise-server-full\ntest:raise-cli-recall-gate"]
push --> guard["check-migrations\nbundle-guard:customui"]
push --> admin["build:admin → deploy:admin-dev\n(si cambia packages/raise-admin)"]
push --> docs["docs-deploy\n(si cambian ADRs / portal)"]
Flujo 3 — Tag Dev → Fly.io
Tag v*.*.*-dev.YYYYMMDD. El primer ambiente con deploy real. E2E al final bloquea la promoción.
flowchart LR
tag["git tag\nv3.1.0-dev.YYYYMMDD"]
tag --> A["test:raise-server-full"]
tag --> B["guard:tag-ancestry"]
tag --> C["build:onnx-model\nallow_failure: true"]
A & B & C --> D["deploy:dev\nfly.staging.toml\napp: raise-server\nstrategy: rolling\nrunner: jinkoniwashi"]
D --> E["smoke:dev\n/health · /api/v2/projects\nalembic drift · 90s"]
E --> F["e2e:dev\ndeployed tests"]
Flujo 4 — Tag RC/Release → Producción Fly.io
Tag de release. El deploy requiere aprobación manual en GitLab. No hay E2E post-production.
flowchart LR
tag["git tag v3.1.0-rc.N\no v3.1.0"]
tag --> A["test:raise-server-full"]
tag --> B["guard:tag-ancestry"]
tag --> C["build:onnx-model\nallow_failure: true"]
A & B & C --> GATE["MANUAL\naprobación en GitLab"]
GATE --> D["deploy:production\nfly.production.toml\napp: raise-server-prod\nstrategy: rolling\nrunner: docker"]
D --> E["smoke:production\n/health · 90s"]
Flujo 5 — Branch GCP QA
Push a ambiente-qa-gcp. Build con Kaniko, migración manual, deploy a Cloud Run.
flowchart LR
push["push → ambiente-qa-gcp"]
push --> O["build:onnx-model\nallow_failure: true"]
push --> AB["build:admin-qa\nCloudflare Pages QA"]
O --> K["build:gcp-qa\nKaniko → Artifact Registry\nnortamerica-south1"]
K --> M["migrate:gcp-qa\nMANUAL\nCloud Run Job"]
K --> D["deploy:gcp-qa\ngcloud run deploy\nraise-server-qa"]
D --> S["smoke:gcp-qa\n/health · 120s"]
AB --> DAD["deploy:admin-qa\nCloudflare Pages"]
Dockerfile y Stack de Servicios
Un único Dockerfile multi-target para todo el monorepo. Estrategia de capas ordenada por frecuencia de cambio para optimizar la cache de Kaniko.
Targets del Dockerfile
| Target | Qué despliega | Puerto | Entrypoint |
|---|---|---|---|
server |
raise-server — API REST del knowledge graph | 8080 | uvicorn raise_server.app:app |
daemon |
rai-agent — Telegram bot + pipeline engine | 8000 | python -m rai_agent.daemon |
Estrategia de Capas (Layer Cache)
| Capa | Contenido | Frecuencia de cambio |
|---|---|---|
| Base | Python 3.13-slim + uv + usuario rai | Muy baja (imagen base) |
| Layer 1 | Todos los pyproject.toml + uv.lock | Baja (solo con deps) |
| Layer 2 | Third-party deps vía uv sync --no-install-workspace | Baja (solo con deps) |
| Layer 3 | Código fuente (packages/) | Alta (cada commit) |
| Layer 4 | Link de workspace packages en venv | Alta (~5s, siempre reconstruye) |
Apps Fly.io en el ecosistema
| App | URL | Stack | Config |
|---|---|---|---|
raise-server |
raise-server.fly.dev | raise-server (Python/FastAPI) | fly.staging.toml |
raise-server-prod |
raise-server-prod.fly.dev | raise-server (Python/FastAPI) | fly.production.toml |
raise-pmo |
— | Caddy + hermes-gateway + commitment-server + litellm | infra/fly.toml (compose) |
Patrón de Migración DB (crítico para GCP)
El patrón de Fly.io ejecuta Alembic antes del corte de tráfico en una máquina efímera. En GCP QA esto se hace manualmente con un Cloud Run Job. Para producción en GCP, el Job debe ejecutarse y esperar éxito antes del gcloud run deploy.
release_command = "sh -c 'cd \ /app/packages/raise-server \ && alembic upgrade head'" # ↑ Corre en máquina efímera. # Si falla → deploy cancelado. # Tráfico nunca se corta.
gcloud run jobs execute \ raise-migrate-prod \ --region northamerica-south1 \ --wait --quiet # ← bloqueante # Si exit != 0 → abortar. # Solo después: gcloud run deploy raise-server-prod \ ...
Secrets y Variables
Variables configuradas en GitLab Settings → CI/CD → Variables. Las marcadas como Protected solo están disponibles en branches/tags protegidos.
| Variable | Usado por | Tipo | Scope |
|---|---|---|---|
FLY_API_TOKEN_STAGING |
deploy:dev, smoke:dev |
Masked + Protected | Tag v*.*.*-dev.* |
FLY_API_TOKEN_PRODUCTION |
deploy:production, smoke:production |
Masked + Protected | Tag v*.*.* |
GCP_SA_KEY_QA |
build:gcp-qa, migrate:gcp-qa, deploy:gcp-qa |
Masked + Protected | Branch ambiente-qa-gcp |
CLOUDFLARE_API_TOKEN |
deploy:admin-* |
Masked | release/3.1.0 |
CLOUDFLARE_ACCOUNT_ID |
deploy:admin-* |
Variable | release/3.1.0 |
ADMIN_QA_VITE_API_URL |
build:admin-qa |
Variable | ambiente-qa-gcp |
RAISE_ALPHA_DEPLOY_TOKEN |
nightly:publish:public |
Masked | Schedule |
RAISE_CI_PYTHON_IMAGE |
Todos los jobs de test | Variable | Global |
RAI_DATABASE_URL |
raise-server (en Fly.io como secret) | Fly secret | Fly.io secrets — NO en GitLab CI |
RAI_DATABASE_URL no vive en GitLab CI — es un Fly.io secret, inyectado via fly postgres attach. Para GCP, debe configurarse como variable de entorno en Cloud Run o como Secret Manager ref.
Deuda Técnica — Jobs Deshabilitados
Seis jobs de seguridad fueron comentados en .gitlab-ci.yml durante la fase de desarrollo activo de v3 para mantener ciclos de iteración cortos. La migración a GCP es una oportunidad para reactivarlos.
| Job | Qué detecta | Riesgo sin él |
|---|---|---|
snyk:sca |
CVEs en dependencias (requirements, uv.lock) | ALTO — dependencias con vulnerabilidades conocidas pasan silenciosamente |
snyk:sast |
Vulnerabilidades en código fuente (SQL injection, path traversal, etc.) | ALTO — código nuevo no auditado por seguridad |
snyk:iac |
Misconfigurations en archivos IaC (Dockerfile, fly.toml, etc.) | MEDIO — configuraciones inseguras en infra no detectadas |
sonarqube:scan |
Calidad de código + seguridad estática acumulada | MEDIO — sin métricas de calidad histórica ni technical debt tracking |
| GitLab SAST template | SAST adicional via GitLab built-in | BAJO — redundante con Snyk SAST, pero capa adicional |
| Dependency Scanning template | Vulnerabilidades en dependencias vía GitLab built-in | BAJO — redundante con Snyk SCA |
main (pipeline para branch dev) tiene Snyk y SonarQube activos — es la referencia de lo que debería correr cuando se reactiven. Para una primera reactivación, allow_failure: true permite visibilidad sin bloquear el CI.
Referencia para Migración a GCP
Lo que ya existe en GCP, lo que hace falta replicar, y los patrones críticos a respetar durante la transición.
ambiente-qa-gcp) ya existe y está validado. La migración consiste en replicar ese patrón para los ambientes dev y producción, y afinar los detalles operativos.
Estado actual por componente
| Componente | Estado en GCP | Pendiente |
|---|---|---|
| raise-server QA | EXISTE Cloud Run, northamerica-south1 | — |
| Artifact Registry | EXISTE raise-docker-qa |
Crear repo raise-docker-dev y raise-docker-prod |
| Cloud Run Job (migrate) | EXISTE raise-migrate-qa |
Crear raise-migrate-dev y raise-migrate-prod |
| Service Account | EXISTE raise-deployer-qa |
Crear SA para dev y prod con scope mínimo |
| raise-server Dev | PENDIENTE | Cloud Run service raise-server-dev |
| raise-server Prod | PENDIENTE | Cloud Run service raise-server-prod + domain mapping |
| GitLab CI dev job | PENDIENTE | Replicar deploy:dev apuntando a Cloud Run en vez de Fly.io |
| GitLab CI prod job | PENDIENTE | Replicar deploy:production con manual gate y Cloud Run |
| Smoke post-deploy | PARCIAL existe en QA (básico) | Replicar el smoke completo de Fly.io (alembic drift check) |
| RAI_DATABASE_URL | PENDIENTE | Configurar en Cloud Run (env var o Secret Manager) |
Patrones críticos a respetar
En Fly.io el release_command garantiza que Alembic corre en máquina efímera antes del corte. En GCP, el Cloud Run Job debe ejecutarse y completar con éxito antes de que gcloud run deploy enrute tráfico a la nueva imagen.
El smoke de Fly.io verifica: /health status:ok, database:connected, /api/v2/projects accesible, y alembic current == heads. El smoke de GCP QA actual solo verifica /health. Extender antes de migrar dev/prod.
El modelo ONNX se sube como artifact en el stage prepare (job build:onnx-model, allow_failure: true). El Dockerfile tiene fallback a HuggingFace Hub si el artifact llega vacío. Este patrón ya funciona en build:gcp-qa — reusarlo para dev/prod.
El deploy a producción siempre requiere aprobación manual en GitLab (when: manual). No automatizar este gate en la migración — es un control deliberado, no una limitación técnica.
Variables de entorno a configurar en Cloud Run
| Variable | Valor Dev | Valor Prod | Fuente |
|---|---|---|---|
RAI_ENV |
staging |
production |
Hardcoded en CI (como --set-env-vars) |
RAI_LOG_LEVEL |
INFO |
WARNING |
Hardcoded en CI |
RAI_PORT |
8080 |
8080 |
Cloud Run ya usa 8080 por default |
RAI_DATABASE_URL |
PostgreSQL dev | PostgreSQL prod | Secret Manager → Cloud Run secret ref |
RAI_FEATURE_CARTRIDGES |
off |
off |
Kill-switch ADR-093 — mantener off hasta decisión T3 |
Health check en Cloud Run
gcloud run deploy raise-server-prod \ --image "$GCP_REGISTRY/raise-server:$CI_COMMIT_SHORT_SHA" \ --region northamerica-south1 \ --platform managed \ --port 8080 \ --set-env-vars "RAI_ENV=production,RAI_LOG_LEVEL=WARNING" \ --set-secrets "RAI_DATABASE_URL=raise-db-url-prod:latest" \ # Cloud Run health check — equivale al [[http_service.checks]] de Fly.io # Se configura en el servicio, no en el deploy command: # Startup probe: GET /health, initial delay 45s (equivale a grace_period) # Liveness probe: GET /health, interval 15s, timeout 5s, failures 3 --quiet
northamerica-south1 (México) — región apropiada para el equipo. Fly.io usa lax (Los Ángeles). La latencia percibida debería mejorar para usuarios en MX con la migración a GCP México.