>_ josetilabs.com
← blog

16 out 2026 · docker, containers, producao · 6 min

Como limitar memória, log e reinício de um contêiner

Um contêiner sem teto de memória morre levando o serviço inteiro junto, e o log sem rotação enche o disco até o servidor parar de responder. A correção são quatro campos no compose: mem_limit, pids_limit, logging e restart: on-failure:3. Rodando os quatro limites de propósito num ambiente isolado, o processo estourou 120 MB num teto de 128 MB e saiu com código 137, o reinício parou na terceira tentativa e o log de 4,2 MB ficou em 1 MB.

O que cada limite segura

mem_limit é o teto de memória do contêiner. Estourou, o OOM killer do cgroup mata o processo com SIGKILL, e o Docker registra o óbito como saída 137. A diferença entre 137 e 143 é o sinal: morte por kill.

pids_limit limita a quantidade de processos e threads, não a memória. Ele segura o serviço que entra em laço e cria filho sem parar, aquele que derruba a máquina com centenas de processos do mesmo nome.

logging com max-size e max-file corta o log quando ele passa do tamanho. Sem isso o json-file só cresce, e é a forma mais comum de encher disco sem ninguém perceber.

restart: on-failure:3 diz que o contêiner pode voltar depois de uma falha, mas só três vezes seguidas. Sem o número, always fica renascendo a noite inteira, gastando CPU com um serviço que não vai funcionar nunca.

Terminal com quatro testes de limite: um processo morre com 120 MB num teto de 128 MB e sai com codigo 137, um contêiner que falha 3 vezes para de reiniciar, 40000 linhas de log de 4,2 MB deixam 1050004 bytes no disco e o limite de 64 pids derruba o contêiner com can't fork
Os quatro limites testados de propósito, cada um com o comando e o número que volta.

O número que mais interessa é o 120 MB. O processo pediu 120 MB e morreu dentro de um teto de 128 MB, sem chegar lá. Quem escreve mem_limit: 128m precisa deixar folga, porque o próprio contêiner gasta memória com o runtime antes de a aplicação começar.

A documentação de limites de recurso do Docker explica a parte importante: o daemon se protege da morte e deixa o contêiner ser a vítima, então o OOM killer quase sempre mata o processo que estourou o teto, não o motor.

O artefato

O compose abaixo é o arquivo inteiro, comentado linha por linha. Ele sobe um contêiner de teste e aplica os quatro limites.

yaml
services:
  app:
    image: python:3.12-slim
    command: ["sleep", "infinity"]

    # teto de memoria. o kernel mata o processo que passar disso
    mem_limit: 128m

    # teto de processos e threads. trava o servico que cria filho sem parar
    pids_limit: 64

    # meio nucleo no maximo, para o container nao tomar a maquina
    cpus: 0.5

    # log que para de crescer: 1 MB por arquivo, 3 arquivos
    logging:
      driver: json-file
      options:
        max-size: "1m"
        max-file: "3"

    # volta depois de falha, mas para na terceira tentativa seguida
    restart: on-failure:3

    # escrita temporaria que some com o container
    tmpfs:
      - /tmp:rw,size=16m,mode=1777

Quem já montou serviço endurecido vai reconhecer o resto do padrão. O post sobre contêiner sem root mostra o que vem junto: user, cap_drop e read_only, na mesma linha de defesa.

Como verificar

Antes de subir, o script confere se o seu compose tem os quatro limites. Ele lê o arquivo, não precisa de Docker e roda em qualquer máquina com bash.

bash
#!/usr/bin/env bash
# verifica-limites.sh: confere se um compose tem os quatro limites.
# uso: bash verifica-limites.sh compose.yaml
# sem argumento, confere o exemplo que esta neste arquivo.
# Nao instala nada e nao mexe em container nenhum, so le arquivo.

EXEMPLO=$(mktemp)
cat > "$EXEMPLO" <<'YAML'
services:
  app:
    image: python:3.12-slim
    command: ["sleep", "infinity"]
    mem_limit: 128m
    pids_limit: 64
    cpus: 0.5
    logging:
      driver: json-file
      options:
        max-size: "1m"
        max-file: "3"
    restart: on-failure:3
    tmpfs:
      - /tmp:rw,size=16m,mode=1777
YAML

arquivo="${1:-$EXEMPLO}"

if [ ! -f "$arquivo" ]; then
  echo "arquivo nao encontrado: $arquivo" >&2
  exit 1
fi

conteudo=$(tr '\n' ' ' < "$arquivo")
faltando=0

conferir() {
  if printf '%s' "$conteudo" | grep -q "$1"; then
    echo "ok    $2"
  else
    echo "FALTA $2"
    faltando=$((faltando + 1))
  fi
}

