Resolução de Problemas em Contêineres Docker: Problemas Comuns de Inicialização e Soluções
Diagnostique contêineres Docker que saem, falham ao vincular portas, perdem arquivos, encontram problemas de permissão ou são encerrados por falta de memória.
Resolução de Problemas em Contêineres Docker: Problemas Comuns de Inicialização e Soluções
Quando um contêiner Docker não inicia, a correção mais rápida geralmente vem de resistir ao impulso de adivinhar. Um contêiner é apenas um processo com um sistema de arquivos, ambiente, configurações de rede e limites ao seu redor. Se esse processo sair, o Docker registra o motivo. Seu trabalho é coletar as evidências na ordem correta.
Geralmente começo com três perguntas: o Docker criou o contêiner, o processo principal iniciou e algo externo ao processo o matou ou bloqueou? Essas perguntas separam um nome de imagem ruim de um comando quebrado, um conflito de porta de uma falha de aplicação e um problema de permissão de um limite de memória.
Comece com o comando básico que diz a verdade:
docker ps -a
Observe STATUS, PORTS e NAMES. Created significa que o Docker criou o contêiner, mas não o colocou em execução. Exited (1) geralmente significa que a aplicação retornou um erro normal. Exited (127) comumente aponta para um comando ausente. Exited (137) geralmente significa que o processo foi morto externamente, frequentemente devido a pressão de memória. Esses códigos são pistas, não respostas finais, mas evitam que você depure a camada errada.
Em seguida, leia os logs:
docker logs --tail 100 <container>
docker logs -f <container>
Se o contêiner morrer imediatamente, docker logs geralmente é mais útil do que reexecutar o mesmo comando docker run. Frameworks de aplicação frequentemente imprimem a variável de ambiente ausente exata, falha de migração, arquivo de configuração inválido ou erro de vinculação antes de sair.
Para estado de baixo nível, inspecione o contêiner:
docker inspect <container> --format '{{json .State}}'
docker inspect <container> --format 'exit={{.State.ExitCode}} oom={{.State.OOMKilled}} error={{.State.Error}}'
Vale a pena memorizar esse segundo comando. Ele informa se o Docker viu uma morte por OOM, qual código de saída foi registrado e se o próprio runtime teve um erro.
Se o contêiner sair imediatamente
Um contêiner permanece ativo apenas enquanto seu processo principal estiver ativo. Se o comando terminar, o Docker para o contêiner. Isso surpreende as pessoas quando executam scripts que iniciam um daemon em segundo plano e depois retornam.
Por exemplo, esse padrão frequentemente sai:
CMD service nginx start
O comando service pode iniciar o nginx e depois terminar. O Docker vê o processo principal terminar e para o contêiner. O padrão amigável para contêineres é executar o servidor em primeiro plano:
CMD ["nginx", "-g", "daemon off;"]
A mesma ideia se aplica a processos Node, Python, Java e workers. O comando em CMD ou ENTRYPOINT deve ser o processo de longa duração, não um lançador que coloca o trabalho real em segundo plano e sai.
Se os logs mostrarem command not found, no such file or directory ou exec format error, teste a imagem interativamente:
docker run --rm -it --entrypoint sh <image>
Algumas imagens não incluem bash, especialmente imagens Alpine e distroless. Use sh primeiro, a menos que saiba que bash existe. Uma vez dentro, verifique o caminho do arquivo, permissões e interpretador:
ls -l /app
which python || true
head -1 /app/start.sh
Um script pode existir e ainda falhar com no such file or directory se seu shebang apontar para um interpretador ausente, como #!/bin/bash em uma imagem que só tem /bin/sh. Outra causa comum são quebras de linha do Windows. Se um script de shell foi editado no Windows, o \r invisível pode fazer o Linux procurar por /bin/sh\r.
Se o Docker disser que a porta já está alocada
Conflitos de porta acontecem no lado do host. Em -p 8080:80, 8080 é a porta do host e 80 é a porta do contêiner. Se algo já estiver ouvindo na porta 8080 do host, o Docker não pode vinculá-la.
Você pode ver um erro como bind: address already in use ou port is already allocated. Encontre o ouvinte:
sudo lsof -i :8080
# ou
sudo ss -ltnp 'sport = :8080'
No macOS, lsof geralmente é o mais fácil. Em servidores Linux, ss geralmente está disponível por padrão. No Windows PowerShell, use:
Get-NetTCPConnection -LocalPort 8080
Em seguida, escolha uma porta de host diferente ou pare o serviço que a possui:
docker run -d -p 8081:80 nginx
Não altere a porta do contêiner a menos que a aplicação dentro do contêiner realmente ouça nessa nova porta. Se o nginx ouvir na porta 80 dentro do contêiner, -p 8081:80 está correto. -p 8081:8081 falhará no navegador se nada dentro do contêiner estiver ouvindo na porta 8081.
Se a aplicação iniciar, mas não conseguir encontrar configuração
Muitas falhas de inicialização são variáveis de ambiente ausentes. A imagem está boa, o comando está bom, mas a aplicação espera DATABASE_URL, REDIS_URL, uma chave de API ou um arquivo de configuração.
Verifique o que o Docker passou:
docker inspect <container> --format '{{range .Config.Env}}{{println .}}{{end}}'
Para projetos Compose, inspecione a configuração resolvida em vez de apenas ler docker-compose.yml:
docker compose config
Isso captura erros de indentação, surpresas do arquivo .env e variáveis que se expandiram para strings vazias. Um exemplo real: DATABASE_URL=${DATABASE_URL} parece inofensivo, mas se o shell ou o arquivo .env não o definir, sua aplicação pode receber um valor vazio e falhar durante a inicialização.
Cuidado com segredos em logs e histórico do terminal. Para depuração local rápida, passar -e NAME=value é suficiente. Para sistemas compartilhados, use o mecanismo de segredos da sua plataforma ou um arquivo de ambiente com permissões controladas.
Se bind mounts ou volumes causarem erros de permissão
Um contêiner pode falhar na inicialização porque não consegue ler um arquivo de configuração, escrever um arquivo PID, criar um diretório de cache ou inicializar um diretório de banco de dados. Os logs geralmente dizem permission denied, read-only file system ou operation not permitted.
Primeiro inspecione o mount:
docker inspect <container> --format '{{json .Mounts}}'
Depois verifique qual usuário o contêiner executa:
docker inspect <container> --format 'user={{.Config.User}}'
Se user estiver vazio, a imagem pode executar como root por padrão, mas muitas imagens de produção definem um usuário não root. Um diretório do host pertencente ao seu UID local pode não ser gravável pelo UID 1000, 1001 ou um usuário específico do serviço dentro do contêiner.
Uma sequência prática de depuração é:
ls -ld ./data
docker run --rm -it -v "$PWD/data:/data" --entrypoint sh <image>
id
ls -ld /data
touch /data/test
Evite resolver todo problema de permissão com chmod 777. Isso pode esconder o problema imediato enquanto cria um pior. Prefira corresponder à propriedade ou usar volumes nomeados para dados da aplicação:
docker volume create app_data
docker run -d -v app_data:/var/lib/app <image>
Volumes nomeados são especialmente úteis no Docker Desktop, onde bind mounts cruzam uma fronteira de virtualização e podem se comportar de forma diferente dos sistemas de arquivos Linux nativos.
Se o contêiner foi morto por falta de memória
O código de saída 137 é uma forte indicação de que o processo recebeu SIGKILL. No trabalho com Docker, isso geralmente significa que o kernel ou o Docker Desktop o matou porque a memória acabou. Confirme com inspect:
docker inspect <container> --format 'exit={{.State.ExitCode}} oom={{.State.OOMKilled}}'
Se OOMKilled for true, você tem duas tarefas: dar ao processo memória suficiente para iniciar e entender por que ele precisou de tanta. Aumentar o limite pode ser a correção de produção certa para um banco de dados ou serviço JVM. Para um pequeno serviço web, pode revelar um padrão ruim.
Aplicativos Java são um exemplo clássico. O comportamento mais antigo da JVM nem sempre se ajustava bem aos limites do contêiner, e mesmo JVMs modernas ainda precisam de configurações sensatas de -Xmx ou baseadas em porcentagem para comportamento previsível. Serviços Node podem precisar de --max-old-space-size em ambientes com memória limitada. Bancos de dados podem precisar de configurações explícitas de cache.
Para um teste único:
docker run --memory=1g <image>
Se você usa Docker Desktop, verifique também a memória atribuída à VM do Docker. Um limite de contêiner não pode ajudar se a própria VM estiver com falta de recursos.
Se a imagem nunca for baixada ou a build nunca produziu uma imagem
Às vezes não há problema de contêiner porque não há imagem utilizável. Se docker run falhar antes de criar um contêiner, verifique a imagem separadamente:
docker image ls | grep my-app
docker pull my-registry/my-app:tag
Para registros privados, confirme a autenticação:
docker login <registry>
Para imagens locais, certifique-se de que a tag que você executa é a tag que você construiu:
docker build -t my-app:dev .
docker run --rm my-app:dev
Um erro local comum é construir my-app:dev e executar my-app:latest, que pode apontar para uma imagem mais antiga ou para nada.
Se a rede for culpada, mas o serviço não estiver ouvindo
Quando um navegador não consegue alcançar um contêiner, as pessoas geralmente pulam para a rede do Docker. Primeiro prove que a aplicação está ouvindo dentro do contêiner.
docker exec -it <container> sh
ss -ltnp || netstat -ltnp
Se a aplicação estiver vinculada a 127.0.0.1 dentro do contêiner, a publicação de porta do Docker não ajudará. A aplicação deve ouvir em 0.0.0.0 ou no endereço da interface do contêiner. Isso é comum com servidores de desenvolvimento. Por exemplo, muitos frameworks usam localhost por padrão e precisam de uma flag como --host 0.0.0.0.
Em seguida, confirme a porta publicada:
docker port <container>
docker ps --format 'table {{.Names}}\t{{.Ports}}'
Você quer ver algo como 0.0.0.0:8080->3000/tcp. Se não houver porta publicada, o serviço pode funcionar a partir de outro contêiner na mesma rede, mas não no navegador do seu host.
Uma lista de verificação confiável para inicialização
Use esta ordem quando estiver travado:
docker ps -apara ver se o contêiner existe e como ele saiu.docker logs --tail 100 <container>para ler a reclamação da própria aplicação.docker inspect <container>para verificar código de saída, status OOM, comando, usuário, mounts e portas.docker run --rm -it --entrypoint sh <image>para testar a imagem manualmente.- Remova uma variável de cada vez: primeiro execute sem mounts, depois sem redes personalizadas, depois apenas com variáveis de ambiente necessárias.
Esse último passo é importante. Um comando docker run longo com portas, volumes, arquivos de ambiente, DNS personalizado, limites de memória e um entrypoint personalizado dá muitos suspeitos. Reduza até que a imagem inicie, depois adicione configurações de volta até quebrar. A configuração que você acabou de adicionar geralmente é onde o problema real reside.