← Voltar para o Blog

Disco cheio às 3h da manhã: achar o culpado e evitar a recaída

O alerta chega de madrugada, você apaga um log, o serviço volta e duas semanas depois acontece de novo. Este é o caminho para achar o que realmente ocupa o disco — e para o problema não voltar.

Equipe EasyOps Cloud · · 8 min de leitura

No space left on device. O banco parou de aceitar escrita, o nginx devolve 500 e o deploy falhou no meio. Você entra na máquina, apaga o log maior que encontra, o serviço volta, e todo mundo segue a vida.

Duas semanas depois, acontece de novo — no mesmo dia da semana, no mesmo horário. Apagar arquivo é a parte fácil e é também o motivo do problema voltar. O que resolve de verdade é descobrir o que cresce, por quê, e colocar um limite que não dependa de alguém estar acordado.

Comece pelo mapa, não pelo maior arquivo

O primeiro comando não é du. É entender qual sistema de arquivos encheu, porque / cheio e /var cheio são problemas diferentes:

df -h                    # espaço por ponto de montagem
df -i                    # inodes — a outra forma de encher

A segunda linha é a que quase todo mundo pula. É perfeitamente possível ter 40% de espaço livre e mesmo assim receber No space left on device: o disco tem bytes sobrando, mas acabaram os inodes, que são as entradas que identificam cada arquivo. Um diretório de sessões PHP, uma fila de e-mail ou um cache com milhões de arquivos minúsculos esgota inode muito antes de esgotar espaço.

Se df -i mostra IUse% 100, pare de procurar arquivo grande e vá procurar diretório com arquivo demais:

sudo find /var -xdev -type d -printf '%h\n' 2>/dev/null \
  | sort | uniq -c | sort -rn | head -20

Descer no diretório certo

Com o ponto de montagem identificado, desça por camada em vez de varrer o disco inteiro:

sudo du -h --max-depth=1 /var 2>/dev/null | sort -rh | head -15