conferir 'mem_limit:' 'teto de memoria (mem_limit)'
conferir 'pids_limit:' 'teto de processos (pids_limit)'
conferir 'max-size:' 'rotacao de log (max-size)'
conferir 'max-file:' 'quantidade de arquivos de log (max-file)'

# restart precisa ter numero: 'always' e 'on-failure' sem :N nao param
if printf '%s' "$conteudo" | grep -qE 'restart:[[:space:]]*(on-failure|unless-stopped):[0-9]'; then
  echo "ok    reinicio com numero de tentativas"
elif printf '%s' "$conteudo" | grep -q 'restart:'; then
  echo "FALTA restart sem numero: um servico quebrado entra em laco"
  faltando=$((faltando + 1))
else
  echo "FALTA politica de reinicio (restart:)"
  faltando=$((faltando + 1))
fi

if [ "$faltando" -gt 0 ]; then
  echo "$faltando limite(s) faltando em $arquivo"
  exit 1
fi

echo "os quatro limites estao em $arquivo"

Rode sem argumento para ele conferir o exemplo embutido, e com o caminho do seu arquivo para conferir o seu. Se faltar um limite, ele diz qual e sai com código 1.

Para ver o que o Docker realmente aplicou, sem confiar no arquivo:

bash
docker compose up -d

docker inspect $(docker compose ps -q) \
  --format 'mem={{.HostConfig.Memory}} pids={{.HostConfig.PidsLimit}}'

A saída esperada é mem=134217728 pids=64. O número grande é o mesmo teto em bytes: 128 MB viram 134217728.

O teste que vale a pena fazer é estourar o limite de propósito. Dentro de um contêiner isolado, este script aloca 8 MB por volta e imprime o total a cada passo:

python
blocos = []
for i in range(1000):
    blocos.append(bytearray(8 * 1024 * 1024))
    print(f"alocado 8 MB x{i + 1} = {(i + 1) * 8} MB", flush=True)
print("SOBREVIVEU")

Rodando com docker run --memory 128m python:3.12-slim python3 -u come.py, a última linha é alocado 8 MB x15 = 120 MB, o processo morre ali e o docker inspect devolve OOMKilled=true ExitCode=137. Esse 120 MB é a folga de que eu falei: quem viu o processo morrer antes de bater no teto aprende a não colar o limite no valor exato do aplicativo.

Se der errado

O serviço reinicia sem parar e enche a CPU de madrugada. Causa: restart: always sem número, ou on-failure sem o :3. Correção: trocar por on-failure:3 e conferir RestartCount depois da falha.

O contêiner morre com 137 e ninguém sabe por quê. Causa: teto de memória baixo demais para a aplicação, contando o runtime. Correção: subir o mem_limit ou medir o consumo real antes de escolher o número.

O disco enche mesmo com log configurado. Causa: o max-file só vale com max-size junto, e driver diferente de json-file ignora as duas. Correção: conferir o tipo em docker inspect e manter o driver padrão.

Tabela

Limite O que segura O que acontece quando estoura
mem_limit: 128m processo que cresce sem teto OOM killer mata, saída 137
pids_limit: 64 laço criando processo sem parar can't fork, saída 2
max-size + max-file log que enche o disco arquivo rotaciona, disco para de crescer
restart: on-failure:3 serviço morrendo em laço para na terceira tentativa

O teto de memória não protege o disco, e o de log não protege a memória. São defesas diferentes para falhas diferentes. A auditoria de memória mostra o que acontece do lado de fora quando um processo cresce sem limite.

FAQ

Por que o processo morreu com 120 MB se o teto é 128 MB? O contêiner gasta memória além do aplicativo, com o runtime e o cache de página. O teto conta tudo, então morre antes de chegar no número do limite.

mem_limit ou deploy.resources.limits no compose? Os dois funcionam no Compose atual. mem_limit é o atalho do serviço, mais curto e mais fácil de ler num arquivo pequeno.

Dá para deixar o contêiner usar swap? Dá, mas memory-swap igual a mem_limit desliga o swap na prática. Se você quer o page cache do contêiner como folga, configure um memory-swap maior que o limite de memória.

O limite de pids derruba um serviço normal? Não. Um contêiner normal usa poucas dezenas de processos. Se 64 é pouco, o serviço está abrindo processo demais, e aí o limite encontrou um bug.

Fontes

Limites de recurso do Docker, versão consultada em 16 de outubro de 2026, e a referência de serviços do Compose, mesma data. Para a base do cgroup por baixo do mem_limit, o cgroups(7) do manual do Linux. Do lado de fora do contêiner, a mesma disciplina de teto em disco aparece no backup com rsync.

Leia também

// Por e-mail

Os textos publicados aqui também vão por e-mail. O envio ainda não começou: quem deixar o endereço agora entra na primeira lista.

Guardo só o seu e-mail, a data e o registro desta autorização. Sem cookies, sem rastreador e nada compartilhado com terceiros. Detalhes na política de privacidade.