Tutoriais de VPS
Contêiner Docker em VPS reiniciando sem parar (2026): checklist de logs, códigos de saída, health checks e OOM
Quando um contêiner Docker em um VPS reinicia sem parar, geralmente não é o próprio Docker que está quebrado, mas sim a aplicação falhando ao iniciar, configuração ausente, conflito de portas, falha no health check ou OOM. Diagnosticar na ordem de logs, códigos de saída, política de restart, recursos e dependências permite localizar a causa rapidamente.

Contêiner Docker em VPS reiniciando sem parar (2026): checklist de logs, códigos de saída, health checks e OOM
O cenário que mais vi quando um contêiner Docker fica constantemente Restarting é este:
docker ps
Na saída, algum serviço se torna repetidamente:
Restarting (1) 12 seconds ago
Restarting (137) 8 seconds ago
Restarting ( unhealthy )
Nesse momento, a primeira reação de muitas pessoas é reiniciar o Docker:
systemctl restart docker
Às vezes isso realmente alivia temporariamente, mas na maioria das vezes não resolve. Porque um contêiner que reinicia sem parar geralmente não é o motor do Docker que está quebrado, mas sim o processo principal dentro do contêiner saindo imediatamente após iniciar.
Ou seja, o Docker está apenas levantando-o repetidamente conforme sua política restart. O problema real costuma estar escondido em:
- Erro de inicialização da aplicação
- Variáveis de ambiente ausentes
- Caminho do arquivo de configuração incorreto
- Porta ocupada
- Banco de dados não conectado
- Permissões de arquivo incorretas
- Memória insuficiente, morto por OOM
- healthcheck escrito errado
Este artigo segue a ordem que costumo usar ao diagnosticar, sem enrolação, direto aos comandos e métodos de julgamento.
O primeiro comando não é restart, mas sim:
docker ps -a
Certifique-se de adicionar -a. Caso contrário, você pode não ver os contêineres que já saíram.
Preste atenção nestas colunas:
STATUS
NAMES
COMMAND
Os estados comuns são:
| Estado | Significado aproximado |
|---|---|
Exited (1) | A própria aplicação deu erro e saiu, o mais comum |
Exited (0) | O processo principal terminou normalmente, mas não é adequado como serviço permanente |
Exited (137) | Muito provavelmente morto por OOM ou recebeu SIGKILL |
Restarting (1) | Levantado repetidamente pela política de restart após falhar ao iniciar |
Restarting (137) | Morto após cada inicialização, verifique a memória primeiro |
Se você vir Restarting (1), não adivinhe, olhe os logs diretamente.
O mais usado é:
docker logs container_name
Mas quando o contêiner reinicia sem parar, os logs podem rolar muito rápido. Recomendo olhar assim primeiro:
docker logs --tail=200 container_name
Se quiser observar continuamente:
docker logs -f --tail=100 container_name
Se você usa Docker Compose:
docker compose logs --tail=200 service_name
Ou veja todos os serviços:
docker compose logs --tail=200
Muitos problemas aparecem diretamente nos logs:
Error: Cannot find module
permission denied
connect ECONNREFUSED 172.18.0.3:5432
address already in use
missing required environment variable DATABASE_URL
no such file or directory
Se você não entende os logs, pelo menos encontre o primeiro erro. Não olhe apenas a última linha, porque a última linha muitas vezes é apenas a mensagem de saída do framework; a causa real está nas dezenas de linhas anteriores.
Se você ainda não está familiarizado com diagnóstico de logs de servidor, pode ler este artigo: Como ler logs de VPS: journalctl, systemctl, Nginx e logs de aplicação.
Os logs nem sempre estão completos, especialmente quando o processo é morto pelo sistema.
Nesse caso, olhe inspect:
docker inspect container_name --format '{{.State.ExitCode}} {{.State.OOMKilled}} {{.State.Error}}'
Saída possível:
137 true
Isso é bem claro: o contêiner foi morto por OOM.
Você também pode ver o State completo:
docker inspect container_name --format '{{json .State}}'
Se quiser formatado:
docker inspect container_name | less
Campos importantes:
ExitCode
OOMKilled
StartedAt
FinishedAt
Error
Health
Alguns códigos de saída comuns podem ser entendidos assim:
| ExitCode | Significado comum |
|---|---|
0 | O programa terminou normalmente, mas o contêiner não deveria terminar |
1 | A aplicação falhou ao iniciar, erro de configuração, falha de dependência |
126 | O comando não pode ser executado, problema de permissão ou do script de entrada |
127 | O comando não existe, imagem ou command escrito errado |
137 | Morto por SIGKILL, comum em OOM |
143 | Recebeu SIGTERM, possivelmente uma parada normal |
Para ExitCode=1, não atribua diretamente ao Docker, olhe primeiro os logs da aplicação. Para ExitCode=137, verifique a memória primeiro.
Muitos arquivos Compose contêm:
restart: always
Ou:
restart: unless-stopped
Isso não é ruim, ambientes de produção geralmente precisam disso. Mas pode fazer você pensar erroneamente que "o contêiner fica quebrando e subindo sozinho o tempo todo".
Primeiro veja a política de restart:
docker inspect container_name --format '{{.HostConfig.RestartPolicy.Name}}'
Se você está diagnosticando, pode parar temporariamente o loop de reinício:
docker update --restart=no container_name
docker stop container_name
Depois inicie manualmente uma vez:
docker start container_name
docker logs --tail=200 container_name
Assim os logs ficam muito mais claros e não ficam rolando sem parar.
Se for um serviço gerenciado pelo Compose, altere docker-compose.yml e depois:
docker compose up -d
Não altere manualmente muitos parâmetros de execução do contêiner, porque no final é fácil ficar inconsistente com o arquivo Compose.
O problema de reinício do Docker que mais encontrei é a primeira categoria: variáveis de ambiente não passadas.
Por exemplo, a aplicação precisa de:
DATABASE_URL
REDIS_URL
SECRET_KEY
JWT_SECRET
APP_URL
Mas .env não está no lugar certo, ou o Compose não o referencia.
Primeiro veja quais variáveis de ambiente o contêiner realmente recebeu:
docker inspect container_name --format '{{range .Config.Env}}{{println .}}{{end}}'
Se você usa Compose, confirme que o arquivo .env está no diretório onde você executa docker compose.
Armadilhas comuns:
environment:
DATABASE_URL: ${DATABASE_URL}
Mas o shell atual ou .env não tem DATABASE_URL de jeito nenhum.
Você pode primeiro ver a configuração renderizada do Compose:
docker compose config
Este comando é muito útil, ele ajuda a ver se as variáveis foram finalmente substituídas.
Se a saída ainda mostrar valores vazios:
DATABASE_URL: ""
Então não é de se estranhar que o contêiner reinicie.
Há dois tipos de conflito de portas.
Por exemplo, o Compose diz:
ports:
- "80:80"
Mas no host o Nginx já ocupa a 80.
Verifique a porta:
ss -lntp | grep ':80'
Ou:
lsof -i :80
Se for o próprio Docker ocupando, você também pode ver:
docker ps --format 'table {{.Names}}\t{{.Ports}}'
Algumas aplicações escutam por padrão em 3000, mas você acha que escutam em 80.
Por exemplo:
ports:
- "8080:80"
Mas a aplicação na verdade roda em 3000 dentro do contêiner, então deveria ser:
ports:
- "8080:3000"
Para problemas de porta, você pode continuar com este artigo: O que fazer quando uma porta do VPS está ocupada.
Quando a aplicação dentro do contêiner inicia, ela precisa ler configuração, gravar cache e gravar logs, mas se as permissões do diretório montado do host estiverem erradas, ela também sai diretamente.
Por exemplo, nos logs aparece:
permission denied
read-only file system
cannot open config file
no such file or directory
Primeiro veja as montagens:
docker inspect container_name --format '{{json .Mounts}}'
Depois veja o diretório do host:
ls -la /path/on/host
Armadilhas comuns:
- O diretório do host não existe e o Docker criou automaticamente um diretório vazio
- O owner do arquivo é
root, mas a aplicação dentro do contêiner roda com o usuárionode,www-dataouapp - O arquivo de configuração foi montado como diretório
- O diretório foi montado no local errado, sobrescrevendo os arquivos originais da imagem
Por exemplo, você originalmente queria montar um arquivo:
volumes:
- ./config.yml:/app/config.yml
Mas o config.yml local não existe e, em alguns casos, ele se torna um diretório, então a aplicação naturalmente não consegue ler.
Se você encontrar problemas de permissão do Docker, pode ler este artigo: O que fazer com Docker Permission Denied em um VPS.
Muitas aplicações precisam se conectar a um banco de dados ou Redis ao iniciar.
Você acha que o Compose já tem:
depends_on:
- db
E está tudo resolvido. Na verdade não.
depends_on por padrão só garante a ordem de inicialização, não que o banco de dados esteja pronto.
Então a aplicação pode se conectar ao banco de dados assim que inicia:
connect ECONNREFUSED db:5432
E sair diretamente, o Docker a levanta de novo, formando um loop de reinício.
Uma abordagem mais robusta é:
- A própria aplicação suportar tentativas de reconexão
- Adicionar healthcheck ao banco de dados
- O script de inicialização da aplicação esperar o banco de dados ficar pronto
Por exemplo, PostgreSQL:
services:
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
app:
depends_on:
db:
condition: service_healthy
Se você quer usar Compose em produção, recomendo ler este artigo: Como configurar Docker Compose para produção em um VPS.
Às vezes a aplicação realmente iniciou, mas o contêiner continua reiniciando.
Nesse caso, veja se o healthcheck e a política de restart estão interagindo de forma errada.
Primeiro veja o estado de saúde:
docker inspect container_name --format '{{json .State.Health}}'
Ou:
docker ps
Se você vir:
Up 30 seconds (unhealthy)
Significa que o processo principal ainda está vivo, mas o health check está falhando.
Erros comuns:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
Mas a imagem não tem curl de jeito nenhum.
Ou o caminho do health check da aplicação não é /health, mas /api/health.
Outro caso: a aplicação inicia muito devagar, mas o healthcheck é impaciente demais:
interval: 5s
timeout: 2s
retries: 3
A aplicação ainda não subiu e já é julgada como falha.
Você pode adicionar start_period:
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:3000/health || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 60s
Se você acessa por meio de proxy reverso, note que o healthcheck é executado dentro do contêiner, e localhost se refere ao próprio contêiner, não ao host, nem ao contêiner do Nginx.
Se o código de saída for 137, ou no inspect você vir:
OOMKilled: true
Verifique a memória primeiro.
Veja os logs do sistema:
dmesg -T | grep -i 'killed process\|out of memory\|oom'
Veja os recursos do contêiner:
docker stats
Veja se o Compose está limitando demais:
mem_limit: 256m
Muitas aplicações Java, Node e Python consomem um pico de memória no momento da inicialização. Você dá 256MB, parece suficiente para rodar, mas na fase de inicialização ela já é morta.
Abordagem de solução:
- Dar ao contêiner um limite de memória mais razoável
- Adicionar swap ou zram ao VPS
- Reduzir a concorrência e o número de workers da aplicação
- Verificar se há vazamento de memória
- Separar serviços pesados como bancos de dados e serviços de busca
Para VPS com pouca memória, você pode ler este artigo: Como otimizar VPS com pouca memória usando ZRAM e Swap. Se já houver OOM frequente, você também pode ler: VPS sem memória suficiente? Soluções para o problema de OOM.
Quando o disco está cheio, o contêiner pode não reportar imediatamente "disco cheio", mas se manifestar como:
- Banco de dados falha ao iniciar
- Aplicação falha ao gravar cache
- Logs não podem ser gravados
- Anomalias no overlay2
- Contêiner sai logo após iniciar
Primeiro veja o disco:
df -h
Depois veja os inodes:
df -i
Depois veja o uso do Docker:
docker system df
Não execute isto de imediato:
docker system prune -a
Este comando pode apagar imagens e cache de build que você ainda precisa. Veja bem antes de limpar.
Para uma rota de limpeza mais robusta, você pode ler este artigo: O que fazer quando o Docker enche o disco em um VPS.
Alguns contêineres Exited (0), não por falha, mas porque o comando terminou de executar.
Por exemplo, você escreveu:
command: npm run build
Após a conclusão do build, o processo sai e o Docker considera o contêiner encerrado. Se você também escreveu:
restart: always
Ele vai reconstruir, sair e reconstruir continuamente.
Um serviço permanente deveria ser:
command: npm run start
Ou o Dockerfile da imagem deve ter o correto:
CMD ["node", "server.js"]
Também preste atenção ao problema de primeiro plano/segundo plano. O processo principal do contêiner deve rodar em primeiro plano. Se você colocar o serviço em segundo plano, o processo principal termina e o contêiner também termina.
Exemplo errado:
nginx &
Abordagem correta: manter o processo principal em primeiro plano.
Depois de alterar .env, docker-compose.yml ou caminhos de montagem, apenas docker restart geralmente não é suficiente.
O mais robusto é:
docker compose up -d --force-recreate
Se a imagem também mudou:
docker compose up -d --build --force-recreate
Se você só alterou um arquivo de configuração montado do host, veja se a aplicação suporta hot reload. Se não suportar, ainda é preciso reiniciar o contêiner correspondente:
docker compose restart app
Mas se você alterou variáveis de ambiente, portas ou mapeamentos de volume, geralmente é preciso recriar o contêiner.
Eu geralmente sigo esta ordem:
# 1. 看所有容器状态
docker ps -a
# 2. 看最近日志
docker logs --tail=200 container_name
# 3. 看退出码、OOM、错误状态
docker inspect container_name --format '{{.State.ExitCode}} {{.State.OOMKilled}} {{.State.Error}}'
# 4. 看 restart 策略
docker inspect container_name --format '{{.HostConfig.RestartPolicy.Name}}'
# 5. 临时停止重启循环
docker update --restart=no container_name
# 6. 手动启动一次,看第一条错误
docker start container_name
docker logs --tail=200 container_name
Se for Compose:
docker compose ps -a
docker compose logs --tail=200 service_name
docker compose config
docker compose up -d --force-recreate
Depois ramifique pelo tipo de erro:
| Fenômeno | O que verificar primeiro |
|---|---|
Exited (1) | Logs da aplicação, variáveis de ambiente, arquivos de configuração, serviços de dependência |
Exited (127) | Se command / entrypoint está escrito errado |
Exited (137) | OOM, limites de memória, logs do sistema |
permission denied | Permissões de volume, usuário, owner de arquivos |
address already in use | Conflito de porta do host ou da porta interna do contêiner |
connect refused | Se o banco de dados/Redis está pronto, se o nome da rede está correto |
unhealthy | Comando, caminho e tempo de inicialização do healthcheck |
O contêiner passar de Restarting para Up é apenas o primeiro passo.
Ainda é preciso continuar olhando:
docker ps
docker logs --tail=100 container_name
docker stats
Depois acesse o serviço uma vez de fora:
curl -I http://127.0.0.1:your_port
Se houver Nginx na frente:
curl -I https://your-domain.com
Por fim, recomendo adicionar isto:
- Uma política
restartrazoável, não coloquealwaysem todos os serviços sem pensar - Adicionar
start_periodao healthcheck - Limites de logs para evitar encher o disco
- Adicionar limites de recursos a serviços críticos, mas não limitar demais
- Backup periódico dos volumes de dados importantes
Por exemplo, limites de logs:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Para configuração de Compose em produção, você pode consultar: Como configurar Docker Compose para produção em um VPS.
Se desta vez for um contêiner Docker reiniciando sem parar, provavelmente você vai encontrar estes problemas mais adiante:
- Como configurar Docker Compose para produção em um VPS
- O que fazer quando o Docker enche o disco em um VPS
- O que fazer com Docker Permission Denied em um VPS
- O que fazer quando um contêiner Docker não consegue acessar a rede em um VPS
Um último lembrete: ao encontrar Restarting, não se apresse em apagar o contêiner ou o volume. Primeiro olhe os logs, os códigos de saída e o OOMKilled. Muitas vezes o que falta não é reinstalar, mas encontrar o primeiro erro real.