Docs/ Déploiement/ Hooks de déploiement

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 :

Terminal
$ 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 :

ÉtapeQuandImage et entréesEn cas d'échec
pre_buildAprès le clonage, avant la construction de l'imageL'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_buildAprès la construction de l'image, avant le démarrage du nouveau conteneurLa 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_deployAprès que le nouveau conteneur a passé son contrôle de santé, avant que la route ne bascule vers luiLa 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_deployAprès la bascule de la route et le retrait de l'ancien conteneurLa 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).
Warning
Il n'existe pas de réglage « échec bloquant » par hook. L'étape seule décide : un hook 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) :

Terminal
$ 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      Yes

sh0 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'ENTRYPOINT de l'image est vidé : la commande s'exécute telle quelle, et non comme arguments de node ou de npm.
  • 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 :

Build log
[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 :

Build log
[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: 3001

Quand 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 :

Build log
[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.