Знакомая картина: вы арендовали компактный VDS за 300–500 рублей в месяц (2 vCPU, 2–4 ГБ RAM), развернули на нём связку из Nginx Proxy Manager, пару сайтов клиентов, бэкенд на Node.js или Python, базу данных MySQL/PostgreSQL и Telegram-бота. Всё работает быстро и ровно, пока однажды в 3 часа ночи не прилетает тревожный алерт: сайты лежат, SSH не отвечает или сервер ушёл в глухой ступор.
Вы заходите в консоль VDS через панель хостинга, перезагружаете сервер, открываете dmesg или journalctl и видите страшную надпись: Out of memory: Kill process (mysqld) score ... or sacrifice child. Один из фоновых контейнеров поймал утечку памяти (memory leak) или зациклился, а ядро Linux принудительно убило самый ценный процесс — базу данных, похоронив работу всех остальных клиентских проектов.
⚠️ Главная иллюзия контейнеризации
По умолчанию Docker не ограничивает оперативную память и процессорное время для контейнеров. Если контейнер запустит бесконечный цикл или выделит 3 ГБ RAM на сервере с 2 ГБ, он без ограничений сожрёт всю память хоста, вызовет панику ядра Linux (OOM) и уронит соседние службы.
1. Анатомия катастрофы: почему при сбое одного контейнера падает весь VDS
В ядре Linux работает защитный механизм — Out of Memory (OOM) Killer. Когда физическая оперативная память и Swap-пространство полностью исчерпаны, ядро оказывается перед выбором: либо полный крах всей операционной системы (Kernel Panic), либо принудительное уничтожение одного из процессов.
Для выбора «жертвы» Linux рассчитывает показатель oom_score (от 0 до 1000). Чем больше оперативной памяти занимает процесс и чем дольше он работает, тем выше его балл. Именно поэтому, когда течет легкий скрипт или фоновый воркер, OOM Killer практически никогда не убивает виновника. Он хладнокровно уничтожает MySQL, PostgreSQL или мастер-процесс PHP-FPM / Nginx, потому что те держат в буферах основной массив памяти.
Результат: виновник утечки продолжает потреблять остатки ресурсов, а сайты клиентов выдают ошибки 502 Bad Gateway или Error establishing a database connection.
2. Синтаксис лимитов в Docker Compose v2: limits vs reservations
Долгое время вокруг лимитов ресурсов в Docker Compose была путаница. В старых версиях Compose v1 директива deploy.resources работала только в режиме Docker Swarm, а при обычном docker-compose up требовалось указывать устаревший флаг --compatibility.
Начиная с Docker Compose v2 (встроенного прямо в CLI docker compose без дефиса), блок deploy.resources стал официальным стандартом и полноценно применяется для любых одиночных серверов. В конфигурации используются два фундаментальных понятия:
limits.memory(Hard Limit — жесткий потолок): Абсолютный предел памяти, который ядро cgroups разрешает выделить контейнеру. Если процесс внутри контейнера попытается запросить хотя бы на 1 байт больше этого значения, OOM Killer убьет только этот конкретный контейнер (с кодом выхода137), а хост-система и соседние контейнеры продолжат работать без малейших сбоев.reservations.memory(Soft Limit — гарантированный минимум): Объем оперативной памяти, который Docker гарантирует контейнеру при запуске. Если на сервере возникнет дефицит ресурсов, ядро не станет отбирать эту зарезервированную память.
💡 Совет инженера: Если вы используете сертифицированные дистрибутивы Ubuntu 22.04 / 24.04 LTS и современный Docker Engine 25+, всегда задавайте жесткий лимит limits.memory для каждого контейнера без исключения. Для второстепенных ботов ставьте от 128M до 256M, для веб-серверов 256M–512M, для баз данных — 512M–1.5G в зависимости от тарифа VDS.
3. Ограничение процессора (CPU): защита от 100% зависаний ядер
Вторая распространенная проблема дешевых серверов — зависший скрипт или регулярное выражение в парсере, которое загружает 1 vCPU на 100%. Из-за этого входящие сетевые пакеты Nginx перестают обрабатываться вовремя, а время отклика (TTFB) подскакивает с 50 мс до 15 секунд.
В блоке deploy.resources.limits параметр cpus задается дробным числом, определяющим доступную долю процессорного времени:
cpus: '0.50'— контейнеру разрешено использовать не более 50% мощности одного процессорного ядра.cpus: '1.50'— контейнер может утилизировать до полутора ядер суммарно на многоядерном сервере.
Для фоновых воркеров, ботов и сборщиков логов никогда не оставляйте неограниченный CPU: лимит 0.50–0.75 гарантирует, что даже при глубоком зацикливании сервер сохранит отзывчивость по SSH и продолжит отдавать статику в вебе.
4. Готовый эталонный docker-compose.yml с изоляцией ресурсов
Ниже представлен готовый боевой шаблон docker-compose.yml для типичного VDS с 2 vCPU и 4 ГБ RAM. В нем развернуты 4 изолированных сервиса: обратный прокси Nginx, веб-приложение, фоновый Telegram-бот и база данных MariaDB/MySQL.
version: '3.8'
services:
# 1. Обратный прокси-сервер (Nginx)
reverse-proxy:
image: nginx:alpine
container_name: prod_proxy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
deploy:
resources:
limits:
cpus: '0.75'
memory: 256M
reservations:
memory: 64M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
- web_net
# 2. Основное веб-приложение (Node.js / Python / PHP)
web-app:
image: node:20-alpine
container_name: prod_webapp
restart: unless-stopped
command: ["node", "server.js"]
deploy:
resources:
limits:
cpus: '1.00'
memory: 768M
reservations:
memory: 256M
logging:
driver: "json-file"
options:
max-size: "15m"
max-file: "3"
depends_on:
- db
networks:
- web_net
- internal_net
# 3. Фоновый Telegram-бот или воркер
tg-bot:
image: python:3.12-slim
container_name: prod_bot
restart: on-failure:5
deploy:
resources:
limits:
cpus: '0.40'
memory: 256M
reservations:
memory: 64M
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
- internal_net
# 4. База данных (MariaDB 11 / MySQL 8)
db:
image: mariadb:11
container_name: prod_db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: "StrongGeneratedPassword_2026!"
MYSQL_DATABASE: "production_db"
volumes:
- db_data:/var/lib/mysql
deploy:
resources:
limits:
cpus: '1.20'
memory: 1200M
reservations:
memory: 512M
logging:
driver: "json-file"
options:
max-size: "20m"
max-file: "3"
networks:
- internal_net
volumes:
db_data:
networks:
web_net:
driver: bridge
internal_net:
driver: bridge
Суммарный жесткий лимит всех контейнеров в этом примере: 256M + 768M + 256M + 1200M = 2480M (~2.4 ГБ). Это идеальный баланс для VDS с 4 ГБ памяти: у операционной системы хоста, демона Docker, SSHD и дискового кэша остается гарантированный буфер в 1.5 ГБ RAM, что полностью исключает падение ядра.
5. Вторая главная боль: защита диска от переполнения логами (json-file)
Вторая по популярности причина внезапной гибели VDS — переполнение диска логами Docker (/var/lib/docker/containers/.../*-json.log). По умолчанию Docker сохраняет весь стандартный вывод контейнеров без ограничения размера. Если скрипт начинает спамить ошибками соединения в stdout, за пару суток файл лога может разрастись до 20–40 ГБ, диск забивается до 100% (ошибка No space left on device), и Docker аварийно завершает работу.
Чтобы раз и навсегда решить эту проблему на уровне всего сервера, настройте глобальный конфиг демона Docker /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
После сохранения примените настройки перезапуском службы: sudo systemctl restart docker. Теперь ни один контейнер на сервере физически не сможет занять под логи более 30 МБ (3 файла по 10 МБ с автоматической ротацией и удалением старых записей).
6. Стратегии перезапуска: почему restart: always опасен на дешевом VDS
Многие вебмастера машинально пишут для всех сервисов директиву restart: always. На недорогом VDS с ограниченными ресурсами это скрытая бомба замедленного действия:
| Политика restart | Поведение при падении | Где использовать |
|---|---|---|
unless-stopped |
Перезапускается всегда, кроме случая, когда контейнер был остановлен вручную командой docker stop. | Критически важные сервисы (Nginx, База данных, основной Web). |
on-failure:5 |
Перезапускается только при ненулевом коде выхода (падение, OOM) максимум 5 раз. Если падает постоянно — останавливается. | Telegram-боты, парсеры, воркеры очередей, интеграционные скрипты. |
always |
Перезапускается бесконечно. При фатальной ошибке запускает бесконечный цикл (bootloop), утилизируя CPU хоста. | Не рекомендуется для фоновых скриптов на слабых серверах. |
Если в вашем Telegram-боте произойдет фатальный синтаксический сбой или отвалится внешний API, политика on-failure:5 предотвратит цикличный бесконечный перезапуск и сохранит ресурсы сервера для стабильной работы клиентских сайтов.
7. Диагностика в реальном времени: docker stats и быстрый поиск утечек
Чтобы в любой момент увидеть реальную картину потребления ресурсов каждым контейнером, выполните в терминале хоста команду:
docker stats --no-stream --format "table {{.Name}} {{.CPUPerc}} {{.MemUsage}} {{.MemPerc}} {{.NetIO}}"
Вывод отобразит лаконичную таблицу с точными процентами использования выделенной памяти и CPU:
NAME CPU % MEM USAGE / LIMIT MEM % NET I/O
prod_db 1.25% 412.5MiB / 1.172GiB 34.35% 18.4MB / 12.1MB
prod_webapp 0.45% 184.2MiB / 768MiB 23.98% 42.6MB / 95.3MB
prod_proxy 0.10% 24.8MiB / 256MiB 9.69% 152MB / 164MB
prod_bot 0.05% 68.1MiB / 256MiB 26.60% 2.4MB / 1.8MB
Если вы видите, что показатель MEM % приближается к 90–95%, значит приложение накапливает данные без очистки сборщиком мусора, либо размер буфера не соответствует реальному объему трафика. Это сигнал заблаговременно провести профилирование кода до срабатывания OOM Killer.
Для постоянного автоматического мониторинга доступности сайтов и замера времени ответа (TTFB) подключите официального Telegram-бота платформы: @syskit_uptime_bot — он уведомит вас о любых сбоях HTTP и истечении SSL-сертификатов за считанные секунды.
⚡ Бесплатный мониторинг сайтов и серверов в Telegram
Добавьте свои домены и контейнеры в бота @syskit_uptime_bot за 1 минуту: получайте мгновенные алерты о простоях и держите аптайм под полным контролем 24/7.
8. Резюме и чек-лист стабильного Docker-сервера
Развертывание множества проектов на одном доступном VDS — отличная стратегия оптимизации бюджета, если инфраструктура настроена профессионально. Краткий чек-лист надежности:
- Всегда указывайте
deploy.resources.limits.memoryдля каждого контейнера вdocker-compose.yml. - Ограничивайте CPU (например
cpus: '0.50') для некритичных скриптов и ботов. - Настройте ротацию логов через
json-fileс лимитомmax-size: 10mв/etc/docker/daemon.json. - Используйте
restart: on-failure:5для ботов вместо слепогоrestart: always. - Оставляйте хост-системе запас минимум в 1–1.5 ГБ неразмеченной оперативной памяти под ядро и системный кэш.
🚀 Нужен надежный сервер под боевой Docker-стек?
Для комфортной работы контейнеров выбирайте проверенные VDS с быстрыми накопителями NVMe и чистой изоляцией ресурсов:
9. Часто задаваемые вопросы (FAQ)
Рекомендуемые VDS-провайдеры
Отказоустойчивые сервера с быстрыми NVMe и каналом до 1 Гбит/с
Timeweb Cloud VDS
Сверхбыстрые NVMe диски до 3000 МБ/с, идеальны для запуска Docker Compose с базами данных и веб-сервисами. Почасовая оплата и моментальные снапшоты перед деплоем.
Selectel Cloud VPS
Выделенные KVM-ресурсы с гарантированной частотой процессоров. Чистая изоляция ядер и честные лимиты без оверселлинга для продуктивных контейнеров.
Beget VPS
Готовый шаблон с предустановленным Docker Engine в 1 клик. Бесплатные ежедневные резервные копии всего VDS и круглосуточная поддержка сисадминов.