diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 5cf1c42e0..2a8e14442 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -860,14 +860,30 @@ jobs: # workspaces, in dependency order, with --provenance, idempotent against the # registry, and rehearsable via a dry run that defaults to on. + # GT-679 — the image build now runs on the PR, not after it. + # + # This job was gated `if: ref == main || tags`, which meant the question "does + # this still build?" was asked only AFTER every gate that could have stopped + # the change had already let it through. That is not a theoretical ordering + # complaint: a broken agent-runtime Dockerfile crossed PRs #669, #670 and #673 + # carrying 46, 78 and 47 green checks, and was found on main, three promotions + # later, by a human reading the registry. + # + # So the BUILD runs everywhere and the PUSH stays ref-guarded. A pull request + # proves all three images compile; only main and version tags publish to GHCR, + # which is the same publishing behaviour this job had before. docker-services: - name: Build & Push Services (GHCR) + name: Build Services (GHCR) needs: [test, test-core-domain, test-core, test-mcp-server, test-core-api, test-sdk-client, test-infra-providers] runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') permissions: contents: read packages: write + env: + # Single source of truth for "is this a publishing ref?", read by both the + # login step and the push flag below. On a pull_request `github.ref` is + # refs/pull/N/merge, so this is false and nothing reaches the registry. + PUBLISH: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }} strategy: fail-fast: false matrix: @@ -894,22 +910,58 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Login to GHCR + if: env.PUBLISH == 'true' uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push ${{ matrix.service }} + - name: Build ${{ matrix.service }} (push only from main/tags) uses: docker/build-push-action@v5 with: context: ${{ matrix.context }} file: ${{ matrix.dockerfile }} - push: true + push: ${{ env.PUBLISH == 'true' }} + # Now that this runs on every PR, an uncached three-image rebuild would + # be the slowest thing in the pipeline. Scoped per service so the three + # matrix legs do not evict each other. + cache-from: type=gha,scope=${{ matrix.service }} + cache-to: type=gha,mode=max,scope=${{ matrix.service }} tags: | ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}:latest ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}:${{ github.sha }} + # A matrix job reports one check per leg, and each context carries its matrix + # values -- "Build Services (GHCR) (core-api, ., ./src/apps/core-api/Dockerfile)". + # Those names change whenever a path or a service does, so they are a poor thing + # for branch protection to name. This job collapses the three into one stable + # context that CAN be marked required. + docker-services-gate: + name: Services build (GHCR) + needs: [docker-services] + runs-on: ubuntu-latest + if: always() + steps: + - name: Assert every service image built + run: | + result='${{ needs.docker-services.result }}' + echo "docker-services: ${result}" + case "${result}" in + success) + echo "las tres imagenes construyen" + ;; + skipped) + # An upstream test suite failed, so the build never ran. That + # failure already has its own red check; do not double-report it. + echo "no se construyo: una suite previa fallo y lo reporta su propio check" + ;; + *) + echo "::error::al menos una imagen de servicio no construye" + exit 1 + ;; + esac + # GT-324 — deploy the freshly-pushed images to the Coolify runtime. # # REWRITTEN 2026-08-22. The previous version fanned out to THREE per-service deploy diff --git a/reference/core/control-center/deploy/README.es.md b/reference/core/control-center/deploy/README.es.md index 4c54eb4f3..9cfc3f066 100644 --- a/reference/core/control-center/deploy/README.es.md +++ b/reference/core/control-center/deploy/README.es.md @@ -68,10 +68,16 @@ Fuente de verdad: `.github/workflows/ci-cd.yml`. 1. **Build & test** — los jobs `Test`, `Test core-domain`, `Test core`, `Test mcp-server`, `Test core-api`, `Test sdk-client`, `Test contract`, `Test infra-providers` corren en cada PR a `main`/`develop`. -2. **`Build & Push Services (GHCR)`** (job `docker-services`) — corre solo en - `main` o tags `v*`. Construye `core-api`, `mcp-server`, `agent-runtime` y - publica `:latest` + `:` en GHCR usando el `GITHUB_TOKEN` incorporado (sin - secret extra). +2. **`Build Services (GHCR)`** (job `docker-services`) — construye `core-api`, + `mcp-server` y `agent-runtime` en **cada PR**, y publica `:latest` + `:` + en GHCR solo desde `main` o tags `v*`, usando el `GITHUB_TOKEN` incorporado + (sin secret extra). El build era solo-main hasta GT-679; un Dockerfile roto + cruzó tres PRs en verde porque el único job que lo habría detenido corría + después de que ya hubieran mergeado. Las tres ramas del matrix se colapsan en + un único check estable, **`Services build (GHCR)`** (job + `docker-services-gate`), que es el contexto que nombra la protección de rama: + los contextos de matrix llevan sus parámetros y cambian cada vez que cambia + una ruta. 3. **`Deploy services (Coolify)`** (job `deploy`, `needs: [docker-services]`) — el paso de promoción. Su guarda es: @@ -194,9 +200,9 @@ el go-live irreversible y le corresponde ejecutarlo al usuario.** Con estos definidos y `VPS_DEPLOY_ENABLED` aún sin definir, el job de deploy sigue saltándose — nada se despliega todavía. 3. **Mergea un cambio normal a `main`** (o vuelve a ejecutar el workflow) y confirma - que el job `Build & Push Services (GHCR)` está en verde y las tres imágenes aparecen - en GHCR. El job `deploy` debería seguir mostrándose como **skipped** (la guarda está - apagada). + que el job `Build Services (GHCR)` está en verde y las tres imágenes aparecen en + GHCR (en un PR el mismo job va en verde sin publicar nada). El job `deploy` debería + seguir mostrándose como **skipped** (la guarda está apagada). 4. **Opcionalmente despliega una vez manualmente desde la UI de Coolify** (cada app → Deploy) para probar que las imágenes arrancan y el env de runtime es correcto, antes de cablear el disparador automático. Observa que cada `/health` pase a verde. diff --git a/reference/core/control-center/deploy/README.md b/reference/core/control-center/deploy/README.md index 6b56e1cb4..c045b4413 100644 --- a/reference/core/control-center/deploy/README.md +++ b/reference/core/control-center/deploy/README.md @@ -62,10 +62,15 @@ Source of truth: `.github/workflows/ci-cd.yml`. 1. **Build & test** — jobs `Test`, `Test core-domain`, `Test core`, `Test mcp-server`, `Test core-api`, `Test sdk-client`, `Test contract`, `Test infra-providers` run on every PR to `main`/`develop`. -2. **`Build & Push Services (GHCR)`** (job `docker-services`) — runs only on - `main` or `v*` tags. Builds `core-api`, `mcp-server`, `agent-runtime` and - pushes `:latest` + `:` to GHCR using the built-in `GITHUB_TOKEN` (no - extra secret). +2. **`Build Services (GHCR)`** (job `docker-services`) — builds `core-api`, + `mcp-server` and `agent-runtime` on **every PR**, and pushes `:latest` + + `:` to GHCR only from `main` or `v*` tags, using the built-in + `GITHUB_TOKEN` (no extra secret). The build was main-only until GT-679; a + broken Dockerfile crossed three PRs with green checks because the only job + that would have caught it ran after they had already merged. The three matrix + legs are collapsed into one stable check, **`Services build (GHCR)`** (job + `docker-services-gate`), which is the context branch protection names — + matrix contexts carry their parameters and change whenever a path does. 3. **`Deploy services (Coolify)`** (job `deploy`, `needs: [docker-services]`) — the promotion step. Its guard is: @@ -188,8 +193,9 @@ irreversible go-live and is the user's to run.** With these set and `VPS_DEPLOY_ENABLED` still unset, the deploy job stays skipped — nothing deploys yet. 3. **Merge a normal change to `main`** (or re-run the workflow) and confirm the - `Build & Push Services (GHCR)` job is green and the three images appear in - GHCR. The `deploy` job should still show as **skipped** (guard is off). + `Build Services (GHCR)` job is green and the three images appear in GHCR (on a + PR the same job is green without publishing anything). The `deploy` job should + still show as **skipped** (guard is off). 4. **Optionally deploy once manually from the Coolify UI** (each app → Deploy) to prove the images boot and the runtime env is correct, before wiring the automatic trigger. Watch each `/health` go green.