Docker Container Exited with Code 137: Полное руководство по устранению

Ваш контейнер внезапно остановился, а в логах docker inspect вы видите зловещий «OOMKilled»: true и код выхода 137? Не паникуйте. Это одна из самых частых проблем в Docker, и она всегда имеет конкретную техническую причину.

Код 137 означает, что контейнер был убит системой принудительно (SIGKILL). В 99% случаев это делает ядро Linux через механизм OOM Killer, когда контейнер превышает выделенный лимит памяти.

Что именно означает код 137?

Формула проста: 128 + 9 = 137.

  • 128 — базовый код выхода для сигналов
  • 9 — номер сигнала SIGKILL (принудительное завершение)

Когда вы видите этот код, система буквально говорит: «Этот процесс съел слишком много RAM, и я его уничтожила, чтобы спасти хост».

Шаг 1: Диагностика — подтвердите OOMKilled

Прежде чем увеличивать память, убедитесь, что причина именно в ней.

# Проверьте статус последнего запуска контейнера
docker inspect --format='{{.State.OOMKilled}}' <container_name>

# Если вывод "true" — это точно нехватка памяти
# Если "false" — ищите другую причину (см. раздел ниже)

Шаг 2: Увеличение лимитов памяти

Самое очевидное решение. Но важно сделать это правильно.

Для docker run:

# Выделяем 2GB RAM + 1GB swap
docker run -m 2g --memory-swap 3g my_image

Для docker-compose.yml:

services:
  app:
    image: my_image
    deploy:
      resources:
        limits:
          memory: 2G
        reservations:
          memory: 1G

Важно: Всегда оставляйте запас 20-30% от реального потребления приложения. Если приложение ест 1.8GB, ставьте лимит минимум 2.5GB.

Шаг 3: Поиск утечек памяти

Если увеличение лимита помогает лишь временно, значит в приложении утечка памяти.

Мониторинг в реальном времени:

# Смотрим потребление памяти контейнером
docker stats <container_name>

# Или более детально через cgroups
cat /sys/fs/cgroup/memory/docker/<container_id>/memory.usage_in_bytes

Для PHP-приложений (WordPress/Laravel):

  • Проверьте memory_limit в php.ini
  • Используйте Xdebug Profiler для поиска утечек
  • Очистите кэш OPcache

Для Node.js:

# Увеличьте heap size явно
node --max-old-space-size=2048 app.js

Шаг 4: Проверьте хост-машину

Иногда контейнер невиновен. Если на хосте закончилась RAM, OOM Killer выбирает жертву случайно (обычно самый прожорливый процесс).

# Проверьте общую память хоста
free -h

# Посмотрите, кто убил контейнер в системных логах
dmesg | grep -i "killed process"
journalctl -xe | grep -i oom

Другие причины кода 137 (не OOM)

Если OOMKilled: false, но код всё равно 137:

  • Ручной docker kill — кто-то или скрипт отправил SIGKILL
  • Systemd/AppArmor — политики безопасности убивают процесс
  • Orchestrator — Kubernetes/Docker Swarm перезапускает контейнер из-за liveness probe

Чек-лист профилактики

  • Всегда задавайте --memory и --memory-swap
  • Настройте алерты на потребление RAM (Prometheus/Grafana)
  • Используйте health checks вместо бесконечных рестартов
  • Регулярно обновляйте образы (фиксы утечек)
  • Тестируйте нагрузку перед продом (stress-ng)

Что делать, если проблема не решается?

Можно ли отключить OOM Killer?

Технически да (oom_score_adj=-1000), но никогда так не делайте. Лучше пусть упадёт один контейнер, чем зависнет весь сервер.

Почему контейнер падает только ночью?

Вероятно, запускается cron-задача (бэкап, индексация, очистка), которая потребляет больше памяти, чем дневная нагрузка.

Как отличить утечку от нормальной нагрузки?

Утечка — это монотонный рост памяти без падения после GC. Нормальная нагрузка — пики и спады. Смотрите график docker stats за 24 часа.

В статье про Docker добавьте ссылку на первую: «Если вы используете Docker для WordPress и столкнулись с белым экраном, читайте наше полное руководство по WSOD