CI/CD — Pipelines Woodpecker Kohinos
Le moteur d'intégration continue de Kohinos est Woodpecker CI self-host
(cf. ADR 0028-ci-engine-woodpecker.md) —
souveraineté, priorité 2 du projet. Le serveur Woodpecker et ses agents tournent sur
l'infrastructure de la Fédération (VPS 4 Go ou Raspberry Pi 5), sans aucun service
cloud propriétaire dans le path critique. Les pipelines vivent dans .woodpecker/*.yml
et sont déclenchés par événement (pull request, push main, tag, cron, manuel).
Aucun déploiement production sans pipeline verte et action manuelle explicite.
Cette page est une vue d'ensemble. Les références détaillées sont :
l'ADR 0028-ci-engine-woodpecker.md
(choix du moteur), docs/ci/woodpecker-architecture.md (server + agents),
docs/ci/woodpecker-workflows.md (les pipelines, fichier par fichier) et
docs/ci/secrets-management.md (secrets & rotation). Les pipelines
eux-mêmes (.woodpecker/*.yml) restent la référence exécutable.
Une configuration .gitlab-ci.yml a existé (qualité + tests, sans
déploiement). Depuis l'ADR 0028 (2026-05-23), le moteur retenu est
Woodpecker : .gitlab-ci.yml est désormais
gitignoré (.gitignore:24, reliquat local inerte qui ne
déclenche rien). Le code vit sur deux forges officielles miroirs — GitLab
gitlab.com/federation-kohinos/kohinos (remote origin)
et GitHub github.com/jujote/kohinos — mais l'orchestration CI/CD passe
entièrement par Woodpecker.
Détail du raisonnement (RAM, licence, portabilité forge) → section
Historique GitLab CI → Woodpecker.
Vue d'ensemble — 6 pipelines par événement
Woodpecker ne raisonne pas en « stages » globaux mais en fichiers de pipeline déclenchés par événement. Six pipelines couvrent le cycle de vie :
< 10 min — feedback rapide"] PUSH[push main/master] --> P2[".woodpecker/main.yml
< 25 min — PR + finance + MSI + deploy staging"] CRON[cron 02:00 UTC] --> P3[".woodpecker/nightly.yml
< 60 min — full + E2E + audit"] TAG[tag v*] --> P4[".woodpecker/tag.yml
note de release"] MAN1[manuel + CONFIRM] --> P5[".woodpecker/cutover.yml
v2→v3 prod orchestré SSH"] MAN2[manuel + CONFIRM] --> P6[".woodpecker/rollback.yml
restore dump + tag précédent"] P2 --> S[deploy:staging via SSH] P5 --> PROD[prod — gates parité + invariants]
Pipelines détaillés
| Pipeline | Déclencheur | Cible | Steps principaux |
|---|---|---|---|
pr.yml |
event: pull_request |
< 10 min | composer-install · php-cs-fixer · phpstan ·
phpunit-unit · phpunit-functional (SQLite) ·
npm-build · assets-freshness.
Saute les suites longues SSA/SEPA/MLC. |
main.yml |
event: push, branche main/master |
< 25 min | Reprend pr.yml + phpunit-financial (SSA/SEPA/MLC) +
infection-msi-gate (--min-msi via secret, palier A2) +
deploy-staging (SSH). |
nightly.yml |
event: cron (nightly = 0 2 * * *, UI Woodpecker) |
< 60 min | phpunit-full · e2e-playwright ·
security-audit (composer audit + checks CSRF/headers) ·
migrate-parity (rejeu ETL synthétique). Diagnostic, non bloquant. |
tag.yml |
event: tag, ref: refs/tags/v* |
< 5 min | changelog-aggregate → var/release/${CI_COMMIT_TAG}.md.
Pas de gh release create auto (souveraineté : pas de
token GitHub longue durée en CI). |
cutover.yml |
event: manual + CONFIRM=I_UNDERSTAND_THIS_TOUCHES_PROD |
manuel | Orchestration SSH v2→v3 : validate-inputs → parité pré-vol →
backup DB → maintenance ON → deploy → migrate → chiffrement IBAN (dry-run puis réel)
→ gates parité + invariants → smoke → maintenance OFF → notif Mattermost. |
rollback.yml |
event: manual + CONFIRM=I_UNDERSTAND_THIS_IS_A_ROLLBACK |
manuel | Maintenance ON → restore-db.sh (dump étiqueté) →
checkout tag précédent → cache clear → smoke → maintenance OFF → notif. |
Les bundles Webpack Encore sont pré-compilés et versionnés dans
public/build/ (+ public/sw.js) : ils sont committés au
dépôt. Aucun Node/npm/yarn n'est requis sur le serveur de prod ni
pour déployer. Le step npm-build (image node:22-alpine) reste
présent dans pr.yml/main.yml à titre de filet, mais
n'est pas un prérequis de déploiement. Le vrai garde-fou est le step
assets-freshness (image alpine:3.20, 100 % git, sans Node,
via scripts/ci-assets-freshness.sh) : il échoue si une source
front (assets/, webpack.config.js, package.json…)
a changé sans que public/build/ ait été republié — c'est au développeur de
committer le build à jour avant de pousser.
Les testsuites ssa, sepa et mlc sont
obligatoirement vertes dans main.yml avant tout déploiement
staging, et le gate infection-msi-gate (Infection sur
src/Service/Sepa, src/Service/Ssa, src/Flux,
src/Entity/Operation.php) doit franchir le palier MSI. La priorité 1 du
projet est l'intégrité financière — un test rouge ou un MSI insuffisant bloque la chaîne
(pas de deploy-staging). Le hook GrumPHP local rejoue les mêmes garde-fous en amont.
Cache & volumes
Pour limiter la pression sur l'agent (objectif : PR < 10 min, même sur Pi 5 8 Go), Woodpecker monte des volumes nommés persistants partagés entre tous les runs et tous les pipelines (PR + main + nightly). Pas de purge automatique.
| Ressource | Volume monté | Portée |
|---|---|---|
| Cache Composer | /cache/composer → $COMPOSER_CACHE_DIR |
incrémental, partagé entre runs et pipelines |
| Cache npm | /cache/npm → ~/.npm |
step npm-build (filet, non bloquant pour le deploy) |
| Navigateurs Playwright | /cache/playwright-browsers → $PLAYWRIGHT_BROWSERS_PATH |
step e2e-playwright (nightly) |
public/build/ |
aucun — versionné dans le dépôt | pré-compilé et committé, pas un artefact CI |
# Extrait .woodpecker/pr.yml — cache Composer via volume nommé
- name: composer-install
image: composer:2.8
commands:
- composer install --prefer-dist --no-progress --no-scripts --no-interaction
volumes:
- /cache/composer:/tmp/composer-cache
environment:
COMPOSER_CACHE_DIR: /tmp/composer-cache
COMPOSER_ALLOW_SUPERUSER: '1'
# Purge manuelle au cas par cas (pas d'expiration auto) :
# docker volume rm kohinos-ci-composer-cache
Secrets Woodpecker
Aucun secret n'est versionné. Les credentials sensibles vivent dans les
secrets Woodpecker (chiffrés au repos, masqués dans les logs, scoppés au dépôt)
et sont injectés par from_secret. Les secrets de prod côté serveur restent
dans le Symfony secrets vault (config/secrets/prod/, déchiffré au boot).
| Secret | Injection | Utilisé par |
|---|---|---|
min_msi | from_secret: min_msi | main.yml step infection-msi-gate (palier MSI A2) |
ssh_deploy_key | from_secret: ssh_deploy_key | main.yml deploy-staging, cutover.yml, rollback.yml |
staging_host | from_secret: staging_host | main.yml step deploy-staging |
staging_url | from_secret: staging_url | nightly.yml step security-audit |
mattermost_webhook | from_secret: mattermost_webhook | cutover.yml / rollback.yml step notify |
| Secrets de prod (DB, SMTP, gateways, IBAN) | Symfony vault | config/secrets/prod/ chiffré, déchiffré au boot serveur |
.woodpecker/*.yml
Toute valeur sensible (clé SSH de déploiement, webhook Mattermost, seuil MSI) passe par
un secret Woodpecker injecté via from_secret — jamais en clair dans
le YAML versionné. Le hook GrumPHP git_blacklist rejette les commits contenant
PAYUM_ ou SECRET en clair, mais ce filet est faillible — la vraie
protection est l'humain. Détail rotation (6 mois) → docs/ci/secrets-management.md.
Agents Woodpecker
Un woodpecker-server + un ou plusieurs woodpecker-agent suffisent.
Chaque step tourne dans un conteneur OCI isolé (image épinglée). La sélection
d'agent se fait par label : aujourd'hui tous les pipelines épinglent
linux/amd64.
| Composant | Rôle | Souveraineté |
|---|---|---|
woodpecker-server |
UI, webhooks forge (OAuth), orchestration, métriques Prometheus | self-host Fédération — < 600 Mo RAM avec 1 agent |
Agent linux/amd64 |
exécute tous les steps actuels (lint, PHPStan, PHPUnit, Infection, npm, deploy) | self-host — VPS 4 Go, données de test isolées |
Agent linux/arm64 (Pi 5) |
inutilisé tant qu'aucun pipeline ne demande platform: linux/arm64 |
évolution future : nightly-arm64.yml pour valider le build sur ARM |
# docker-compose (extrait) — agent ARM64 sur Raspberry Pi 5
services:
woodpecker-agent-arm64:
image: woodpeckerci/woodpecker-agent:v3
environment:
WOODPECKER_SERVER: woodpecker.kohinos.example:9000
WOODPECKER_AGENT_SECRET: ${WOODPECKER_AGENT_SECRET}
WOODPECKER_BACKEND: docker
WOODPECKER_FILTER_LABELS: "platform=linux/arm64"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
Un Raspberry Pi 5 (8 Go RAM, SSD NVMe via HAT) tient confortablement les steps
php-cs-fixer, phpstan et phpunit-unit. Pour
phpunit-financial avec SQLite, prévoir un peu de marge ou offload sur un
agent x86. Le low-tech est une priorité, pas une religion — si un step CI met
> 5 min sur Pi, le déporter sur un agent plus costaud sans culpabiliser.
Boucle commit recommandée
Reproduit localement, dans l'ordre, ce que la CI vérifiera ensuite. Évite les aller-retours pipeline rouge → fix → re-push.
- Mettre à jour
CHANGELOG.md(technique) etCHANGELOG_USER.md(fonctionnel). - Lancer les tests ciblés — jamais
vendor/bin/phpunitseul. - Formater le code avec php-cs-fixer.
- Lancer la pipeline locale via GrumPHP (mêmes hooks que la CI).
- Committer (le hook pre-commit GrumPHP rejouera tout).
# 1. CHANGELOG (manuel, dans l'éditeur)
$EDITOR CHANGELOG.md CHANGELOG_USER.md
# 2. Tests ciblés (cf. CLAUDE.md)
vendor/bin/phpunit --testsuite=ssa
vendor/bin/phpunit tests/Service/Sepa/SepaProcessorTest.php
# 3. Format
vendor/bin/php-cs-fixer fix
# 4. Pipeline locale
vendor/bin/grumphp run
# 5. Commit + push
git add -A
git commit -m "feat(ssa): nouvelle stratégie de redistribution proportionnelle"
git push origin feature/ssa-proportional
Règles dures
- Jamais de déploiement production sans pipeline CI verte
(incluant
phpunit-financialetinfection-msi-gate). - Jamais de merge vers
mainavec un test rouge, même un test « flaky » — soit on le stabilise, soit on le marque@group flakyet on l'isole dans un step séparé non bloquant. - Jamais de secret en clair dans
.woodpecker/*.yml,composer.json, ou un fichier versionné. Tout passe par les secrets Woodpecker (from_secret) ou le Symfony secrets vault. - Jamais de
git push --forcesurmainni sur les tagsv*. - Jamais de skip de tests « temporaire » — un
markTestSkipped()sans ticket associé et sans date de réactivation est une dette qui dure 6 mois. PréférermarkTestIncomplete()avec message explicite, ou supprimer le test. - Jamais de
composer updatedans la CI : uniquementcomposer install --prefer-distà partir ducomposer.lockversionné.
GrumPHP local — pre-commit hook
GrumPHP rejoue dans le hook pre-commit les mêmes contrôles que la CI,
de sorte qu'un push ne franchisse jamais la frontière du laptop sans avoir passé
les filtres. Les tâches actives sont configurées dans grumphp.yml
à la racine. Alignement détaillé pre-commit / CI → docs/ci/pre-commit-vs-ci.md.
# grumphp.yml (extrait)
grumphp:
tasks:
git_blacklist:
keywords:
- "die("
- "var_dump("
- "dump("
- "exit;"
whitelist_patterns:
- /^scripts\//
- /^docs\//
yamllint: ~
composer: ~
jsonlint: ~
phpcsfixer:
config: .php-cs-fixer.dist.php
phpunit:
config_file: phpunit.xml
testsuite: unit
Activation locale :
composer install # GrumPHP s'installe automatiquement
vendor/bin/grumphp git:init # registre le hook .git/hooks/pre-commit
vendor/bin/grumphp run # exécution manuelle
Le hook local peut être contourné (git commit --no-verify) et ne
tourne pas phpunit-financial ni infection-msi-gate par défaut
(trop long). La CI reste le filet de sécurité final ; GrumPHP est
le filet amont qui économise un round-trip agent.
Historique GitLab CI → Woodpecker
Le code est hébergé sur deux forges miroirs — GitLab
(gitlab.com/federation-kohinos/kohinos, remote origin)
et GitHub (github.com/jujote/kohinos). Une configuration .gitlab-ci.yml de qualité/tests a existé,
sans déploiement. Le choix d'un moteur CI auto-hébergé a tranché
en faveur de Woodpecker (ADR 0028, 2026-05-23). Comparaison des options :
| Critère | Woodpecker CI (retenu) | GitLab CI |
|---|---|---|
| Licence | Apache-2.0 (OSI), pas de risque de bascule propriétaire | cœur libre, mais SaaS GitLab.com propriétaire dans le path |
| RAM minimale | ~600 Mo (server + 1 agent) — tient sur Pi 5 / VPS 4 Go | runner ~4 Go+, impose la stack GitLab complète |
| Souveraineté (prio 2) | self-host, OAuth multi-forge, aucun SaaS imposé | tire vers GitLab.com SaaS ou un GitLab CE lourd |
| Sobriété (prio 3) | ARM64 first-class, conteneurs OCI légers | runner gourmand, peu adapté Raspberry Pi |
| Déploiement | deploy-staging + cutover + rollback (SSH) | aucun — qualité/test uniquement |
| Portabilité forge | GitHub / Gitea / Forgejo / GitLab via OAuth | couplé à GitLab |
État actuel :
| Élément | État |
|---|---|
.woodpecker/{pr,main,nightly,tag,cutover,rollback}.yml | ✅ actifs — moteur CI/CD réel |
.gitlab-ci.yml | gitignoré (.gitignore:24) — reliquat local inerte, ne déclenche rien |
| Forges officielles (miroirs) | GitLab gitlab.com/federation-kohinos/kohinos (origin, déploiement) + GitHub github.com/jujote/kohinos (backend OAuth du CI). Branche par défaut main ; liens doc paramétrables via KOHINOS_FORGE=gitlab|github |
| Dependabot | remplacé par Renovate self-host |
Renovate self-host
Renovate (bot open-source de mise à jour des dépendances) tourne en self-host sur le serveur Fédération Kohinos, via cron. Remplace Dependabot (GitHub-only) et reste forge-agnostique (GitLab aujourd'hui, portable demain).
# renovate.json (à la racine du dépôt)
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"schedule": ["before 6am on monday"],
"packageRules": [
{
"matchUpdateTypes": ["patch"],
"groupName": "patches",
"automerge": false
},
{
"matchPackagePatterns": ["doctrine/dbal"],
"allowedVersions": "<4.0.0",
"description": "Pin DBAL 3.x — DBAL 4.x incompatible avec ORM 2.14"
},
{
"matchPackagePatterns": ["sonata-project/admin-bundle"],
"allowedVersions": "<5.0.0",
"description": "Pin Sonata 4.x — Sonata 5.x non testé"
}
],
"vulnerabilityAlerts": { "enabled": true, "labels": ["security"] }
}
doctrine/dbal est épinglé à 3.x : la 4.x introduit
des breaking changes incompatibles avec Doctrine ORM 2.14. sonata/admin
reste en 4.x tant que la migration vers 5.x n'est pas planifiée. Renovate doit
respecter ces bornes — sans quoi une MR auto-mergée peut casser la prod.
Voir aussi
- ADR 0028 — choix du moteur CI Woodpecker
docs/ci/woodpecker-architecture.md— server + agents amd64/arm64docs/ci/woodpecker-workflows.md— les pipelines, fichier par fichierdocs/ci/secrets-management.md— inventaire et rotation des secrets- ← Installation — prérequis système et setup local
- Déploiement — procédure post-CI vers le serveur
- Performance — tuning runtime, OPcache, mesures
- Monitoring — logs, métriques, alertes