O contentor não arranca: ler o que ele diz antes de morrer
O docker ps não o mostra. O docker ps -a mostra, e diz Exited (127) 2 seconds ago. A partir daqui há uma ordem de perguntas que resolve quase todos os casos — e quase nenhuma delas começa por mexer no Dockerfile.
1. Primeiro, o que ele disse
docker ps -a
docker logs --tail 50 o-meu-contentorUm pormenor que poupa tempo: os registos de um contentor morto continuam lá até alguém lhe fazer rm. Usar docker run --rm enquanto se depura é apagar a única prova que havia.
2. O código de saída diz mais do que parece
docker inspect --format '{{.State.ExitCode}}' o-meu-contentor0 — terminou bem. Num serviço, isso é o problema: o processo principal acabou e o contentor foi atrás dele.
1 — erro da aplicação. A resposta está nos registos.
125 — o
docker runé que falhou, antes de arrancar seja o que for. Opção mal escrita.126 — o comando existe mas não é executável. Falta o
chmod +x.127 — comando não encontrado. O clássico: um script com
#!/bin/bashnuma imagem Alpine, que só trazsh.137 — morreu com um sinal de morte imediata, quase sempre por falta de memória.
Nesse último caso há uma confirmação que não deixa dúvidas:
docker inspect --format '{{.State.OOMKilled}}' o-meu-contentor
# true3. ENTRYPOINT e CMD não são a mesma coisa
É a confusão que mais custa a depurar. O CMD é o argumento do ENTRYPOINT, não uma alternativa a ele. Neste site:
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["node", "server.js"]O que corre é docker-entrypoint.sh node server.js. E é por isso que abrir uma consola não funciona como se espera:
docker run -it a-minha-imagem sh
# substitui o CMD, mas o entrypoint corre na mesma — e recebe "sh"
docker run -it --entrypoint sh a-minha-imagem
# assim sim4. O exec que quase toda a gente esquece
Este é o erro silencioso desta história, porque não impede o arranque — estraga a paragem. O entrypoint deste site acaba assim:
set -e
node /app/cli/node_modules/prisma/build/index.js migrate deploy
exec "$@"Sem aquele exec, o script continuaria a ser o processo número um e a aplicação seria um filho dele. Quando o Docker mandasse parar, o sinal ia para o script, que não faz nada com ele — e dez segundos depois o Docker matava tudo à força, a meio do que estivesse a acontecer.
Com o exec, a aplicação substitui o script e passa a receber os sinais em primeira mão.
O set -e da primeira linha é o par disto: uma migração falhada impede o arranque, em vez de deixar subir um servidor contra um esquema errado.
5. Entrar num contentor que não fica de pé
O docker exec não serve: precisa de um contentor vivo. A saída é arrancar outro com a mesma imagem e uma consola no lugar do programa:
docker run -it --rm --entrypoint sh a-minha-imagem
# lá dentro, correr à mão aquilo que o entrypoint corria
ls -l /app
node server.jsQuase sempre o erro aparece ali à primeira tentativa, com uma mensagem que os registos tinham cortado.
6. De pé não quer dizer pronto
Um contentor pode estar a correr e não servir nada. É para isso que existe o healthcheck — e o parâmetro que mais vezes falta é o do tempo de arranque:
HEALTHCHECK --interval=30s --timeout=3s --start-period=25s --retries=3 \
CMD wget -qO- http://127.0.0.1:3000/healthz >/dev/null || exit 1Sem o --start-period, um arranque que demore vinte segundos é declarado doente antes de chegar a responder, e o contentor entra num ciclo de reinícios que parece uma avaria e é só impaciência.
O resumo
Registos primeiro, código de saída a seguir. O 127 é um interpretador que não existe, o 126 é uma permissão, o 137 é memória, e o 0 num serviço quer dizer que ele achou que tinha acabado. Se nada disso explicar, arranca-se a imagem com uma consola e corre-se à mão aquilo que ela tentava fazer sozinha.