Un webhook Coolify manuel ne vise pas une application : il vise toutes les applications dont l’URL de dépôt contient <owner>/<repo> sur la branche poussée. Sur mon instance Coolify 4.0.0 (état relevé le 4 août 2026 sur srv-a), un seul push Gitea sur le dépôt du site vitrine déclenche cinq déploiements, parce que cinq applications partagent ce dépôt et cette branche. Et quand un webhook ne déclenche rien, Coolify répond quand même HTTP 200 : la raison est écrite dans le corps de la réponse, jamais dans le code de statut.
Trois chemins de déploiement qui ne partagent rien
Coolify propose trois manières de lancer un déploiement depuis l’extérieur. Elles n’ont ni la même route, ni le même secret, ni la même logique pour retrouver l’application :
| Mécanisme | Route | Secret vérifié | Matching de l’application |
|---|---|---|---|
| GitHub App | POST /webhooks/source/github/events | secret de l’App, global | repository_project_id + source non publique |
| Webhook manuel | POST /webhooks/source/{github,gitea,gitlab,bitbucket}/events/manual | secret par application | git_repository LIKE %owner/repo% + git_branch |
| API deploy | GET ou POST /api/v1/deploy?uuid=… | token API (permission deploy) | UUID explicite |
Mes dépôts Gitea sont joignables seulement par le mesh WireGuard, donc en deploy key, comme expliqué dans l’article sur Coolify et un Gitea en IP privée. Pour eux, c’est le webhook manuel, créé à la main dans Gitea avec l’URL et le secret affichés dans l’onglet Webhooks de l’application.
Pourquoi un webhook Coolify déclenche plusieurs déploiements ?
Le site vitrine est un seul dépôt, perso/vitrine, d’où sortent cinq vhosts : le site, deux sous-sites et deux redirections. Cinq applications Coolify, même dépôt, même branche main. Le webhook manuel cherche les applications avec un LIKE : les cinq correspondent, les cinq partent en déploiement.
Ici, c’est voulu. Mais le LIKE n’est pas ancré : %perso/vitrine% correspond aussi à perso/vitrine-admin. Un push sur vitrine déploie donc aussi les applications construites depuis vitrine-admin sur la même branche.
Un webhook manuel Coolify déploie toutes les applications dont l’URL de dépôt contient
owner/reposur la branche poussée, pas seulement celle qui a fourni l’URL du webhook.
Deuxième conséquence : le secret manuel est par application, pas par dépôt. Un webhook Gitea ne porte qu’un secret, alors que les cinq applications en ont chacune un, généré automatiquement. Il faut soit créer un webhook Gitea par application, soit aligner les cinq secrets sur la même valeur. Je l’ai noté comme point à vérifier au prochain déploiement partiel sur ce dépôt.
HTTP 200 ne veut pas dire « déployé »
Ma première fausse piste quand un push ne déclenche rien : l’interrupteur Auto Deploy. Actif par défaut, il l’était sur mes 12 applications. Les contrôleurs de webhook répondent 200 avec un message explicite : il faut lire le corps dans Recent Deliveries (GitHub) ou dans l’historique du webhook (Gitea).
| Corps de la réponse (HTTP 200) | Cause |
|---|---|
Nothing to do. No GitHub App found. | l’en-tête X-GitHub-Hook-Installation-Target-Id ne correspond à aucune App connue |
Invalid signature. | secret différent entre la forge et Coolify |
Nothing to do. No applications found with branch 'X'. | la branche de l’application n’est pas celle poussée |
Webhook secret not configured. | secret manuel vide sur l’application |
Deployments disabled. | Auto Deploy désactivé |
Changed files do not match watch paths. | watch_paths défini et aucun fichier modifié ne correspond |
Un test « Test Delivery » réussi dans Gitea renvoie {"status":"success","message":"Deployment queued.", …}.
Les deux cas où rien n’arrive, sans aucun message
La source « Public GitHub » ne reçoit jamais de webhook. Le handler de la GitHub App filtre avec whereRelation('source', 'is_public', false). Une application créée en Public Repository n’est jamais auto-déployée par l’App : il faut la rattacher à la GitHub App ou lui poser un webhook manuel.
Un repository_project_id vide rend l’application invisible à la GitHub App. repository_project_id est l’identifiant numérique du dépôt chez GitHub. Coolify ne le renseigne que si on choisit le dépôt dans la liste déroulante de la source, pas en collant une URL. À NULL, l’App ne trouve jamais l’application, et aucun message ne le signale.
La requête SQL qui répond avant l’UI
Pour voir en une fois dépôt, branche, identifiant GitHub et flag d’auto-deploy de toutes les applications :
ssh deploy@srv-a 'docker exec coolify-db psql -U coolify -d coolify -c \
"SELECT a.name, a.git_repository, a.git_branch, a.repository_project_id, s.is_auto_deploy_enabled \
FROM applications a JOIN application_settings s ON s.application_id = a.id ORDER BY a.id;"'
Deux applications avec le même couple dépôt/branche partiront ensemble. Une application GitHub App avec repository_project_id vide ne partira jamais.
Lessons learned
- Un webhook manuel Coolify déploie toutes les applications qui partagent le dépôt et la branche. Le matching est un
LIKE %owner/repo%, pas un identifiant d’application. - Le
LIKEn’est pas ancré, doncvitrinematchevitrine-admin. Éviter les noms de dépôts préfixes l’un de l’autre sur une même instance. - Coolify répond HTTP 200 même quand il ne fait rien. Le diagnostic est dans le corps de la réponse :
Invalid signature.,Changed files do not match watch paths.… - La source « Public GitHub » et un
repository_project_idvide échouent en silence. Aucun message, aucun déploiement : seule la base le montre. - Le secret d’un webhook manuel appartient à l’application, pas au dépôt. Plusieurs applications sur un dépôt demandent plusieurs webhooks ou des secrets alignés.