Hooks de Implantação
Execute um comando a partir da imagem do seu aplicativo em quatro momentos de uma implantação Git: antes e depois do build, antes e depois da troca de tráfego.
O que é um hook de implantação
Um hook de implantação é um único comando associado a um aplicativo e a uma etapa da sua implantação. O sh0 o executa em um contêiner descartável iniciado a partir da imagem do aplicativo, registra a saída no log da implantação e decide, pelo código de saída, se a implantação continua.
Usos típicos: uma migração de banco de dados antes que o tráfego chegue à nova versão (npm run migrate, php artisan migrate --force), um teste rápido da imagem recém-construída, um aquecimento de cache após a troca.
Quais aplicativos executam hooks
Somente aplicativos implantados a partir de um repositório Git executam hooks. O sh0 recusa criar um hook em qualquer outro aplicativo (envio de arquivo com sh0 push, imagem Docker, Dockerfile colado, arquivo Compose, modelo), porque o hook nunca seria disparado:
$ 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)Um aplicativo Git cujo repositório é implantado como uma pilha Compose também não executa hooks: o caminho Compose sai do pipeline antes da primeira etapa de hooks.
As quatro etapas
A etapa decide quando o hook é executado, em qual imagem ele roda, o que recebe e o que uma falha provoca:
| Etapa | Quando | Imagem e entradas | Se falhar |
|---|---|---|---|
| pre_build | Após o clone, antes do build da imagem | A imagem da versão atualmente implantada. Sem variáveis de ambiente nem volumes. Ignorado na primeira implantação (ainda não existe imagem). | A implantação falha. A versão anterior continua atendendo o tráfego. |
| post_build | Após o build da imagem, antes de o novo contêiner iniciar | A nova imagem, com as variáveis de ambiente do aplicativo. Sem volumes. | A implantação falha. A versão anterior continua atendendo o tráfego. |
| pre_deploy | Depois que o novo contêiner passa na verificação de saúde, antes de a rota mudar para ele | A nova imagem, com as variáveis de ambiente e os volumes do aplicativo montados. | A implantação falha, o novo contêiner é removido e a versão anterior continua atendendo o tráfego. |
| post_deploy | Após a troca da rota e a remoção do contêiner antigo | A nova imagem, com as variáveis de ambiente e os volumes. | Registrado como falha; a implantação continua bem-sucedida (a nova versão já está no ar). |
pre_build, post_build ou pre_deploy com falha sempre faz a implantação falhar; um hook post_deploy com falha, nunca.Criar um hook
No painel, abra o aplicativo, depois a aba Hooks de implantação, e clique em Criar hook. Escolha o Tipo de hook, informe o Comando e, se necessário, o Tempo limite (segundos) (300 por padrão).
Pela CLI, o tipo é passado com --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> remove um hook. Para pausá-lo sem removê-lo, envie PATCH /api/v1/deploy-hooks/<id> com enabled=false. A API também oferece GET e POST /api/v1/apps/<app-id>/deploy-hooks. Criar ou alterar um hook exige o papel de desenvolvedor no aplicativo.
Onde um hook é executado
Nunca no próprio servidor. Cada hook recebe um contêiner de uso único, iniciado a partir da imagem indicada na tabela acima, com estas propriedades:
- Ele roda com o usuário sem privilégios
1000:1000, nunca como root. - O
ENTRYPOINTda imagem é esvaziado: o comando é executado como está, e não como argumentos denodeounpm. - Ele é conectado à rede
sh0-net, então consegue alcançar os bancos de dados do aplicativo pelo nome de host interno. - Sem shell. O comando é dividido nos espaços e executado diretamente.
|,;,&,>,<, crases e$(são recusados, e as aspas são repassadas literalmente. Para tudo o que precisar de um shell, coloque um script na sua imagem e chame-o:sh scripts/migrate.sh. As regras completas são as das tarefas agendadas; um comando é limitado a 4.096 caracteres.
Um hook post_build que executa id em uma imagem Node.js mostra o usuário do ambiente isolado:
[HOOKS] post_build hooks:
[OK] id (242ms, exit: Some(0))
stdout: uid=1000(node) gid=1000(node) groups=1000(node)Ordem e tempo limite
Os hooks de uma mesma etapa são executados um após o outro, o criado mais recentemente primeiro. Se a ordem importar, crie primeiro o que deve rodar por último, ou encadeie os passos em um único script.
Cada hook tem seu próprio tempo limite, 300 segundos por padrão. Um hook que o ultrapassa conta como falha (Hook timed out after 5s no log). Seu contêiner não é parado: continua rodando até o comando terminar sozinho e depois permanece no servidor.
Ler os resultados
Os resultados são acrescentados ao log de build da implantação, na aba Implantações. Cada hook tem uma linha com o status (OK, FAIL ou SKIP), a duração e o código de saída, seguida das 10 primeiras linhas da saída padrão e da saída de erro. Os segredos são mascarados nessas linhas. Log real de uma primeira implantação com cinco 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: 3001Quando um hook bloqueante falha, o log termina com o erro e a remoção do novo contêiner. Aqui um hook pre_deploy saiu com o código 3, e a versão já no ar continuou rodando:
[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.Nesse caso, a saída do hook com falha, e a dos hooks da mesma etapa executados antes dele, não é mantida: só resta a linha de erro.
Limites
- Um comando com um caractere proibido é aceito na criação do hook e só recusado na execução. Em uma etapa bloqueante, a implantação falha; em
post_deploy, o hook some do log sem deixar rastro. Verifique o comando antes de salvá-lo. - Os hooks não enviam notificações. Os alertas do sh0 e seus canais (e-mail, Slack, Discord, Telegram, webhook) são descritos na página de monitoramento.
- Uma reversão de um aplicativo Git passa de novo por todo o pipeline, hooks incluídos. Veja a página de reversões para saber o que uma reversão realmente implanta.