From 7d1ee934ca6f68a550e694785e04d936aba94590 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Thu, 10 Sep 2026 22:03:26 +0200 Subject: [PATCH] ci(release): build et push des images Docker sur le registre Forgejo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le job `sanity-build` (build de `apps/api/Dockerfile` sans push) devient `build-and-push-images` : `docker login` sur le registre Forgejo intégré via le token Actions du run (`permissions: packages: write`), puis build + push des 3 images `batchcooking-{app,intent-service,llm-worker}` taguées `vX.Y.Z` et `latest`. Un job séquentiel plutôt qu'une matrix — 3 builds concurrents saturent le daemon Docker du runner unique (cf. mémoire `ci-forgejo-runner-constraints`) sans gain réel pour une release. `docker-compose.yml` référence maintenant ces images (`image: git.dev.kyuno.fr/kyuno/batchcooking-*:${IMAGE_TAG:-latest}`) avec le bloc `build:` gardé en repli local — Portainer peut tirer les images pré-construites ou continuer à builder depuis Git. `.env.example` documente `IMAGE_TAG` ; le README (section registre + Release) est réécrit. Co-Authored-By: Claude Sonnet 5 --- .env.example | 8 +++++ .github/workflows/release.yml | 66 ++++++++++++++++++++++++++++------- README.md | 34 +++++++++++------- docker-compose.yml | 13 +++++++ 4 files changed, 96 insertions(+), 25 deletions(-) diff --git a/.env.example b/.env.example index 2445596..3f95484 100644 --- a/.env.example +++ b/.env.example @@ -15,6 +15,14 @@ JWT_SECRET=changeme-generate-a-real-random-secret-at-least-32-chars # (docker-compose.yml), serving both the API and the built frontend. # APP_PORT=3000 +# Optional — tag of the prebuilt images the release CI pushes to the Forgejo +# container registry (git.dev.kyuno.fr/kyuno/batchcooking-{app,intent-service, +# llm-worker}). Used by `docker compose pull` and a pull-based Portainer +# stack; leave it `latest` or pin a release tag (e.g. v0.2.4). Ignored when +# building locally instead (`docker compose build`, or `up` with no image +# present) — see docker-compose.yml's `image:`/`build:` comment. +# IMAGE_TAG=latest + # --- Admin surface (apps/web's /admin/* routes + the /admin/* API) -------- # All optional: an instance that doesn't run the admin app needs none of # these. `requireAdmin` fails closed when ADMIN_JWT_SECRET is unset, so diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b87ac85..4ad65dc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,20 +1,34 @@ name: Release -# No image registry involved: Portainer is wired to this repo's Git remote -# and builds/redeploys apps/api/Dockerfile itself. This workflow sanity-checks -# that the image actually builds at the tagged commit, builds+attaches a -# signed Android APK, publishes a Forgejo Release, then (best-effort) pings -# Portainer's deploy webhook so it doesn't have to wait for its own polling -# interval. +# On a `vX.Y.Z` tag: build the three service images and push them to this +# Forgejo instance's built-in container registry (git.dev.kyuno.fr// +# batchcooking-{app,intent-service,llm-worker}, tagged with the release tag +# and `latest`), build+attach a signed Android APK, publish a Forgejo +# Release, then (best-effort) ping Portainer's deploy webhook. Portainer can +# pull those prebuilt images (docker-compose.yml references them via +# ${IMAGE_TAG}) or keep building from the Git remote — `docker-compose.yml` +# keeps a `build:` fallback next to every `image:`. on: push: tags: ["v*.*.*"] permissions: contents: write + # Lets this workflow's GITHUB_TOKEN push to the Forgejo container registry + # (the `build-and-push-images` job below `docker login`s with it). If a + # future instance policy scopes the run token tighter, swap that login's + # password for a `write:package` PAT added as a repo secret. + packages: write jobs: - sanity-build: + # Build the three service images at the tagged commit and push them to the + # Forgejo container registry. A successful push also *is* the "does it + # still build" check the old `sanity-build` job did (which only covered + # apps/api). One sequential job, not a per-image matrix: three concurrent + # `docker build`s hammer this single persistent runner's Docker daemon + # (the contended resource per project memory `ci-forgejo-runner-constraints`) + # for no real gain on a release, which is rare and usually runs alone. + build-and-push-images: runs-on: ubuntu-latest steps: # This runner's job containers don't come with docker preinstalled — @@ -25,10 +39,36 @@ jobs: - uses: https://github.com/actions/checkout@v4 - # Build only, no push, no registry — just confirms Portainer will be - # able to build this same Dockerfile successfully at this tag before - # anyone points it at this ref. - - run: docker build -f apps/api/Dockerfile . + - name: Build et push des 3 images vers le registre Forgejo + env: + # The per-run token Forgejo Actions mints — has `packages: write` + # via this workflow's `permissions:` block. `docker login` accepts + # it as the password for the actor who pushed the tag. + REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -eu + # $GITHUB_SERVER_URL is "https://git.dev.kyuno.fr" — the registry + # is that same host. $GITHUB_REPOSITORY is "/"; the + # registry namespace is the owner, lower-cased (Forgejo package + # names are case-insensitive but the CLI wants a stable form). + registry="${GITHUB_SERVER_URL#http*://}" + owner="$(printf '%s' "${GITHUB_REPOSITORY%%/*}" | tr '[:upper:]' '[:lower:]')" + tag="$GITHUB_REF_NAME" + echo "$REGISTRY_TOKEN" | docker login "$registry" -u "$GITHUB_ACTOR" --password-stdin + + # Every Dockerfile's build context is the repo root (see + # docker-compose.yml's `build.context: .`), hence the trailing `.`. + build_push() { + image="$registry/$owner/$1" + echo "::group::$1" + docker build -f "$2" -t "$image:$tag" -t "$image:latest" . + docker push "$image:$tag" + docker push "$image:latest" + echo "::endgroup::" + } + build_push batchcooking-app apps/api/Dockerfile + build_push batchcooking-intent-service services/tech-step-intent-service/Dockerfile + build_push batchcooking-llm-worker services/tech-step-llm-worker/Dockerfile # Builds the same signed APK as ci.yml's build-android job (issue #64), # with one difference: the version baked into the APK is derived from the @@ -124,11 +164,11 @@ jobs: forgejo-release: runs-on: ubuntu-latest - needs: [sanity-build, build-android-apk] + needs: [build-and-push-images, build-android-apk] steps: # Ce runner ne fournit ni docker ni curl par défaut dans ses # conteneurs de job — même constat déjà fait pour docker (voir - # sanity-build) et pour curl dans ci.yml's test job. + # build-and-push-images) et pour curl dans ci.yml's test job. - name: Install curl run: apt-get update && apt-get install -y curl diff --git a/README.md b/README.md index a8a4f97..013ff9b 100644 --- a/README.md +++ b/README.md @@ -207,10 +207,18 @@ depuis la même origine. Les deux autres services du fichier (`tech-step-intent-service`, `tech-step-llm-worker`) n'exposent eux aucun port au host — voir leurs propres README pour leur rôle. -**Pas de registre d'image** dans cette configuration : l'instance **Portainer** de -production est reliée directement au dépôt Git et reconstruit elle-même -`docker-compose.yml`/`apps/api/Dockerfile` à chaque déploiement — la CI ne pousse -donc aucune image nulle part. +**Registre d'image** : la CI de release (voir plus bas) construit et pousse les +trois images de service — `batchcooking-app`, `batchcooking-intent-service`, +`batchcooking-llm-worker` — sur le **registre de conteneurs intégré à Forgejo** +(`git.dev.kyuno.fr/kyuno/…`), taguées à la fois avec le tag de release (`vX.Y.Z`) +et `latest`. `docker-compose.yml` référence ces images via `IMAGE_TAG` (variable +d'environnement, défaut `latest` — voir `.env.example`), tout en gardant un bloc +`build:` local à côté de chaque `image:` : `docker compose pull` récupère les +images pré-construites, `docker compose up`/`build` retombe sur un build local si +elles sont absentes. L'instance **Portainer** de production peut donc désormais +tirer les images pré-construites (stack en mode *pull*, `IMAGE_TAG` pinné sur la +release voulue) **ou** continuer à builder elle-même depuis le dépôt Git — les +deux modes fonctionnent avec le même `docker-compose.yml`. ### Release (`.github/workflows/release.yml`) @@ -221,14 +229,16 @@ git tag vX.Y.Z git push --tags ``` -Le pipeline enchaîne trois jobs : `sanity-build` (build de l'image Docker sans -push, juste pour vérifier qu'elle build encore à ce tag avant de laisser Portainer -redéployer dessus), `github-release` (crée une Release GitHub avec changelog -auto-généré à partir des PRs mergées), puis `notify-portainer` — envoie une requête -au webhook de redeploy de Portainer si le secret de dépôt `PORTAINER_WEBHOOK_URL` -est configuré (sinon Portainer se resynchronise simplement à son prochain -polling Git). Pour l'activer : récupérer l'URL du webhook depuis les réglages du -stack Portainer, puis l'ajouter comme secret GitHub `PORTAINER_WEBHOOK_URL`. +Le pipeline enchaîne quatre jobs : `build-and-push-images` (build des 3 images à +ce tag + push sur le registre Forgejo sous `vX.Y.Z` et `latest` — un push +réussi vaut aussi vérification que tout build encore à ce commit), +`build-android-apk` (APK signé, `versionName` dérivé du tag), `forgejo-release` +(crée la Release Forgejo `vX.Y.Z` et y attache `batchcooking.apk`), puis +`notify-portainer` — envoie une requête au webhook de redeploy de Portainer si +le secret de dépôt `PORTAINER_WEBHOOK_URL` est configuré (sinon Portainer se +resynchronise à son prochain polling). Pour l'activer : récupérer l'URL du +webhook depuis les réglages du stack Portainer, puis l'ajouter comme secret de +dépôt `PORTAINER_WEBHOOK_URL`. ## Application mobile (Android) diff --git a/docker-compose.yml b/docker-compose.yml index 05042bc..204bb96 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,6 +23,13 @@ services: # apps/api/Dockerfile) — no separate nginx/web container, no cross-origin # CORS_ORIGIN to keep in sync between two ports. app: + # Prebuilt image pushed by .github/workflows/release.yml to the Forgejo + # container registry on every `vX.Y.Z` tag. `IMAGE_TAG` (root .env, + # default `latest`) pins which release to run — a pull-based Portainer + # stack / `docker compose pull` uses this. The `build:` block below stays + # as a local fallback: `docker compose up` builds it if the image isn't + # present, `docker compose build` (re)creates it under this same name. + image: git.dev.kyuno.fr/kyuno/batchcooking-app:${IMAGE_TAG:-latest} build: context: . dockerfile: apps/api/Dockerfile @@ -87,6 +94,9 @@ services: # port — reachable only from `app` on the compose network, nothing ever # calls into it from outside. tech-step-intent-service: + # See the `app` service's `image:` comment — same release-pushed / local + # fallback split, shared `IMAGE_TAG`. + image: git.dev.kyuno.fr/kyuno/batchcooking-intent-service:${IMAGE_TAG:-latest} build: context: . dockerfile: services/tech-step-intent-service/Dockerfile @@ -131,6 +141,9 @@ services: # less deployment can omit this service entirely and `app` still runs # fine, just without the offline audit/feedback-loop jobs. tech-step-llm-worker: + # See the `app` service's `image:` comment — same release-pushed / local + # fallback split, shared `IMAGE_TAG`. + image: git.dev.kyuno.fr/kyuno/batchcooking-llm-worker:${IMAGE_TAG:-latest} build: context: . dockerfile: services/tech-step-llm-worker/Dockerfile -- 2.45.2