MAATRIX / Блог / Docker Compose для продакшена на сервере: частые ошибки и решения

Docker Compose для продакшена на сервере: частые ошибки и решения

Docker Compose для продакшена на сервере: частые ошибки и решения

MAATRIX

Стек на Docker Compose работает в разработке, а на сервере ведёт себя коварно: после перезагрузки половина контейнеров лежит, база внезапно потеряла данные, приложение убивается по памяти, а логи разрослись и съели диск. Эти ошибки Docker Compose для продакшена на сервере типичны, и у каждой есть понятная причина. Разберём их по схеме «симптом — причина — решение».

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

С чего начинать диагностику

Первым делом посмотрите, в каком состоянии контейнеры и почему они в нём оказались. Команда статуса показывает, что запущено, что упало и что нездорово:

docker compose ps
docker compose logs --tail=100 web

Если контейнер перезапускается по кругу, статус будет мигать между Restarting и Exited. Чтобы понять причину выхода, смотрите код завершения и последние строки лога конкретного сервиса. Отдельно полезно проверить, не упирается ли что-то в память или диск на самом хосте:

docker stats --no-stream
df -h

Эти четыре команды закрывают большинство вопросов диагностики: они показывают, какой сервис падает, из-за чего, и хватает ли серверу ресурсов. Держа их под рукой, вы отличите ошибку конфигурации от нехватки железа. Дальше разберём частые симптомы по отдельности.

Контейнеры не поднимаются после перезагрузки

Классика продакшена: сервер перезагрузился по обновлению или сбою, а сервисы не встали. Причина почти всегда одна — не задана политика перезапуска. Без неё контейнер после остановки хоста так и остаётся лежать.

Добавьте каждому сервису политику unless-stopped, которая поднимает контейнер и после сбоя, и после перезагрузки:

services:
  web:
    image: myapp:1.4.0
    restart: unless-stopped

Вторая причина — не включён автозапуск самого демона Docker. Если служба Docker не стартует при загрузке системы, никакая политика внутри не поможет. Проверьте и включите:

systemctl is-enabled docker
systemctl enable docker

Третья, более тонкая ситуация — контейнеры стартуют, но приложение падает, потому что зависимый сервис (например, база) ещё не готов. Это отдельная проблема гонки запуска, и решается она проверками здоровья, о которых ниже. После настройки политики перезапуска и автозапуска демона перезагрузка перестаёт быть проблемой: весь стек поднимается сам.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Контейнер убивается по памяти (OOM Killed)

Пугающий симптом: сервис внезапно падает, в статусе Exited (137), а в системном логе — сообщение OOM Killed. Число 137 означает, что процесс убит по нехватке памяти. Причин две, и они противоположны по решению.

Первая — у контейнера нет лимита памяти, он разросся и был убит системой, когда память сервера кончилась. Проверьте потребление:

docker stats --no-stream
dmesg | grep -i oom

Правильное решение — задать лимиты памяти каждому сервису, чтобы прожорливый контейнер убивался в одиночку, не утягивая соседей, и чтобы вы сразу видели виновника:

deploy:
  resources:
    limits:
      memory: 512M

Вторая причина — лимит есть, но занижен, и нормально работающему сервису его не хватает. Здесь нужно, наоборот, поднять лимит под реальные потребности. Ключевой вопрос — хватает ли памяти всему серверу. Если суммарные потребности контейнеров превышают RAM хоста, никакая настройка лимитов не спасёт: вы лишь выбираете, кого убивать первым. Это прямой сигнал, что проекту нужен VPS с большим объёмом памяти.

Потеря данных при пересоздании контейнера

Самая болезненная ошибка: обновили образ базы, контейнер пересоздался — и данные пропали. Причина в том, что данные хранились внутри контейнера, а не в томе. Всё, что не вынесено в volume, живёт только пока существует контейнер, и при пересоздании исчезает.

Данные обязательно монтируют в именованный том, объявленный отдельно. Тогда контейнер можно пересоздавать сколько угодно — том остаётся:

services:
  db:
    image: postgres:16
    volumes:
      - db_data:/var/lib/postgresql/data
volumes:
  db_data:

Частая тонкая ошибка — неверный путь монтирования: том подключён не к тому каталогу, где сервис реально хранит данные. Для Postgres это /var/lib/postgresql/data, для других сервисов — свой путь, и его нужно смотреть в документации образа. Проверьте, что тома существуют и данные в них есть:

docker volume ls
docker volume inspect db_data

И помните: наличие тома — не замена бэкапам. Том защищает от пересоздания контейнера, но не от повреждения данных, ошибочного удаления или гибели диска. Настройте регулярное резервное копирование содержимого томов отдельно.

Гонка запуска и неверный depends_on

Симптом: при старте стека приложение падает с ошибкой подключения к базе, но если запустить его повторно, всё работает. Это классическая гонка: приложение стартовало раньше, чем база была готова принимать соединения. Многие думают, что depends_on решает это, но обычный depends_on лишь задаёт порядок запуска, не дожидаясь готовности сервиса.

Правильное решение — условие service_healthy вместе с проверкой здоровья у зависимости:

services:
  web:
    depends_on:
      db:
        condition: service_healthy
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

Теперь приложение стартует только после того, как база прошла проверку и реально готова. Дополнительно закладывайте в само приложение устойчивость к временной недоступности зависимостей — повтор подключения с задержкой. Это защищает не только при старте, но и когда база кратковременно перезапускается в процессе работы. Комбинация healthcheck и повторов делает стек устойчивым к гонкам.

Логи съели диск и другие эксплуатационные грабли

Неожиданный сбой: всё работало, а потом сервер встал из-за нехватки места, и виноваты разросшиеся логи контейнеров. По умолчанию Docker пишет логи без ограничения размера, и болтливый сервис за недели заполняет диск. Проверьте, что занимает место:

df -h
du -sh /var/lib/docker/containers/*/*.log

Решение — ограничить размер и ротацию логов, глобально в настройках демона или на уровне сервиса в compose-файле:

services:
  web:
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

Другие частые эксплуатационные грабли: накопление старых неиспользуемых образов, которые тоже едят диск (чистится через docker image prune), и публикация портов контейнеров прямо в интернет вместо привязки к localhost за реверс-прокси. И общий вывод: значительная часть «ошибок Docker Compose» на сервере — это на деле нехватка ресурсов, когда контейнерам тесно по памяти или диску. Тонкая настройка выжимает максимум из имеющегося железа, но когда потолок достигнут, надёжнее взять VPS с запасом RAM и быстрым NVMe. У MAATRIX такие серверы доступны в локациях RU, US и UK с оплатой из России картой или криптой.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Частые вопросы

Почему контейнеры не поднимаются после перезагрузки сервера?

Не задана политика перезапуска или не включён автозапуск демона Docker; добавьте restart: unless-stopped и systemctl enable docker.

Что означает Exited (137) и OOM Killed?

Контейнер убит по нехватке памяти; задайте лимиты памяти сервисам, а если ресурсов не хватает всему серверу — увеличьте объём RAM.

Почему при обновлении образа пропали данные базы?

Данные хранились внутри контейнера, а не в томе; монтируйте их в именованный volume по правильному пути и делайте бэкапы.

Почему depends_on не спасает от ошибки подключения к базе?

Обычный depends_on не ждёт готовности; используйте condition: service_healthy с healthcheck у зависимости и повторы подключения в приложении.

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.