Краткий вывод: Для стабильной работы Docker и Docker Compose сервер обязан базироваться исключительно на полноценной аппаратной виртуализации с выделенным ядром, накопителем NVMe и актуальной версией Linux. Контейнеризация на разделяемом ядре хоста гарантированно приведет к сбоям сетевого стека и падениям демона.
Содержание
- Аппаратные требования и сайзинг: Какой VPS нужен для Docker и Docker Compose в 2026 году
- Критерии выбора VPS под контейнеризацию: виртуализация, ядро и оверселлинг
- Цены и тарифы: сколько стоит купить VPS для Docker и Docker Compose
- Готовый комплект для продакшна: архитектура базового стека сервисов
- Кейс: развертывание интернет-магазина в Docker Compose на KVM VPS
- Настройка VPS для Docker и Docker Compose своими руками: пошаговое руководство
- Новинки Docker и Compose v2: переход на современные стандарты
- Чек-лист распространенных ошибок при эксплуатации Docker на VPS
- Часто задаваемые вопросы (FAQ)
Аппаратные требования и сайзинг: Какой VPS нужен для Docker и Docker Compose в 2026 году
- Тип гипервизора: Только KVM виртуализация. Контейнерные VPS (OpenVZ, LXC) непригодны: общее с хост-нодой ядро блокирует корректную работу драйвера хранилища
overlay2, запрещает тонкую настройку лимитов ресурсов через cgroups v2, не позволяет загружать модули ядра для сетевых фильтров (iptables/nftables,ebpf) и часто лишена возможности настроить собственный swap. KVM дает независимое ядро Linux (рекомендуется 6.x) и изолированное адресное пространство памяти. - Минимальные требования (Dev-окружение, пет-проекты): 1 vCPU, 2 GB RAM, 20 GB NVMe. Серверы с 1 GB RAM брать бессмысленно: фоновые процессы
dockerdиcontainerdпотребляют 150–250 MB, а запуск типового стека (Nginx + приложение на Node.js/Go + Redis) моментально триггерит системныйOOM Killerпри первом же всплеске трафика или выполнении миграций. - Рекомендованный боевой стек (Production, микросервисы в Docker Compose): от 2 до 4 vCPU, 4–8 GB RAM, 50+ GB NVMe. Этот объем покрывает нужды связки из бэкенда, очередей (RabbitMQ/Redis), СУБД (PostgreSQL) с достаточным объемом для
shared_buffersи дискового кэша ОС (Page Cache), предотвращающего деградацию производительности. - Дисковая подсистема и задержка (p99 latency): Docker генерирует интенсивную нагрузку на случайные операции ввода-вывода (Random 4K R/W). Сборка многослойных образов через BuildKit распаковывает десятки тысяч мелких файлов: медленный SATA SSD или переподписанный HDD с задержками диска p99 > 20 мс загоняет процессор в глухой
iowait, растягивает деплой в разы и приводит к таймаутамhealthcheckпри старте контейнеров. Для продакшна критически важен диск с гарантированными NVMe IOPS (от 5 000–10 000 IOPS при задержке p99 < 1–2 мс), обеспечивающий бессбойное создание snapshot-слоев и быструю синхронизацию именованных томов (named volumes).
Критерии выбора VPS под контейнеризацию: виртуализация, ядро и оверселлинг
Архитектура виртуализации и конфигурация ядра определяют, сможет ли хост стабильно держать нагрузку контейнеров под пиковым трафиком или уйдет в циклический отказ. Контейнеризация через Docker делит ресурсы хостовой ОС, поэтому любые аппаратные ограничения, скрытый оверселлинг или блокировки модулей ядра на уровне гипервизора напрямую бьют по контейнерам.
Виртуализация: KVM vs LXC/OpenVZ и сетевой стек
Для продакшн-окружения на базе Docker и Docker Compose подходит исключительно аппаратная виртуализация KVM (или bhyve/Proxmox VE с полноценным QEMU-эмулятором). Контейнерная виртуализация уровня ОС (LXC/OpenVZ 7) непригодна для развертывания dockerd по следующим техническим причинам:
- Единое разделяемое ядро хоста: В OpenVZ и LXC гостевая система использует ядро хост-ноды. Вы лишены возможности обновлять ядро, применять кастомные патчи безопасности или активировать специфические подсистемы (например, eBPF для сетевого мониторинга Cilium или расширенные cgroups v2).
- Блокировка системных вызовов и namespaces: В непривилегированных контейнерах LXC заблокирован системный вызов
mountдля ряда файловых систем. Запуск Docker требует режима nested virtualization (Docker-in-LXC), что создает критические уязвимости безопасности (проброс привилегий root) и приводит к сбоям драйвера хранилищаoverlay2. - Сетевой драйвер и модуль
br_netfilter: Сетевая изоляция Docker опирается на мосты (bridge) и правилаiptables/nftables. Чтобы трафик между контейнерами корректно проходил через цепочки фаервола хоста, ядро обязано загружать модульbr_netfilter. На OpenVZ этот модуль часто недоступен пользователю, из-за чего межконтейнерный трафик внутриdocker-composeсетей теряет изоляцию, а внутренний DNS-резолвер Docker (127.0.0.11) зависает при резолве имен сервисов.
На KVM пользователь получает изолированное кольцо защиты Ring 0, собственное ядро и полный доступ к управлению модулями:
# Проверка и принудительная загрузка br_netfilter на KVM
sudo modprobe br_netfilter
lsmod | grep br_netfilter
| Параметр сравнения | KVM (Kernel-based Virtual Machine) | OpenVZ / LXC (Контейнерная) | Влияние на стек Docker & Compose |
|---|---|---|---|
| Изоляция ядра | Полная (собственное виртуальное ядро Linux) | Отсутствует (общее ядро хост-машины) | В LXC сбой чужого процесса на хосте может уронить ваше окружение |
| Драйвер хранилища | Нативная поддержка overlay2 |
Проблемы с fuse-overlayfs или vfs |
Деградация скорости дисковых операций в LXC до 3–5 раз |
| Сетевые модули | Доступны br_netfilter, ip_tables, vxlan |
Ограничены конфигурацией хост-ноды | В OpenVZ ломается маршрутизация и port forwarding в Compose |
| Контрольные группы | Полная поддержка cgroups v2 | Часто урезанный cgroups v1 | Лимиты памяти (mem_limit) и CPU в Compose игнорируются или сбоят |
| Вердикт для Docker | Production-ready стандарт | Не рекомендуется / непригодно | Непредсказуемые отказы демона dockerd |
Скрытый оверселлинг: выявление CPU Steal Time (%st)
Оверселлинг vCPU — распространенная практика бюджетных хостеров, при которой на одно физическое ядро (Thread) аллоцируется до 8–12 виртуальных процессоров. Если соседи по физическому серверу утилизируют вычислительные мощности, ваши контейнеры встают в очередь ожидания планировщика гипервизора.
Главный маркер дефицита процессорного времени — метрика CPU Steal Time (%st). Она отражает процент времени, в течение которого виртуальный процессор был готов к исполнению инструкций, но гипервизор не выделил ему физические такты CPU.
# Мониторинг CPU Steal Time в реальном времени (столбец %st)
mpstat 1 5
# Либо через vmstat (последний столбец st)
vmstat 1 5
- Норма:
%st=0.0% – 0.5%. Кратковременные всплески до1.5%допустимы при пиковых нагрузках ноды. - Троттлинг: Постоянный показатель
%st > 3.0%означает критический оверселлинг.
При высоком %st происходят следующие сбои: * Резко возрастает задержка ответа (p99 latency) веб-серверов Nginx/Traefik. * Происходит рассинхронизация heartbeat в кластерах (Redis Sentinel, etcd, MongoDB Replica Set), что инициирует ложные переключения master-нод. * Зависают фоновые воркеры Celery / RabbitMQ из-за микрозадержек планировщика Linux.
Дисковая подсистема: преимущество KVM NVMe над SATA SSD
Работа Docker сопряжена с интенсивной мелкоблочной случайной записью (Random I/O 4K). При сборке образов, выполнении docker pull и слоистой записи overlay2 распаковываются десятки тысяч мелких файлов конфигураций, библиотек и метаданных.
- SATA SSD (AHCI протокол): Ограничен одной очередью команд глубиной в 32 запроса (32 queue depth) и пропускной способностью шины ~550 МБ/с. В моменты одновременного сброса буферов баз данных (PostgreSQL WAL, MySQL redo log) и деплоя нового контейнера диск уходит в полку по I/O, параметр
%wa(I/O Wait) в выводеtopпревышает20–30%, блокируя все процессы ядра. - NVMe SSD (PCIe Gen3/Gen4): Использует параллельный протокол с поддержкой до 64 000 очередей по 64 000 команд в каждой. Задержка доступа снижается с 1000–1500 мкс (SATA) до 30–70 мкс (NVMe), а производительность на случайных операциях возрастает с 40 000–80 000 IOPS до 400 000–800 000+ IOPS.
NVMe исключает узкие горлышки при одновременной работе ротации логов контейнеров (json-file), транзакционных СУБД и выполнения фоновых задач в Compose.
Конфигурация swap и swappiness для защиты демона dockerd
Нехватка оперативной памяти приводит к срабатыванию подсистемы ядра OOM Killer (Out-Of-Memory Killer). По умолчанию OOM Killer рассчитывает показатель oom_score каждого процесса на основе объема потребляемой им RAM и прибивает наиболее «тяжелый» процесс сигналом SIGKILL. В контейнерной среде жертвой нередко становится сам демон dockerd или containerd-shim, что влечет падение всех запущенных сервисов.
Наличие swap-пространства обязательно даже при достаточном объеме RAM — оно служит буфером для сброса «холодных» анонимных страниц памяти, предотвращая аварийную блокировку системы при резких спайках нагрузки.
Оптимальная стратегия — организация swap на NVMe с жестким контролем интенсивности сброса страниц через параметры sysctl.
1. Создание и оптимизация swap-файла на NVMe
# Создание swap-файла размером 4GB без фрагментации через fallocate
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# Персистентное подключение в /etc/fstab
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
2. Тюнинг ядра через sysctl
Значение vm.swappiness по умолчанию в Ubuntu/Debian равно 60. Это заставляет ядро преждевременно сбрасывать страницы памяти в swap, вызывая просадки производительности. Для серверов с Docker параметр снижается до 10 (сброс в swap только при исчерпании доступной физической памяти).
Создайте конфигурационный файл /etc/sysctl.d/99-docker-performance.conf:
# Минимизация сброса рабочей памяти в swap
vm.swappiness = 10
# Защита от блокировок при выделении виртуальной памяти
vm.overcommit_memory = 1
# Сетевой роутинг и фильтрация мостов для Docker
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
# Лимит открытых файловых дескрипторов под высокую плотность контейнеров
fs.file-max = 2097152
Примените параметры немедленно без перезагрузки:
sudo sysctl --system
3. Иммунитет dockerd к OOM Killer
Чтобы гарантировать выживание управляющего демона Docker при нехватке памяти, зафиксируйте для него минимальный приоритет на уничтожение через systemd. Задайте OOMScoreAdjust=-1000 в оверрайде сервиса:
sudo systemctl edit docker
Внесите блок:
[Service]
OOMScoreAdjust=-1000
Перезагрузите демон:
sudo systemctl daemon-reload
sudo systemctl restart docker
При исчерпании лимитов памяти OOM Killer завершит аварийный изолированный контейнер-виновник утечки (например, упавший процесс Node.js или Java heap), но сохранит рабочий dockerd и сопутствующие критические сервисы инфраструктуры.
Цены и тарифы: сколько стоит купить VPS для Docker и Docker Compose
Базовая vps для docker и docker compose цена на рынке начинается от 450–600 ₽/мес за минимальный инстанс для пет-проектов и доходит до 4 000–12 000 ₽/мес за отказоустойчивые production-ноды с гарантированной частотой процессора. При развертывании контейнерного стека решающее значение имеет архитектура виртуализации: пригодны исключительно серверы на базе KVM. Контейнеризация поверх OpenVZ или LXC исключает кастомную настройку модулей ядра (iptables, overlay2, cgroups v2), что блокирует запуск Docker Engine.
Грамотный расчет ресурсов требует закладывать системный оверхед: сам демон dockerd, runtime containerd, сборщик логов и сетевые мосты (docker0, overlay) потребляют от 400 до 800 МБ RAM до старта первого пользовательского сервиса. Если памяти недостаточно, Linux активирует oom-killer, который в первую очередь принудительно завершает ресурсоемкие процессы контейнеров (чаще всего базы данных).
Матрица подбора конфигурации под объем контейнеров
Попытка запустить микросервисную архитектуру на тарифе без запаса по RAM приводит к деградации дискового ввода-вывода из-за постоянного сброса страниц в swap.
Ниже приведена матрица для планирования бюджета при выборе KVM NVMe сервера:
| Масштаб стека | Минимальная конфигурация (vCPU / RAM / NVMe) | Типовой состав Docker Compose сервисов | Диапазон цен (РФ / Зарубеж) | Риски и узкие места |
|---|---|---|---|---|
| Пет-проект / Тест (до 5 контейнеров) | 1–2 vCPU (Shared) 2–4 ГБ RAM 30–40 ГБ NVMe |
Nginx/Traefik, Go/Node.js API, SQLite/Redis, Telegram-бот | 450 – 900 ₽/мес ($5 – $10/mo) |
Дефицит памяти при сборке образов (docker build) прямо на сервере. |
| Staging / Небольшой Prod (10–20 контейнеров) | 4 vCPU (Fair share $\ge 50\%$) 8–16 ГБ RAM 80–120 ГБ NVMe |
Reverse Proxy, PostgreSQL, Redis, 2-3 Backend API, Celery/RabbitMQ, Prometheus + Node Exporter | 1 800 – 4 500 ₽/мес ($20 – $45/mo) |
Высокий IOPS при одновременной записи логов и транзакций БД; требуется тонкая настройка logging.driver. |
| Highload Production (50+ контейнеров) | 8+ vCPU (100% Dedicated Core) 32–64 ГБ RAM 200–400 ГБ NVMe (RAID-10) |
Полный микросервисный стек, Kafka/ClickHouse, Elasticsearch/OpenSearch, Grafana + Loki, CI/CD runners | 7 500 – 18 000+ ₽/мес ($80 – $200+/mo) |
Network socket exhaustion, переполнение таблицы conntrack (nf_conntrack_max), CPU Steal Time при оверселлинге. |
Скрытые статьи расходов: реальный TCO хостинга
При формировании счета итоговая стоимость владения виртуальным сервером часто возрастает на 30–60% относительно базового тарифа на лендинге. Чтобы выгодно купить vps для docker и docker compose, необходимо учитывать скрытые параметры биллинга:
- Исходящий сетевой трафик:
Большинство зарубежных и ряд отечественных провайдеров включают в базовую стоимость пакет от 1 до 20 ТБ/мес. Перерасход тарифицируется по цене от 0.01$ до 0.08$ за гигабайт. При активной отдаче медиафайлов или регулярном выкачивании «тяжелых» образов из приватных Docker Registry плата за превышение полосы может превысить стоимость самого сервера в 2–3 раза. - Выделенный IPv4-адрес:
В условиях глобального дефицита IPv4 хостеры выводят адрес из базовой стоимости. Аренда одного IPv4 обходится в 150–350 ₽/мес ($1.5–$3.5/mo). Подсеть IPv6 (/64) обычно бесплатна, однако внешние API и клиенты без поддержки IPv6 не смогут обращаться к сервисам напрямую без промежуточного reverse-proxy. - Резервное копирование и снапшоты:
Автоматические снапшоты (point-in-time snapshot диска KVM) тарифицируются отдельно и составляют 15–25% от стоимости тарифа либо около 0.04–0.07$ за ГБ занятого дискового пространства в месяц. Хранение бэкапов на том же физическом накопителе создает единую точку отказа, поэтому для баз данных в Docker Compose обязателен вынос дампов в S3-совместимое объектное хранилище (дополнительно 200–500 ₽/мес за бакет). - Характер выделения vCPU (CPU Steal Time):
Дешевые тарифы используют распределенные ядра (Shared CPU c лимитом 10–20% от ядра). При пиковых нагрузках, компиляции или работе Garbage Collector метрика%st(Steal Time) вtop/htopподскакивает выше 15–20%, приводя к «зависанию» контейнеров. Для критических сервисов требуется тариф с выделенными ядрами (Dedicated vCPU) с частотой не ниже 3.2–3.8 ГГц.
Готовый комплект для продакшна: архитектура базового стека сервисов
Развертывание боевых приложений на изолированном инстансе требует стандартизированного окружения: маршрутизации входящего трафика, изоляции сетевых контуров, сбора телеметрии и контролируемого обновления образов. Базовый vps для docker и docker compose комплект инфраструктурных утилит потребляет суммарно от 350 до 550 МБ оперативной памяти и разворачивается через единый Compose-манифест до запуска целевых бизнес-сервисов.
Архитектура стека строится по принципу минимально необходимых привилегий: приложения никогда не публикуют порты напрямую в хост-сеть (0.0.0.0), управление демоном изолируется через сокет-прокси, а метрики собираются напрямую из псевдофайловых систем ядра (cgroups, procfs, sysfs).
Матрица компонентов базовой инфраструктуры
| Компонент | Роль в стеке | RAM (idle / нагрузка) | Сетевой контур | Вектор отказа / Ограничения |
|---|---|---|---|---|
| Traefik | Ingress, Reverse-proxy, Let's Encrypt | 45 МБ / 120 МБ | host: 80, 443, сеть proxy-net |
Ошибки в лейблах контейнеров приводят к 404/502 на роутере |
| Nginx Proxy Manager | Ingress + Web UI для ручного управления | 110 МБ / 250 МБ | host: 80, 443, 81, сеть proxy-net |
Зависимость от MariaDB/SQLite; не подходит под строгий GitOps |
| Portainer CE | Web-интерфейс аудита и управления | 30 МБ / 80 МБ | Сеть internal-admin, без доступа из WAN |
Прямой проброс /var/run/docker.sock дает root-доступ к хосту |
| Docker Socket Proxy | RBAC-экран перед Docker API | 15 МБ / 25 МБ | Внутренняя сеть socket-net |
Блокировка нужных API-эндпоинтов ломает часть функций UI |
| cAdvisor | Сбор per-container метрик (CPU, RAM, I/O) | 60 МБ / 180 МБ | Сеть monitoring-net |
Нагрузка на диск при высоком churn rate (частом пересоздании) контейнеров |
| Node Exporter | Сбор системных метрик ядра и ОС хоста | 20 МБ / 35 МБ | host: 9100 (привязка к 127.0.0.1) |
Требует монтирования /proc и /sys в режиме ro |
| Watchtower | Автоматизация ротации контейнеров | 15 МБ (sleep) / 40 МБ | Сеть socket-net |
Неконтролируемый перезапуск БД-контейнеров без миграций схемы |
Ingress и автоматический TLS: Traefik против Nginx Proxy Manager
Маршрутизация внешнего трафика на продакшн-сервере должна полностью исключать ручное перевыпускание SSL-сертификатов и правку конфигурационных файлов Nginx при каждом деплое.
- Traefik v3 (Production Standard):
- Декларативность: Конфигурируется исключительно через Docker labels целевых контейнеров. Роутер автоматически перехватывает запуск нового сервиса в сети
proxy-net. - Zero-Downtime TLS: Интегрированный ACME-клиент запрашивает сертификаты Let's Encrypt через HTTP-01 или DNS-01 challenge (для wildcard-доменов).
- Безопасность: Поддерживает авторедирект с HTTP на HTTPS, активацию HSTS (preloading), ограничение TLS до версии 1.3 и настройку
rate-limitна уровне middleware. - Nginx Proxy Manager (Альтернатива для малых стендов):
- Подходит для разработчиков, которым необходим визуальный дашборд с авторизацией по паролю или 2FA.
- Минус в продакшне: слабая совместимость с CI/CD-пайплайнами, конфигурации сохраняются в реляционной базе данных, а не в репозитории кода.
Безопасное администрирование: Portainer CE и изоляция docker.sock
Монтирование /var/run/docker.sock напрямую в веб-интерфейс управления Portainer CE — критическая уязвимость. Любой пользователь, получивший доступ к UI (или эксплойт контейнера), может создать привилегированный контейнер с флагом --privileged и смонтировать корень хостовой файловой системы / с правами суперпользователя.
Архитектурное решение — изоляция через Docker Socket Proxy (tecnativa/docker-socket-proxy). Прокси-контейнер перехватывает запросы к сокету и пропускает только безопасные вызовы по белому списку:
- Разрешено (GET):
/containers,/images,/networks,/volumes,/version,/info. - Запрещено (POST/DELETE к критическим ресурсам):
/swarm,/plugins,/auth, создание privileged-контейнеров.
Веб-порт Portainer (9000/9443) никогда не публикуется наружу без аутентификации. Доступ организуется через обратный прокси с ограничением по IP-адресу или через WireGuard/Tailscale VPN.
Мониторинг и автоматизация: cAdvisor, Node Exporter, Watchtower
Инфраструктурный контур завершают сервисы аудита ресурсов и поддержания актуальности образов.
- cAdvisor (Container Advisor): Читает данные напрямую из файловой системы Linux cgroups (
/sys/fs/cgroup). Предоставляет численные данные о реальном потреблении памяти (учитывая RSS и Page Cache), дропах пакетов на виртуальных интерфейсахvethи троттлинге процессора (container_cpu_cfs_throttled_seconds_total). - Node Exporter: Мониторит утилизацию самого VPS: процент CPU Steal Time (
%st— индикатор оверселлинга у хостера), задержки дискового I/O (iowait) и дефицит inodes. - Watchtower: Оптимизированный демон для обновления контейнеров без пересборки. В боевом окружении запрещено включать безусловное автообновление для всего сервера.
Безопасная конфигурация Watchtower: * Запуск с флагом --label-enable (обновляются только контейнеры с лейблом com.centurylinklabs.watchtower.enable=true). * Очистка старых слоев с помощью --cleanup во избежание переполнения дискового пространства хоста. * Отправка web-уведомлений (Telegram, Discord, Slack) о статусе ротации образов.
Эталонный docker-compose.infra.yml
Манифест объединяет базовый стек в единый воспроизводимый сценарий. Перед запуском создается внешний ingress-мост:docker network create proxy-net
services:
# Ingress-роутер и автоматический SSL Let's Encrypt
traefik:
image: traefik:v3.1
container_name: infra-traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
ports:
- "80:80"
- "443:443"
environment:
- TZ=UTC
volumes:
- /etc/localtime:/etc/localtime:ro
- ./traefik/acme.json:/acme.json:rw
command:
- "--providers.docker=true"
- "--providers.docker.endpoint=tcp://socket-proxy:2375"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=proxy-net"
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "[email protected]"
- "--certificatesresolvers.letsencrypt.acme.storage=/acme.json"
networks:
- proxy-net
- socket-net
# Шлюз безопасности для изоляции Docker API
socket-proxy:
image: tecnativa/docker-socket-proxy:latest
container_name: infra-socket-proxy
restart: unless-stopped
read_only: true
tmpfs:
- /run
environment:
- LOG_LEVEL=warning
- CONTAINERS=1
- IMAGES=1
- NETWORKS=1
- VOLUMES=1
- INFO=1
- POST=0
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- socket-net
# Интерфейс администрирования
portainer:
image: portainer/portainer-ce:2.21.0-alpine
container_name: infra-portainer
restart: unless-stopped
security_opt:
- no-new-privileges:true
volumes:
- ./portainer_data:/data
command: -H tcp://socket-proxy:2375
labels:
- "traefik.enable=true"
- "traefik.http.routers.portainer.rule=Host(`portainer.example.com`)"
- "traefik.http.routers.portainer.entrypoints=websecure"
- "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
- "traefik.http.services.portainer.loadbalancer.server.port=9000"
networks:
- proxy-net
- socket-net
# Сбор метрик контейнеров
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.49.1
container_name: infra-cadvisor
restart: unless-stopped
privileged: true
devices:
- /dev/kmsg:/dev/kmsg
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
labels:
- "traefik.enable=false"
networks:
- monitoring-net
# Контролируемый апдейтер контейнеров
watchtower:
image: containrrr/watchtower:latest
container_name: infra-watchtower
restart: unless-stopped
environment:
- DOCKER_HOST=tcp://socket-proxy:2375
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_LABEL_ENABLE=true
- WATCHTOWER_POLL_INTERVAL=86400
networks:
- socket-net
networks:
proxy-net:
external: true
socket-net:
internal: true
monitoring-net:
internal: true
Запуск данного набора гарантирует изоляцию системного сокета хоста, автоматизирует выпуск TLS-сертификатов для любых будущих сервисов и формирует стабильный фундамент для масштабирования проектов без ручного вмешательства в конфигурацию системы.
Кейс: развертывание интернет-магазина в Docker Compose на KVM VPS
Развертывание стека электронной коммерции на KVM VPS (4–8 vCPU, 8–16 ГБ RAM, NVMe-накопитель) для проекта с расчетной нагрузкой до 60–80 rps требует изоляции сетевого контура, фиксации дисковых квот и защиты транзакционных данных. Настраивая надежный vps для docker и docker compose интернет магазин избавляют от накладных расходов Kubernetes, сохраняя предсказуемую производительность СУБД и повторяемость окружения.
Стек сервисов интернет-магазина: * Edge Proxy: Nginx с терминацией TLS 1.3 и отдачей статики. * Application Backend: контейнеризованный сервис бизнес-логики (Go / Node.js / Python). * Транзакционная СУБД: PostgreSQL 16. * Кэш и сессии: Redis cache (in-memory хранилище с периодическим RDB-сбросом). * Backup-агент: вспомогательный контейнер для выгрузки дампов в S3-хранилище по расписанию.
1. Архитектурное сегментирование: изоляция сетей на уровне Docker Bridge
Использование дефолтной bridge-сети Docker в продакшене создает прямую угрозу безопасности: стандартная сеть не имеет внутреннего DNS (разрешение имен идет только по устаревшим link-связям), а скомпрометированный веб-контейнер получает прямой сетевой доступ к портам базы данных.
В боевом сценарии реализуется строгая изоляция сетей на два независимых контура: * frontend_net: связывает Nginx и backend-сервис. У базы данных и кэша доступа в эту сеть нет. * backend_net: закрытый внутренний контур, объединяющий backend, PostgreSQL и Redis cache. Для сети активирован флаг internal: true, который запрещает шлюзование внешнего трафика.
[ Интернет ]
│
(443/80)
▼
┌──────────────┐ frontend_net ┌──────────────┐
│ Nginx │ ───────────────> │ Backend │
└──────────────┘ └──────┬───────┘
│ backend_net (internal: true)
┌──────┴──────┐
▼ ▼
┌────────────┐ ┌─────────────┐
│ PostgreSQL │ │ Redis cache │
└────────────┘ └─────────────┘
Директива ports: - "5432:5432" для контейнеров СУБД исключается. Docker по умолчанию модифицирует таблицы PREROUTING в iptables, пробрасывая порт наружу в обход системного UFW-фаервола хоста. В изолированной сети сервисы обращаются друг к другу по внутренним именам (postgres, redis) через встроенный DNS-демон Docker (127.0.0.11), не выставляя сокеты на интерфейс eth0.
2. Хранилище данных: named volumes против bind mounts на NVMe
Стабильность СУБД под нагрузкой напрямую зависит от способа монтирования постоянных данных (persistent volumes):
- Bind Mounts (
./data/postgres:/var/lib/postgresql/data):- Проблемы: привязка к хостовым путям приводит к коллизиям UID/GID. Официальный образ
postgres:alpineзапускается под пользователем с UID70(или999в Debian), что требует ручной смены владельца директорий на хосте (chown -R). Ошибки в правах приводят к отказу запуска при обновлении образов. Кроме того, прямой bind mount чувствителен к контекстам SELinux и нестабилен при параллельных операциях ввода-вывода.
- Проблемы: привязка к хостовым путям приводит к коллизиям UID/GID. Официальный образ
- Named Volumes (
db_data:/var/lib/postgresql/data):- Преимущества: жизненным циклом каталога управляет Docker Storage Engine в директории
/var/lib/docker/volumes/. Инициализация прав, создание структуры каталогов WAL и табличных пространств происходят прозрачно. На NVMe-дисках KVM VPS именованные тома обеспечивают прямой I/O без накладных расходов на дополнительную трансляцию путей хостовой ОС.
- Преимущества: жизненным циклом каталога управляет Docker Storage Engine в директории
Тонкая настройка хостового ядра Linux (/etc/sysctl.conf) для предотвращения I/O spikes и задержек при сбросе дискового кэша:
# Предотвращение блокировок ввода-вывода при интенсивной записи WAL
sysctl -w vm.dirty_background_ratio=5
sysctl -w vm.dirty_ratio=10
# Гарантия бессбойного выделения памяти для Redis Background Save (BGSAVE)
sysctl -w vm.overcommit_memory=1
3. Резервное копирование: организация дампов базы и синхронизации томов в S3 без даунтайма
Создание снимков хранилища через остановку контейнеров (docker compose stop) в e-commerce неприемлемо: остановка приводит к прерыванию оформления заказов и сбросу корзин.
Безотказный backup организуется по трехуровневой схеме:
- Транзакционный дамп PostgreSQL: использование утилиты
pg_dumpв пользовательском сжатом формате (-Fc). Команда создает моментальный логический срез состояния базы на основе снимка транзакции (MVCC), не блокируя операции чтения и записи таблиц:bash docker compose exec -T postgres pg_dump -U ${DB_USER} -d ${DB_NAME} -Fc -Z 6 > /tmp/backup_$(date +%F_%H%M).dump - Снапшот сессий Redis: запуск неблокирующей команды
BGSAVE. Redis порождает дочерний процесс через системный вызовfork(), сбрасывая данные памяти в файлdump.rdbбез задержек в основном потоке обработки запросов. - Потоковая репликация в S3: дамп базы и медиа-файлы каталога (изображения товаров из тома
media_data) сжимаются и отправляются в защищенное S3-хранилище с шифрованием AES-256 черезaws-cliилиrclone. Локальное дисковое пространство VPS не расходуется на хранение тяжелых архивов.
4. Конфигурация продакшен-стека docker-compose.yml
Манифест содержит изоляцию сетей, аппаратные лимиты памяти/процессора (cgroups), проверки доступности (healthcheck) и профиль для автономного бэкапа:
services:
nginx:
image: nginx:1.25-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/certs:/etc/letsencrypt:ro
- static_data:/var/www/static:ro
- media_data:/var/www/media:ro
networks:
- frontend_net
depends_on:
- backend
backend:
image: shop-engine:production
restart: unless-stopped
environment:
DATABASE_URL: "postgresql://${DB_USER}:${DB_PASS}@postgres:5432/${DB_NAME}"
REDIS_URL: "redis://:${REDIS_PASS}@redis:6379/0"
deploy:
resources:
limits:
cpus: '2.0'
memory: 2048M
volumes:
- static_data:/app/static
- media_data:/app/media
networks:
- frontend_net
- backend_net
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASS}
deploy:
resources:
limits:
cpus: '2.0'
memory: 4096M
volumes:
- pg_data:/var/lib/postgresql/data
networks:
- backend_net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7.2-alpine
restart: unless-stopped
command: >
redis-server
--requirepass ${REDIS_PASS}
--save 900 1
--save 300 10
--maxmemory 1024mb
--maxmemory-policy volatile-lru
deploy:
resources:
limits:
cpus: '1.0'
memory: 1280M
volumes:
- redis_data:/data
networks:
- backend_net
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASS}", "ping"]
interval: 5s
timeout: 3s
retries: 5
backup-agent:
image: alpine:3.19
restart: "no"
profiles: ["backup"]
environment:
AWS_ACCESS_KEY_ID: ${S3_KEY}
AWS_SECRET_ACCESS_KEY: ${S3_SECRET}
S3_BUCKET: ${S3_BUCKET_NAME}
S3_ENDPOINT: "https://storage.yandexcloud.net"
networks:
- backend_net
entrypoint: ["/bin/sh", "-c"]
command:
- |
apk add --no-cache postgresql16-client aws-cli
TIMESTAMP=$$(date +%Y%m%d_%H%M%S)
pg_dump -h postgres -U ${DB_USER} -d ${DB_NAME} -Fc | \
aws s3 cp - s3://${S3_BUCKET}/db/dump_$${TIMESTAMP}.dump --endpoint-url ${S3_ENDPOINT}
networks:
frontend_net:
driver: bridge
backend_net:
driver: bridge
internal: true
volumes:
pg_data:
driver: local
redis_data:
driver: local
static_data:
driver: local
media_data:
driver: local
Автоматизация выгрузки снимка СУБД в S3 настраивается через системный cron хост-машины без запуска постоянно работающих демонов внутри контейнеров:
# Ночной бэкап базы данных в 03:00 без влияния на работу сайта
0 3 * * * cd /srv/shop && docker compose --profile backup run --rm backup-agent
Такая конфигурация исключает прямой доступ злоумышленников к СУБД при взломе веб-приложения, обеспечивает сохранность заказов при пиковых нагрузках на диск и гарантирует мгновенное восстановление сервиса из защищенных S3-снимков.
Настройка VPS для Docker и Docker Compose своими руками: пошаговое руководство
Грамотная подготовка VPS для Docker и Docker Compose своими руками на базе дистрибутива Ubuntu 24.04 LTS исключает внезапные падения сервисов из-за переполнения диска логами, утечек памяти и неконтролируемого открытия портов во внешнюю сеть. Ниже приведена production-ready конфигурация KVM-инстанса: от изоляции привилегий до устранения архитектурных конфликтов Docker с сетевым экраном.
Шаг 1: Базовый харднинг KVM-сервера
Работа под учетной записью root в среде контейнеризации недопустима: скомпрометированный контейнер с проброшенным сокетом /var/run/docker.sock дает мгновенный root-доступ к хосту.
- Создание непривилегированного пользователя с доступом к sudo:
bash adduser deployer --gecos "" usermod -aG sudo deployer
- Перенос SSH-ключей и блокировка парольной аутентификации:
bash mkdir -p /home/deployer/.ssh cp /root/.ssh/authorized_keys /home/deployer/.ssh/ chown -R deployer:deployer /home/deployer/.ssh chmod 700 /home/deployer/.ssh chmod 600 /home/deployer/.ssh/authorized_keys
Создайте изолированный конфигурационный файл /etc/ssh/sshd_config.d/99-security.conf:
ini PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes KbdInteractiveAuthentication no X11Forwarding no MaxAuthTries 3
Примените настройки через systemd:
bash sudo systemctl restart ssh
- Выделение файла подкачки (Swap) с защитой от thrashing: Для нод с объемом RAM $\le 4$ ГБ swap обязателен, чтобы сглаживать кратковременные пики потребления памяти сборщиками или базами данных без триггера ядра на OOM Killer.
bash fallocate -l 2G /swapfile chmod 600 /swapfile mkswap /swapfile swapon /swapfile echo '/swapfile none swap sw 0 0' >> /etc/fstab
Ограничьте агрессивность сброса страниц в swap, добавив параметры ядра в /etc/sysctl.d/99-sysctl.conf:
ini vm.swappiness=10 vm.vfs_cache_pressure=50
Примените изменения: sudo sysctl --system.
Шаг 2: Установка Docker Engine и Compose v2 из официального репозитория
Пакеты docker.io из стандартных репозиториев Ubuntu содержат устаревшие версии и лишены актуального плагина Compose v2. Установка выполняется строго из upstream-репозитория Docker Inc.
- Удаление конфликтующих пакетов:
bash sudo apt-get remove -y docker.io docker-doc docker-compose podman-docker containerd runc
- Подключение официального GPG-ключа и репозитория:
```bash sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null ```
- Инсталляция ядра Docker, плагинов и запуск службы:
bash sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
- Делегирование прав непривилегированному пользователю: Чтобы запускать
docker composeбез префиксаsudo:
bash sudo usermod -aG docker deployer Параметр вступит в силу после перелогина пользователя в SSH-сессию.
- Активация сервиса через systemd:
bash sudo systemctl enable --now docker
Шаг 3: Тюнинг демона /etc/docker/daemon.json
По умолчанию Docker пишет логи контейнеров в неограниченные JSON-файлы, что при активной записи приводит к исчерпанию дискового пространства и зависанию VPS. Кроме того, перезапуск демона Docker рвет TCP-соединения запущенных контейнеров.
Создайте файл конфигурации /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "20m",
"max-file": "3"
},
"live-restore": true,
"userland-proxy": false,
"default-address-pools": [
{
"base": "172.20.0.0/14",
"size": 24
}
]
}
log-driverиlog-opts: жестко фиксируют ротацию stdout/stderr. Ни один контейнер не сможет занять логами более 60 МБ дискового пространства.live-restore: разрешает контейнерам оставаться в запущенном состоянии при перезапуске, обновлении или падении процесса демонаdockerd.userland-proxy: false: отключает прокси-демонdocker-proxyв пользовательском пространстве. Проброс трафика передается на уровень ядра через таблицыiptables, снижая задержку (latency) и потребление RAM под нагрузкой.default-address-pools: исключает конфликты подсетей Docker с локальными IP-адресами VPS-провайдера или корпоративными VPN-туннелями (WireGuard/OpenVPN).
Примените тюнинг:
sudo systemctl restart docker
Шаг 4: Устранение уязвимости Docker с обходом правил UFW через ufw-docker
Главная скрытая угроза сетевой подсистемы Docker: демон напрямую модифицирует цепочки iptables (в частности PREROUTING в таблице nat), перехватывая сетевые пакеты до того, как они дойдут до цепочек брандмауэра UFW (ufw-user-input). Если на VPS включен UFW со строгим правилом deny all, но контейнер запущен с флагом -p 5432:5432, порт PostgreSQL автоматически станет открыт для всего интернета.
Отключение iptables: false в конфиге демона ломает маршрутизацию контейнеров и резолв встроенного DNS. Корректное решение — внедрение фильтрации через утилиту ufw-docker.
- Активация базового профиля UFW:
bash sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow 22/tcp comment 'SSH' sudo ufw enable
- Установка ufw-docker:
bash sudo wget -O /usr/local/bin/ufw-docker https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker sudo chmod +x /usr/local/bin/ufw-docker
- Патчинг конфигурации брандмауэра: Утилита модифицирует файл
/etc/ufw/after.rules, добавляя проверку правил UFW для трафика, адресованного Docker-сетям:
bash sudo ufw-docker install sudo systemctl restart ufw
- Управление доступом к портам контейнеров: Теперь публикация порта в
docker-compose.yml(ports: - "8080:80") делает его доступным локально, но внешний мир отсекается UFW. Разрешение доступа выполняется адресно: - Открыть порт веб-сервера для всех:
bash sudo ufw-docker allow nginx-frontend 80/tcp - Ограничить доступ к БД или панели управления доверенным IP-адресом:
bash sudo ufw-docker allow postgres-db 5432/tcp 203.0.113.15 - Проверить текущее состояние пробросов:
bash sudo ufw-docker status
Данная конфигурация обеспечивает устойчивую изоляцию хоста, предсказуемую утилизацию диска и детерминированную безопасность портов при развертывании любых стеков приложений.
Новинки Docker и Compose v2: переход на современные стандарты
Оптимизируя vps для docker и docker compose новинки контейнерного стека дают ощутимый прирост производительности: исключение накладных расходов интерпретатора Python, изоляция ядра без прав суперпользователя и детерминированные параллельные сборки. Эксплуатация устаревших версий утилит в production-контурах не только снижает скорость развертывания, но и создает критические векторы атак на хост-систему.
Отказ от legacy docker-compose: преимущества нативного Go-плагина
Классический скрипт docker-compose первой версии (написанный на Python и упакованный через PyInstaller) официально выведен из эксплуатации. Ему на смену пришел Docker Compose v2 — плагин, интегрированный непосредственно в бинарник Docker CLI (docker compose вместо вызова через дефис).
Переход на кодовую базу Go решает фундаментальные проблемы первой версии:
- Нулевой runtime-оверхед: Отсутствие виртуального окружения Python и синтаксического парсинга через тяжелые сторонние библиотеки снижает задержку между вводом команды и обращением к Docker Engine Daemon до минимума.
- Сквозная типизация и Compose Spec: Версия v2 жестко следует стандарту Compose Specification, устраняя путаницу с атрибутом
version: '3.8'. Валидация YAML-манифестов происходит на этапе компиляции структур в Go с выводом точных строк ошибок. - Параллельное управление графом зависимостей: Развертывание сервисов (
docker compose up -d) теперь использует нативный пул горутин. Если между контейнерами не задана директиваdepends_on, запуск десятков микросервисов выполняется строго конкурентно. - Поддержка профилей (
--profile): Позволяет описывать в едином файле конфигурации для разработки, нагрузочного тестирования и production, запуская только целевую группу сервисов:bash # Запуск только базы данных и сервиса миграций docker compose --profile migration up -d
Безопасность без привилегий: Rootless mode
Стандартная архитектура Docker предполагает работу демона dockerd с правами суперпользователя (root). Любой пользователь, добавленный в группу docker, де-факто получает эквивалент root-доступа к хосту через монтирование сокета /var/run/docker.sock или хостовых директорий (-v /:/host).
Режим Rootless mode полностью устраняет эту уязвимость: и сам демон dockerd, и контейнеры запускаются в рамках непривилегированного пользовательского пространства имен (User Namespaces).
- Механика изоляции: Пользователь
root(UID 0) внутри контейнера сопоставляется с обычным непривилегированным UID хоста (например, UID 10001) через диапазоны/etc/subuidи/etc/subgid. Побег из контейнера через эксплойт ядра или уязвимость вruncдает атакующему доступ лишь к изолированному непривилегированному процессу, блокируя компрометацию хостовой операционной системы. - Сетевой стек: Для проброса сетевого трафика без прав root задействуется стек
slirp4netnsилиpasta(в ядрах Linux 5.13+), обеспечивающий трансляцию сетевых пакетов на уровне пространства имен пользователя.
Активация на сервере выполняется без изменения системных привилегий:
# Установка rootless окружения для текущего пользователя
dockerd-rootless-setuptool.sh install
# Настройка системного сокета
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
systemctl --user enable --now docker
Ограничение: Rootless mode накладывает запрет на связывание с портами ниже 1024 без измененияsysctl net.ipv4.ip_unprivileged_port_start=80, а также не поддерживает монтирование оверлейных файловых систем без модуляfuse-overlayfsна старых версиях ядер.
Оптимизация сборок: BuildKit, Buildx и Docker Bake
Движок сборщика нового поколения BuildKit стал стандартом по умолчанию, вытеснив legacy-билдер. В связке с CLI-плагином Buildx он превращает компиляцию образов в высокопроизводительный распределенный конвейер.
Параллельное исполнение этапов (Multi-stage + DAG)
BuildKit строит ориентированный ациклический граф (DAG) инструкций Dockerfile. Независимые этапы multi-stage сборки выполняются параллельно, а неиспользуемые стадии полностью игнорируются:
# syntax=docker/dockerfile:1.4
FROM golang:1.22-alpine AS builder-go
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /server .
FROM node:20-alpine AS builder-frontend
WORKDIR /web
COPY web/package.json web/package-lock.json ./
RUN npm ci
COPY web/ .
RUN npm run build
# Финальный образ собирает артефакты обеих параллельных стадий
FROM alpine:3.20
COPY --from=builder-go /server /server
COPY --from=builder-frontend /web/dist /var/www/html
ENTRYPOINT ["/server"]
Удаленное кэширование с --cache-to и --cache-from
Сборки на CI/CD серверах больше не требуют хранения локальных базовых слоев. BuildKit позволяет выносить кэш непосредственно в Container Registry или внешние хранилища:
docker buildx build \
--push \
--tag myregistry.com/app:prod \
--cache-to type=registry,ref=myregistry.com/app:cache,mode=max \
--cache-from type=registry,ref=myregistry.com/app:cache \
.
Параметр mode=max принудительно сохраняет кэш всех промежуточных стадий multi-stage билда, а не только результирующего слоя.
Декларативное управление через Docker Bake
Для сложных проектов с десятками микросервисов вызовы длинных bash-команд заменяются на Docker Bake. Вся матрица сборок описывается в конфигурации docker-bake.hcl:
group "default" {
targets = ["api", "worker"]
}
target "base" {
dockerfile = "Dockerfile"
platforms = ["linux/amd64", "linux/arm64"]
}
target "api" {
inherits = ["base"]
target = "api-target"
tags = ["myregistry.com/api:latest"]
}
target "worker" {
inherits = ["base"]
target = "worker-target"
tags = ["myregistry.com/worker:latest"]
}
Запуск всего цикла сборки под несколько архитектур выполняется одной командой: docker buildx bake.
Цепочка поставок и аудит: генерация SBOM
Современные требования DevSecOps обязывают верифицировать состав образов до их выкатки на production. Buildx нативно формирует SBOM (Software Bill of Materials) в форматах SPDX или CycloneDX непосредственно в момент сборки, вшивая цифровую спецификацию пакетов в метаданные манифеста:
# Сборка образа с генерацией отчета о зависимостях (SBOM) и метаданных происхождения
docker buildx build \
--attest type=sbom,generator=docker/buildkit-syft-scanner \
--attest type=provenance,mode=max \
-t myregistry.com/app:secure \
--push .
Проверка содержимого пакетов и уязвимых зависимостей внутри готового артефакта выполняется без запуска самого контейнера:
docker buildx imagetools inspect myregistry.com/app:secure --format "{{ json .SBOM }}"
Такой подход защищает инфраструктуру от внедрения скомпрометированных upstream-пакетов и фиксирует точное состояние зависимостей для регуляторных проверок.
Чек-лист распространенных ошибок при эксплуатации Docker на VPS
По умолчанию Docker разворачивается с «жадными» системными настройками: демон не ограничивает аппетиты контейнеров к RAM, не очищает слои сборки и пишет бесконечный поток вывода в локальную файловую систему. На виртуальном сервере с фиксированным диском NVMe и жестким пулом vCPU это приводит к внезапным отказам — от аварийной перезагрузки демона до перехода корневого раздела в режим read-only.
Ниже разобран технический чек-лист из четырех фатальных ошибок конфигурирования и методы их превентивного устранения.
1. Утечка логов и переполнение диска: дефолтный json-file драйвер
Стандартный драйвер журналирования в Docker — json-file. Он перехватывает потоки stdout и stderr контейнера и записывает их на хост по пути: /var/lib/docker/containers/<container_id>/<container_id>-json.log
Без явного ограничения этот файл непрерывно растет до тех пор, пока на VPS не останется 0 байт свободного места. Как следствие, PostgreSQL или Redis аварийно сбрасывают состояние (panic), а SSH-сервер перестает пускать администратора из-за невозможности создать файл сессии в /tmp.
Диагностика:
# Проверка дискового пространства
df -h /
# Поиск тяжелых логов контейнеров
du -ah /var/lib/docker/containers/ | grep -E '\.log$' | sort -rh | head -n 5
Решение на уровне хоста:
Задайте глобальную ротацию для всех контейнеров в файле /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "3"
}
}
После правки перезапустите демон: systemctl reload docker. Это предотвратит любую утечку логов, ограничив журнал каждого сервиса 150 МБ.
2. Исчерпание inodes из-за dangling-образов и слоев BuildKit
Команда df -h может показывать 40–50% свободного дискового пространства, но сервер при этом выдает критическую ошибку No space left on device. Причина — исчерпание inodes (структур файловой системы, хранящих метаданные файлов).
При частых деплоях, сборках с DOCKER_BUILDKIT=1 и обновлении тегов :latest на диске накапливаются тысячи dangling-слоев (образы со статусом <none>:<none>), промежуточных кэшей сборок и анонимных томов. Каждый мелкий файл исходного кода или npm-зависимости внутри слоя сжигает отдельный дескриптор inode.
Диагностика:
# Проверка процента занятых inodes
df -i /
# Оценка объема мусора в подсистеме Docker
docker system df
Решение:
Очистите зависшие сущности и кэш сборщика:
# Удаление остановленных контейнеров, сетей и dangling-образов
docker system prune -f
# Глубокая очистка неиспользуемых образов старше 7 дней и кэша сборки
docker system prune -af --volumes --filter "until=168h"
Для production-серверов автоматизируйте очистку через системный cron (/etc/cron.weekly/docker-prune):
#!/bin/sh
docker system prune -f --filter "until=336h" > /dev/null 2>&1
3. OOM Crash сервера из-за отсутствия лимитов memory/cpus
В базовом синтаксисе docker-compose.yml многие разработчики опускают блоки ограничений вычислительных ресурсов. Если Node.js-сервис начинает накапливать утечку памяти или фоновый worker парсинга запускает тяжелый цикл, контейнер забирает 100% RAM хоста.
Когда свободной памяти не остается, ядро Linux активирует механизм Out-Of-Memory Killer. В системных логах фиксируется критический OOM crash (код завершения контейнера 137). При этом ядро может принудительно завершить не только виновный контейнер, но и критически важные процессы хоста — systemd-journald, dockerd или процесс демона базы данных.
Диагностика:
# Мониторинг потребления ресурсов в реальном времени
docker stats --no-stream
# Поиск следов убийства процессов ядром
dmesg -T | grep -E -i 'oom|kill'
Решение:
Всегда фиксируйте memory limits и резервы в спецификации сервиса docker-compose.yml:
services:
backend-api:
image: my-app:latest
deploy:
resources:
limits:
cpus: '1.50'
memory: 1024M
reservations:
cpus: '0.25'
memory: 256M
restart: unless-stopped
Параметр limits.memory не позволит процессу превысить выделенный порог в 1 ГБ, изолируя хост-систему от деградации.
4. Игнорирование healthcheck и отсутствие автоматического перезапуска
Директива restart: unless-stopped обязательна для production: без нее любая перезагрузка хоста провайдером после регламентных работ оставит проект в отключенном состоянии.
Однако одного флага restart недостаточно. Процесс внутри контейнера может зависнуть в состоянии deadlock, упасть в бесконечный цикл или перестать отвечать на внешние запросы, сохраняя статус Up в выводе docker ps (поскольку PID 1 жив). Docker не перезапустит такой контейнер самостоятельно, пока не настроен модуль валидации состояния (healthcheck).
Решение:
Добавьте активный healthcheck и политику перезапуска в docker-compose.yml:
services:
web-service:
image: nginx:alpine
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1/healthz || exit 1"]
interval: 15s
timeout: 5s
retries: 3
start_period: 20s
- start_period: Время на холодный старт сервиса (инициализация фреймворка, миграции БД).
- retries: Количество неудачных попыток до присвоения статуса
unhealthy. Для автоматического перезапуска контейнеров со статусомunhealthyна уровне хоста подключается легковесный sidecar-агентautoheal(модульwillfarrell/autoheal).
Сводная матрица проблем и регламентов обслуживания
| Категория сбоя | Корневая причина | Диагностическая команда | Решение / Параметр конфигурации |
|---|---|---|---|
| Переполнение диска (GB) | Бесконечный рост *-json.log без ротации |
du -sh /var/lib/docker/containers/* |
"max-size": "50m", "max-file": "3" в /etc/docker/daemon.json |
Отказ файловой системы (No space) |
Исчерпание inodes из-за dangling-образов и слоев | df -i / и docker system df |
Регулярный docker system prune -af --filter "until=168h" через cron |
| OOM crash хоста | Не заданы жесткие memory limits | dmesg -T \| grep -i oom (Exit Code 137) |
Блок deploy.resources.limits.memory в docker-compose.yml |
| Зависание приложения ("Silent Death") | Отсутствие мониторинга живости сервиса | docker ps --format "{{.Names}}: {{.Status}}" |
Блок healthcheck в Compose + restart: unless-stopped |
Часто задаваемые вопросы (FAQ)
Сколько оперативной памяти (RAM) требуется для комфортной работы Docker на VPS?
Для запуска самого Docker Engine и пары вспомогательных утилит достаточно 1-2 ГБ RAM. Для боевого стека (веб-сервер, Node.js/Python бэкенд, PostgreSQL и Redis) необходимо минимум 4 ГБ оперативной памяти с обязательным подключением swap на NVMe.
Почему для Docker рекомендуется именно KVM, а не OpenVZ или LXC?
KVM обеспечивает полную аппаратную виртуализацию с собственным независимым ядром Linux. Это гарантирует поддержку cgroups v2, управление лимитами ресурсов, работу драйвера overlay2 и предотвращает конфликты сетевого стека, характерные для разделяемого ядра OpenVZ.
Правда ли, что Docker обходит правила стандартного файрвола UFW на сервере?
Да, Docker напрямую модифицирует цепочки iptables (FORWARD/PREROUTING) и публикует порты в сеть раньше, чем сработают фильтры UFW. Для безопасной изоляции портов требуется связать демон с localhost (127.0.0.1:port:port) либо установить утилиту ufw-docker.
В чем разница между командами docker-compose и docker compose?
Команда через дефис (docker-compose v1) представляла собой отдельный скрипт на Python, поддержка которого полностью прекращена. Современный стандарт (Compose v2) написан на Go, интегрирован прямо в CLI Docker (команда без дефиса) и работает в несколько раз быстрее.
Как предотвратить внезапное переполнение диска логами Docker контейнеров?
В файле конфигурации /etc/docker/daemon.json необходимо глобально задать ротацию логов через параметры 'log-driver': 'json-file' и 'log-opts': {'max-size': '10m', 'max-file': '3'}. Это ограничит объем логов каждого сервиса максимум 30 мегабайтами.