Repita trocando /var pelo maior resultado, até chegar ao culpado. É mais rápido que du -sh /* e não castiga o disco de uma máquina que já está sofrendo.

O -xdev também importa: sem ele, o du atravessa pontos de montagem e você acaba somando um bucket montado via s3fs ou um volume de rede na conta do disco local.

Os suspeitos de sempre, em ordem de frequência:

  • /var/log — log sem rotação, ou rotação configurada e nunca aplicada.
  • /var/lib/docker — imagem antiga, container parado, volume órfão, cache de build. Costuma ser o maior de todos em servidor de aplicação.
  • /var/lib/mysql ou /var/lib/postgresql — dado real, mas também WAL acumulado e binlog que ninguém configurou para expirar.
  • /tmp — arquivo temporário que o processo nunca apagou porque morreu antes.
  • /home — dump de banco que alguém gerou "só para testar" há oito meses.

Quando o df e o du discordam

Este é o caso que mais consome tempo de quem não conhece. O du soma 4 GB, o df insiste que 40 GB estão ocupados, e a diferença não aparece em lugar nenhum.

A explicação é que apagar um arquivo no Linux remove apenas o nome. Se algum processo ainda tem aquele arquivo aberto, os blocos continuam alocados até o processo fechar o descritor ou morrer. O du percorre nomes e não vê nada; o df pergunta ao sistema de arquivos e vê tudo.

É exatamente o que acontece quando alguém "resolve" o disco cheio com rm access.log sem reiniciar o nginx. O espaço não volta.

# Arquivos apagados que ainda seguram espaço
sudo lsof +L1 | awk '$5=="REG" {print $1, $2, $7, $NF}' | sort -k3 -rn | head

A saída traz processo, PID e tamanho. A correção é recarregar o serviço dono do arquivo, não matar nada às cegas:

sudo systemctl reload nginx     # reabre os arquivos de log
sudo systemctl restart meuapp   # quando reload não basta

E a lição para a próxima vez: nunca apague log ativo. Trunque, que libera o espaço sem invalidar o descritor aberto:

sudo truncate -s 0 /var/log/nginx/access.log

Docker: o maior consumidor silencioso

Em servidor de aplicação, /var/lib/docker é o campeão. O Docker guarda camada de imagem, container parado, volume que ninguém removeu e cache de build — nenhum deles some sozinho.

docker system df           # visão geral do que ocupa
docker system df -v        # detalhado, por imagem e volume

A limpeza tem dois níveis, e a diferença entre eles é séria:

# Seguro: remove container parado, rede sem uso,
# imagem pendente e cache de build
docker system prune

# Agressivo: remove TAMBÉM imagem sem container rodando
docker system prune -a

# Perigoso: apaga volume — é onde vive o dado do banco
docker system prune --volumes

O --volumes merece cuidado real. Se o seu Postgres em container guarda o dado em volume nomeado e ele não está de pé no momento do prune, você acabou de apagar o banco. Antes de rodar qualquer variação com --volumes, tire um snapshot — é a diferença entre um susto e uma restauração de backup.

Para o cache de build, que cresce sem parar em servidor de CI:

docker builder prune --filter 'until=168h'

journald e logrotate: onde colocar o limite

O journald tem limite próprio e por padrão ele é generoso demais — 10% do disco. Em uma VPS de 80 GB isso significa 8 GB de log:

journalctl --disk-usage
sudo journalctl --vacuum-size=500M     # corte imediato
sudo journalctl --vacuum-time=14d      # ou por idade

O corte imediato resolve a noite. O que evita a recaída é fixar o teto em /etc/systemd/journald.conf:

[Journal]
SystemMaxUse=500M
MaxRetentionSec=14day
sudo systemctl restart systemd-journald

Para os logs que não passam pelo journal — nginx, aplicação, script de cron — quem manda é o logrotate. Vale conferir se ele está realmente rodando, porque um arquivo de configuração com erro de sintaxe faz o logrotate abortar todas as rotações seguintes, não apenas a defeituosa:

sudo logrotate -d /etc/logrotate.conf    # simula, não altera nada
systemctl status logrotate.timer

Um arquivo mínimo e correto para a sua aplicação:

/var/log/meuapp/*.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    copytruncate
}

O copytruncate existe justamente para o caso do descritor aberto: ele copia e trunca em vez de renomear, evitando o problema da seção anterior quando a aplicação não sabe reabrir o próprio log.

O que dá para apagar às pressas — e o que não

Na urgência, a tentação é apagar o que for grande. Alguns alvos são seguros e outros custam caro depois.

Seguro, e o primeiro lugar para onde ir:

  • Cache de pacote. sudo apt clean libera /var/cache/apt/archives sem consequência nenhuma — são arquivos .deb já instalados.
  • Log rotacionado antigo. Os .gz em /var/log são histórico. Apagar os mais velhos que o seu período de retenção é exatamente o que a rotação faria.
  • Kernel antigo. Em Debian e Ubuntu, sudo apt autoremove --purge costuma liberar centenas de MB em /boot, que é uma partição pequena e um motivo comum de alarme.

Cuidado redobrado, porque parece lixo e não é:

  • `/var/lib/docker/volumes`. Só olhando o nome não dá para distinguir volume órfão de volume com o banco de produção dentro.
  • Arquivo grande em `/var/lib/mysql` ou `/var/lib/postgresql`. Nunca apague nada ali por fora. Binlog e WAL têm comandos próprios de expiração; remover o arquivo na mão corrompe o banco.
  • Qualquer coisa em `/proc` ou `/sys`. Não são arquivos reais e não ocupam disco.

A regra que evita arrependimento: em emergência, libere o mínimo necessário para o serviço voltar — truncar um log costuma bastar — e só então investigue com calma. A decisão tomada às 3h com o serviço fora do ar é sempre pior que a mesma decisão tomada às 9h com tudo funcionando.

Pare de descobrir pelo alerta de disco cheio

Diagnosticar às 3h é o sintoma de não estar olhando antes. Duas medidas simples mudam isso.

A primeira é enxergar a tendência. Disco raramente enche de repente — ele enche em linha reta durante semanas. O painel mostra a curva de uso e é ali que dá para prever a data em que o limite chega, com folga para agir em horário comercial.

A segunda é um aviso local, útil quando o crescimento é súbito:

#!/usr/bin/env bash
# /usr/local/bin/check-disco.sh
LIMITE=85
df -P -x tmpfs -x devtmpfs | awk -v l="$LIMITE" 'NR>1 {
  gsub(/%/,"",$5); if ($5+0 >= l)
    printf "ALERTA: %s em %s%% (%s livre)\n", $6, $5, $4
}'

Um systemd timer de hora em hora chamando esse script avisa enquanto ainda dá para agir com calma. Se preferir cron, confira antes por que o cron às vezes não roda — um alerta de disco que nunca dispara é pior que nenhum, porque gera confiança falsa.

O que fazer com o que precisa ser guardado

Boa parte do que enche disco não é lixo: é dump de banco, arquivo de cliente, export mensal. Esse material não deveria estar no disco da VM, que é o armazenamento mais caro e mais frágil que você tem — some junto com a máquina.

O destino natural é um bucket. Um dump que nasce e já vai embora para o bucket S3 sai da conta do disco no mesmo dia, com custo por GB mais baixo e retenção configurável. O script de dump e envio já sai com retenção, então o crescimento fica limitado por desenho.

Se depois de limpar o disco continuar cheio sem explicação, vale seguir o diagnóstico de VPS lenta: disco cheio e disco lento produzem sintomas parecidos na aplicação, mas as causas — e as correções — não têm nada em comum.

Suba um servidor em minutos

Preço em real, suporte em português e dados no Brasil.

Criar conta