Hooks de déploiement
Exécutez une commande depuis l'image de votre application à quatre moments d'un déploiement Git : avant et après la construction, avant et après la bascule du trafic.
Ce qu'est un hook de déploiement
Un hook de déploiement est une commande unique, rattachée à une application et à une étape de son déploiement. sh0 l'exécute dans un conteneur jetable démarré depuis l'image de l'application, consigne sa sortie dans le journal du déploiement et décide, d'après son code de sortie, si le déploiement continue.
Usages typiques : une migration de base de données avant que le trafic n'atteigne la nouvelle version (npm run migrate, php artisan migrate --force), un test rapide de l'image tout juste construite, un préchauffage de cache après la bascule.
Quelles applications exécutent des hooks
Seules les applications déployées depuis un dépôt Git exécutent des hooks. sh0 refuse d'en créer un sur toute autre application (envoi d'archive avec sh0 push, image Docker, Dockerfile collé, fichier Compose, modèle), parce que le hook ne se déclencherait jamais :
$ sh0 hooks my-site create --hook-type pre_build --command "node --version"
Error: Deploy hooks only run on git-based deploys; app 'my-site' is a 'upload' app, where hooks would never fire. Remove the hook or convert the app to a git deploy. (BAD_REQUEST)Une application Git dont le dépôt est déployé comme une pile Compose n'exécute pas non plus de hooks : le chemin Compose quitte le pipeline avant la première étape de hooks.
Les quatre étapes
L'étape décide du moment où le hook s'exécute, de l'image dans laquelle il tourne, de ce qu'il reçoit et de l'effet d'un échec :
| Étape | Quand | Image et entrées | En cas d'échec |
|---|---|---|---|
| pre_build | Après le clonage, avant la construction de l'image | L'image de la version actuellement déployée. Aucune variable d'environnement, aucun volume. Sauté au premier déploiement (aucune image n'existe encore). | Le déploiement échoue. La version précédente continue de servir le trafic. |
| post_build | Après la construction de l'image, avant le démarrage du nouveau conteneur | La nouvelle image, avec les variables d'environnement de l'application. Aucun volume. | Le déploiement échoue. La version précédente continue de servir le trafic. |
| pre_deploy | Après que le nouveau conteneur a passé son contrôle de santé, avant que la route ne bascule vers lui | La nouvelle image, avec les variables d'environnement et les volumes de l'application montés. | Le déploiement échoue, le nouveau conteneur est retiré, la version précédente continue de servir le trafic. |
| post_deploy | Après la bascule de la route et le retrait de l'ancien conteneur | La nouvelle image, avec les variables d'environnement et les volumes. | Consigné comme un échec ; le déploiement reste réussi (la nouvelle version est déjà en ligne). |
pre_build, post_build ou pre_deploy en échec fait toujours échouer le déploiement, un hook post_deploy en échec jamais.Créer un hook
Dans le tableau de bord, ouvrez l'application, puis l'onglet Hooks de déploiement, et cliquez sur Créer un hook. Choisissez le Type de hook, saisissez la Commande et, au besoin, le Délai d'expiration (secondes) (300 par défaut).
En ligne de commande, le type se passe avec --hook-type (pre_build, post_build, pre_deploy, post_deploy) :
$ sh0 hooks my-api create --hook-type post_build --command "id"
Deploy hook created: 949d4097-4350-48c0-9c7c-6ca3c582a51a
Type: post_build
Command: id
Timeout: 300s
$ sh0 hooks my-api list
ID Type Command Timeout Enabled
----------------------------------------------------------------------------------------------------------
281e3d14-fdca-4478-8ae3-985d4a604562 post_deploy env | grep NODE 300 Yes
1b025181-05a1-4f8e-bd6b-3a26fcb69848 post_deploy printenv PORT 300 Yes
22ee2ab3-7fc4-4714-a05d-f1dd779514f3 pre_deploy echo second 300 Yes
6f1fa1be-b188-4cc0-b277-cf5acfbe0780 pre_deploy echo first 300 Yes
949d4097-4350-48c0-9c7c-6ca3c582a51a post_build id 300 Yes
b8303249-54fe-4358-90f8-a287390c219f pre_build node --version 300 Yessh0 hooks my-api delete <id> supprime un hook. Pour en suspendre un sans le supprimer, envoyez PATCH /api/v1/deploy-hooks/<id> avec enabled=false. L'API expose aussi GET et POST /api/v1/apps/<app-id>/deploy-hooks. Créer ou modifier un hook exige le rôle développeur sur l'application.
Où s'exécute un hook
Jamais sur le serveur lui-même. Chaque hook obtient un conteneur à usage unique, démarré depuis l'image indiquée dans le tableau ci-dessus, avec ces propriétés :
- Il tourne sous l'utilisateur non privilégié
1000:1000, jamais en root. - L'
ENTRYPOINTde l'image est vidé : la commande s'exécute telle quelle, et non comme arguments denodeou denpm. - Il est rattaché au réseau
sh0-net: il peut joindre les bases de données de l'application par leur nom d'hôte interne. - Pas de shell. La commande est découpée sur les espaces et exécutée directement.
|,;,&,>,<, les accents graves et$(sont refusés, et les guillemets sont transmis tels quels. Pour tout ce qui demande un shell, placez un script dans votre image et appelez-le :sh scripts/migrate.sh. Les règles complètes sont celles des tâches planifiées ; une commande est limitée à 4 096 caractères.
Un hook post_build qui exécute id sur une image Node.js montre l'utilisateur du bac à sable :
[HOOKS] post_build hooks:
[OK] id (242ms, exit: Some(0))
stdout: uid=1000(node) gid=1000(node) groups=1000(node)Ordre et délai
Les hooks d'une même étape s'exécutent l'un après l'autre, le plus récemment créé en premier. Si l'ordre compte, créez d'abord celui qui doit passer en dernier, ou enchaînez les étapes dans un seul script.
Chaque hook a son propre délai, 300 secondes par défaut. Un hook qui le dépasse compte comme un échec (Hook timed out after 5s dans le journal). Son conteneur n'est pas arrêté : il continue de tourner jusqu'à ce que la commande se termine d'elle-même, puis reste sur le serveur.
Lire les résultats
Les résultats s'ajoutent au journal de construction du déploiement, dans l'onglet Déploiements. Chaque hook y a une ligne avec son statut (OK, FAIL ou SKIP), sa durée et son code de sortie, suivie des 10 premières lignes de sa sortie standard et de sa sortie d'erreur. Les secrets en sont masqués. Journal réel d'un premier déploiement avec cinq hooks :
[HOOKS] pre_build hooks:
[SKIP] node --version (0ms, exit: None)
stderr: skipped: no app image available yet (hooks run in a container, never on the host)
[HOOKS] post_build hooks:
[OK] id (292ms, exit: Some(0))
stdout: uid=1000(node) gid=1000(node) groups=1000(node)
[HOOKS] pre_deploy hooks:
[OK] echo second (287ms, exit: Some(0))
stdout: second
[OK] echo first (295ms, exit: Some(0))
stdout: first
[HOOKS] post_deploy hooks:
[OK] printenv PORT (249ms, exit: Some(0))
stdout: 3001Quand un hook bloquant échoue, le journal se termine par l'erreur et le retrait du nouveau conteneur. Ici, un hook pre_deploy est sorti avec le code 3, et la version déjà en ligne a continué de tourner :
[STEP 5/6] Health check passed (previous step: 8.3s)
[ERROR] Deploy hook 'node -e process.exit(3)' failed with exit code Some(3):
[WARNING] Removed the container this failed deployment had started (/sh0-17b83e1c-my-api-6abc39e3). A failed deploy leaves nothing running.Dans ce cas, la sortie du hook en échec, et celle des hooks de la même étape exécutés avant lui, n'est pas conservée : seule la ligne d'erreur reste.
Limites
- Une commande contenant un caractère interdit est acceptée à la création du hook et refusée seulement à l'exécution. Sur une étape bloquante, le déploiement échoue alors ; sur
post_deploy, le hook disparaît du journal sans laisser de trace. Vérifiez votre commande avant de l'enregistrer. - Les hooks n'envoient pas de notifications. Les alertes de sh0 et leurs canaux (courriel, Slack, Discord, Telegram, webhook) sont décrits dans la page sur la supervision.
- Un retour en arrière sur une application Git repasse par tout le pipeline, hooks compris. Voir la page sur les retours en arrière pour ce qu'un retour en arrière déploie réellement.