Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 68 additions & 4 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -894,22 +910,70 @@ 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.
#
# Everyone READS the cache; only publishing refs WRITE it. Three images
# at mode=max is roughly a gigabyte per run, and the repo's Actions
# cache is a single 10 GB pool already holding ~1.7 GB that the test
# jobs depend on. Letting every PR write its own copy would evict those
# npm caches by LRU and show up as the test suites mysteriously slowing
# down. This way a PR warms itself from main and writes nothing.
cache-from: type=gha,scope=${{ matrix.service }}
cache-to: ${{ env.PUBLISH == 'true' && format('type=gha,mode=max,scope={0}', 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.
#
# ONLY `success` passes, and that is deliberate. The first version of this job
# let `skipped` through, reasoning that a skip means an upstream suite failed
# and that failure already carries its own red check. The premise was true and
# the conclusion was wrong: `docker-services` needs seven test jobs, and two of
# them -- `test-sdk-client` and `test-infra-providers` -- are NOT required
# contexts on main or develop. So a broken Dockerfile arriving together with a
# single flake in either suite would skip the build, report this check GREEN
# having built nothing, and leave all eight required contexts satisfied. That is
# the exact failure this job exists to prevent, wearing a required-check badge.
#
# A required check must assert that the build RAN, not merely that it did not
# fail. Two reds instead of one is not double-reporting; it is this check
# declining to vouch for something it never saw.
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}"
if [ "${result}" != "success" ]; then
echo "::error::este check no puede afirmar nada: el build de imagenes termino en '${result}', no en 'success'"
exit 1
fi
echo "las tres imagenes de servicio construyen"

# 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
Expand Down
21 changes: 14 additions & 7 deletions reference/core/control-center/deploy/README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,17 @@ 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` + `:<sha>` 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` + `:<sha>`
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. Pasa **solo** en `success`: un build saltado significa que el check
no vio nada, y entonces no puede responder por nada.
3. **`Deploy services (Coolify)`** (job `deploy`, `needs: [docker-services]`) —
el paso de promoción. Su guarda es:

Expand Down Expand Up @@ -194,9 +201,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.
Expand Down
20 changes: 14 additions & 6 deletions reference/core/control-center/deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,17 @@ 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` + `:<sha>` 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` +
`:<sha>` 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. It
passes **only** on `success`: a skipped build means the check saw nothing and
must not vouch for anything.
3. **`Deploy services (Coolify)`** (job `deploy`, `needs: [docker-services]`) —
the promotion step. Its guard is:

Expand Down Expand Up @@ -188,8 +195,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.
Expand Down
Loading