Особенности работы Docker на VPS сервере
Узнайте, почему Docker обходит правила UFW, как избежать ошибки OOM Killer при нехватке RAM и почему контейнеры не стартуют после перезагрузки. Практическое руководство по настройке.
Что меняется при запуске Docker на VPS
Docker на VPS использует тот же движок и те же образы, что и Docker на вашем ноутбуке, поэтому все знакомые вам команды продолжают работать. Меняется лишь окружение. У ноутбука есть запас оперативной памяти, брандмауэр, который никто не сканирует, и достаточно большой диск, о котором не нужно беспокоиться. Арендованный сервер имеет жесткий лимит памяти, публичный IP-адрес, который начинают сканировать через несколько минут после запуска, и корневую файловую систему, которую Docker заполнит без предупреждения.
Четыре отличия вызывают большинство проблем на небольшом сервере:
- Память ограничена, и при её нехватке ядро системы завершает процесс (OOM Killer).
- Опубликованный порт становится доступен в обход UFW (uncomplicated firewall), так как Docker записывает собственные правила брандмауэра.
- Контейнеры не запускаются автоматически после перезагрузки, если вы не настроили это заранее.
- Образы, контейнеры, тома и кэш сборки растут до тех пор, пока не закончится место на диске.
В каждом разделе ниже указана проблема, сообщение об ошибке, которое вы увидите, и руководство по её устранению. Если вы еще не написали compose-файл, сначала прочитайте Основы Docker Compose на VPS и вернитесь сюда. Эта страница предполагает, что вы уже умеете запускать стек сервисов.
Сколько оперативной памяти потребляет Docker-контейнер?
Меньше, чем ожидает большинство пользователей. Контейнер — это процесс в cgroup (control group), а не виртуальная машина, поэтому здесь нет гостевого ядра и фиксированного выделения ресурсов. Расход памяти равен объему данных, к которым обращается процесс внутри. Именно поэтому полный стек может уместиться в 2 GB, тогда как аналогичный стек на виртуальных машинах — нет.
Приведенные ниже цифры — это типичные показатели потребления в режиме простоя для стандартных образов на Ubuntu 24.04 с конфигурацией по умолчанию, полученные с помощью docker stats через несколько минут после запуска. Это отправная точка для планирования, а не эталонный тест вашей рабочей нагрузки. Запустите docker stats --no-stream на своем сервере, прежде чем доверять любым цифрам, включая эти.
Данные, на которых построен график
[
{
"label": "nginx",
"idle_mb": 8,
"budget_mb": 64
},
{
"label": "Redis 7",
"idle_mb": 12,
"budget_mb": 128
},
{
"label": "Traefik v3",
"idle_mb": 40,
"budget_mb": 128
},
{
"label": "PostgreSQL 16",
"idle_mb": 45,
"budget_mb": 512
},
{
"label": "Uptime Kuma",
"idle_mb": 95,
"budget_mb": 256
},
{
"label": "MariaDB 11",
"idle_mb": 190,
"budget_mb": 512
},
{
"label": "Nextcloud (Apache)",
"idle_mb": 210,
"budget_mb": 768
}
]Два столбца выполняют разные задачи. idle_mb — это объем, который контейнер использует, пока ничего не делает. budget_mb — это объем, который нужно зарезервировать при планировании, так как реальная работа не ограничивается простоем. PostgreSQL в простое потребляет около 45 MB, но требует 512 MB, когда активны соединения, операции сортировки и кэш. Планируйте ресурсы, опираясь на столбец бюджета. Используйте столбец простоя для отладки.
Обратите внимание на значения в этих 7 строках. nginx в простое потребляет 8 MB, а Nextcloud — 210 MB. Прокси-сервер перед вашими приложениями практически не потребляет ресурсов. Именно под базу данных и PHP-приложение следует подбирать размер сервера.
Предупреждение относительно docker stats: показатель памяти включает page cache, который был задействован при чтении файлов самим контейнером, поэтому он растет некоторое время после запуска, а затем стабилизируется. Наблюдайте за ним в течение часа, прежде чем делать вывод об утечке памяти.
Выбор размера VPS: что поместится в 2 ГБ, 4 ГБ и 8 ГБ
Сначала вычтите долю хоста. Ядро, systemd, journald, sshd и Docker daemon используют ту же оперативную память, что и ваши контейнеры, а dockerd вместе с containerd занимают около 100 МБ. Вам также нужна свободная память для page cache и для пиковых нагрузок, возникающих при сборке образов или создании дампов баз данных.
Данные, на которых построен график
[
{
"plan": "2 GB VPS",
"total_mb": 2048,
"host_reserve_mb": 768,
"container_mb": 1280
},
{
"plan": "4 GB VPS",
"total_mb": 4096,
"host_reserve_mb": 1024,
"container_mb": 3072
},
{
"plan": "8 GB VPS",
"total_mb": 8192,
"host_reserve_mb": 1536,
"container_mb": 6656
}
]host_reserve_mb покрывает операционную систему, Docker daemon и запас ресурсов, который сохраняет отзывчивость сервера под нагрузкой. Остаток — это container_mb, и это единственный объем памяти, который вы можете использовать. Резерв увеличивается вместе с тарифным планом: от 768 МБ на самом маленьком сервере до 1536 МБ на самом большом. Это связано с тем, что более мощный сервер запускает больше контейнеров, записывает больше логов и требует больше места под page cache.
План на 2 ГБ оставляет 1280 МБ для контейнеров. Выделите 512 МБ из них на PostgreSQL и 128 МБ на Traefik — половина объема уже занята. Остатка хватит на два небольших приложения примерно по 256 МБ каждое. Это полноценный и полезный сервер. Но места для Nextcloud и поискового кластера поверх этого уже не останется.
План на 4 ГБ оставляет 3072 МБ. Этого достаточно, чтобы одновременно разместить базу данных, reverse proxy, три приложения и контейнер мониторинга. Это минимальный размер, который стоит использовать для важных задач, так как свободная память позволяет сгладить последствия неудачного развертывания.
План на 8 ГБ оставляет 6656 МБ из общего объема в 8192 МБ. В этом случае ограничением обычно становится не память, а процессор или пропускная способность диска. SSD Nodes не продает конфигурации меньше: их минимальный план имеет 8 ГБ ОЗУ (страница с ценами проверена 2026-10-11), поэтому для этого хостинга актуальна именно эта строка, а варианты на 2 ГБ и 4 ГБ описывают более слабые серверы других провайдеров. Существует класс контейнеров, которые определяют потребление памяти исходя из конфигурации, а не из текущей нагрузки: локальный сервер моделей резервирует KV cache пропорционально размеру контекстного окна, поэтому увеличение num_ctx в Ollama может добавить гигабайты к бюджету еще до поступления первого запроса. Если расчеты показывают, что ваш стек не помещается, купите более мощный план вместо попыток оптимизации: сколько на самом деле стоит VPS объясняет, во сколько обходятся дополнительные гигабайты в месяц.
Два правила помогут сохранить точность расчетов. Установите лимит памяти для каждого сервиса, чтобы один вышедший из-под контроля процесс не привел к падению всего сервера. Оставляйте часть бюджета нераспределенной, так как docker compose build и pg_dump требуют память именно в самый неподходящий момент. В статье Лимиты памяти в Docker Compose описан синтаксис и возможные ошибки.
Почему мой контейнер завершается с кодом 137?
Это означает, что ядро принудительно завершило процесс. Код 137 — это сумма 128 и 9, где сигнал 9 соответствует SIGKILL. Контейнер запросил больше памяти, чем ему было разрешено, и механизм out of memory (OOM) killer завершил его работу.
CONTAINER ID IMAGE STATUS
9f2c1a4d7b3e postgres:16 Exited (137) 4 minutes agoПодтвердите причину, вместо того чтобы строить догадки:
docker inspect my-db | grep -i oomkilled
sudo journalctl -k | grep -i "out of memory""OOMKilled": true означает, что контейнер достиг лимита своей cgroup, а в журнале ядра указан процесс, который был выбран для завершения:
Memory cgroup out of memory: Killed process 2417 (postgres) total-vm:1284416kBЭто благоприятный сценарий, так как ущерб ограничен одним контейнером. Плохой сценарий — это контейнер без установленных лимитов. Без ограничений его потолком становится вся оперативная память машины, поэтому утечка в одном сервисе приводит к нехватке ресурсов на хосте, и ядро выбирает жертву по объему потребления во всей системе. В строке журнала отсутствует префикс Memory cgroup, и она выглядит как Out of memory: Killed process 2417 (postgres). Выбранным процессом часто оказывается база данных, в то время как контейнер с утечкой продолжает работать. Именно поэтому наличие лимита для каждого сервиса важнее, чем точное значение любого отдельного ограничения.
Swap меняет время наступления события, а не саму арифметику. Большинство образов VPS поставляются без swap. Проверьте это с помощью swapon --show, которая ничего не выведет, если swap отсутствует. Файл подкачки дает ядру место для выгрузки неактивных страниц, что дает вам несколько минут, чтобы заметить проблему.
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -hfree -h теперь должен показывать ненулевое значение в строке Swap. Swap не добавляет RAM. Система, находящаяся под постоянной нагрузкой на память, становится настолько медленной, что вы не сможете подключиться по SSH для исправления ситуации, поэтому рассматривайте swap как буфер для срабатывания оповещений и исправляйте настройки потребления ресурсов.
Почему UFW не блокирует опубликованный порт Docker?
Потому что трафик не доходит до цепочки, которую контролирует UFW. Когда вы публикуете порт с помощью -p 5432:5432 или записи в ports:, демон записывает правило DNAT (destination network address translation) в таблицу nat и правило accept в свою собственную цепочку DOCKER. Пакет, предназначенный для контейнера, перенаправляется в этот контейнер, а не доставляется на хост, поэтому он обрабатывается в цепочке FORWARD и никогда не проходит через правила INPUT, созданные UFW.
Вы можете увидеть это на сервере:
sudo ufw status | grep 5432
sudo iptables -t nat -L DOCKER -n | grep 5432UFW может отображать 5432 DENY IN Anywhere, в то время как таблица nat содержит правило DNAT tcp ... to:172.18.0.2:5432 для того же порта. С другой машины nc -vz your.server.ip 5432 всё равно подключается. База данных находится в публичном доступе, хотя брандмауэр утверждает обратное.
Решение — публиковать меньше портов. Контейнеры в одном проекте compose используют общую сеть и взаимодействуют друг с другом по имени сервиса, поэтому базе данных, которая обслуживает только соседнее приложение, вообще не нужна запись ports:. Если вам нужен локальный доступ, привяжите публикацию к loopback:
services:
db:
image: postgres:16
ports:
- "127.0.0.1:5432:5432"После docker compose up -d попытка nc -vz your.server.ip 5432 извне завершится неудачей, а psql -h 127.0.0.1 -p 5432 на самом сервере продолжит работать. В корректно настроенном небольшом стеке только обратный прокси публикует порты, обычно 80 и 443. В Почему опубликованные порты Docker обходят UFW описана цепочка DOCKER-USER для случаев, когда необходимо опубликовать порт и при этом отфильтровать его, а Основы брандмауэра UFW охватывает базовые правила хоста.
Почему мои контейнеры исчезают после перезагрузки?
Потому что их никто не настроил на автоматический запуск. Контейнер создается с политикой перезапуска no, если вы не указали иное, поэтому после перезагрузки он остается остановленным, а демон не предпринимает никаких действий. Перезагрузки на VPS — явление частое: обновления ядра через unattended upgrades, техническое обслуживание со стороны провайдера и упомянутое выше срабатывание OOM-killer приводят к ним.
Должны выполняться два условия. Демон должен запускаться при загрузке системы:
systemctl is-enabled dockerЭта команда выводит enabled в стандартной установке Ubuntu. Затем для каждого сервиса нужно задать политику:
services:
app:
image: ghcr.io/example/app:1.4
restart: unless-stoppedПараметр unless-stopped возвращает контейнер в работу после перезагрузки и учитывает контейнеры, которые вы остановили намеренно. Параметр always также перезапускает те контейнеры, которые вы остановили вручную, каждый раз при перезапуске демона, что может стать неожиданностью во время отладки. Простого редактирования файла недостаточно, так как политика перезапуска задается в момент создания контейнера. Выполните docker compose up -d, чтобы пересоздать его, а затем проверьте текущее значение:
docker inspect my-app | grep -A3 RestartPolicyПосле этого выполните принудительную перезагрузку сервера и запустите docker compose ps в директории проекта. Стек, который успешно запускается после плановой перезагрузки, выдержит и внеплановую. Если вашему стеку требуется гарантированный порядок запуска или выполнение разовой задачи при загрузке, лучше использовать юнит systemd: в запуске Docker Compose при загрузке приведен пример файла юнита. Чтобы убедиться, что запущенный контейнер действительно работает корректно, добавьте Compose healthchecks.
Почему на моем VPS заканчивается место на диске?
Docker хранит все данные, пока вы не дадите команду их удалить. Каждый скачанный тег образа, каждый остановленный контейнер, каждый анонимный том, оставшийся после пересоздания, и каждый слой кэша сборки остаются на диске. Увеличение объема диска лишь отсрочит проблему. Даже 160 GB SSD на самом младшем тарифе SSD Nodes (проверено на странице цен 2026-10-11) заполняется точно так же на сервере, где еженедельно скачиваются и собираются образы, а меньший размер корневой файловой системы просто ускоряет наступление сбоя.
Переполнение диска не выглядит как падение системы. Вы получите no space left on device от контейнера, от apt, от journald и от docker pull в течение одного часа. PostgreSQL перестает принимать записи. Сервер остается в сети, что делает проблему менее заметной, чем циклическая перезагрузка.
Проверьте ситуацию перед удалением:
docker system df
df -h /docker system df разделяет общий объем на образы, контейнеры, локальные тома и кэш сборки, отображая столбец RECLAIMABLE (доступно для очистки) для каждой категории. На сервере, где выполняется сборка собственных образов, кэш сборки обычно занимает больше всего места.
docker image prune -a
docker builder prune
docker system dfdocker image prune -a удаляет все образы, которые не используются ни одним контейнером. docker builder prune очищает кэш сборки. Обе команды безопасны во время работы сервисов, так как используемые данные пропускаются. Что небезопасно, так это docker system prune --volumes. Она удаляет остановленные контейнеры, а затем все анонимные тома, которые не используются ни одним из оставшихся контейнеров. Стек, который вы остановили на выходные, имеет именно такой вид, и если его база данных хранила данные в анонимном томе (который Docker создает, когда образ объявляет том, а ваш compose-файл ничего туда не монтирует), эти данные будут удалены. Именованные тома переживут эту команду, но docker volume prune --all удаляет и неиспользуемые именованные тома. Прочитайте bind mounts versus named volumes перед использованием любого из этих флагов и сначала сделайте резервную копию.
Логи контейнеров — это менее очевидный источник роста данных. Драйвер по умолчанию json-file не имеет ограничения по размеру, поэтому один «разговорчивый» контейнер может записать гигабайты в /var/lib/docker/containers. Установите лимит для каждого контейнера в /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}Примените настройки с помощью sudo systemctl restart docker, что приведет к перезапуску контейнеров, поэтому выберите подходящий момент. Лимит применяется к контейнерам, созданным после внесения изменений, поэтому пересоздайте работающие контейнеры с помощью docker compose up -d --force-recreate и проверьте результат:
docker inspect my-app | grep -A5 LogConfig
sudo du -sh /var/lib/docker/containersВывод команды inspect должен показывать, что max-size установлено. Если поле пустое, значит, контейнер был создан до внесения изменений и продолжает запись без ограничений.
Привычки для поддержания работоспособности небольшого Docker-сервера
Для этого не нужны панели управления или инструменты, которые требуют долгого изучения.
- Запускайте
docker system dfиdf -h /первого числа каждого месяца. Две команды, тридцать секунд — и вы увидите тенденцию задолго до того, как она приведет к сбою. - Установите лимит памяти для каждого сервиса, включая те, которые кажутся вам совсем небольшими. Лимит превращает общесистемный сбой в перезапуск одного контейнера.
- Мониторьте сервер извне, чтобы узнавать о нехватке памяти или дискового пространства до того, как вмешаются механизмы ядра. Uptime Kuma работает в контейнере и потребляет в простое около 95 MB.
- Делайте резервные копии томов, а не контейнеров. Контейнер — это расходный материал, а том — нет. Резервное копирование с помощью restic на VPS описывает настройку расписания и проверку восстановления.
- Фиксируйте теги образов в файле compose и обновляйте их в выбранный вами день. При использовании
latestверсия, которую вы получите после следующегоdocker compose pull, будет той, что была выпущена в это утро.
Небольшой VPS с Docker будет работать годами, если четыре показателя остаются в норме: бюджет памяти, список открытых портов, политика перезапуска для каждого сервиса и свободное место на диске. Всё остальное — это тот же Docker, который вы уже используете.
FAQ
Сколько оперативной памяти нужно для работы Docker на VPS?
Сам Docker потребляет немного ресурсов. Демон и containerd вместе занимают около 100 МБ, остальное зависит от ваших контейнеров. Сначала выделите ресурсы для хоста: 768 МБ на сервере с 2048 МБ для операционной системы, демона и запаса, что оставляет 1280 МБ для работы приложений. База данных на 512 МБ, reverse proxy на 128 МБ и два небольших приложения вполне поместятся в этот объем. Измеряйте потребление вашего стека с помощью docker stats --no-stream, а не полагайтесь на опубликованные цифры.
Можно ли запустить Docker на VPS с 1 ГБ оперативной памяти?
Да, для одного или двух легких контейнеров, но перед началом работы обязательно добавьте swap-файл. Примерно половина объема 1 ГБ сервера будет занята операционной системой и демоном Docker, что оставляет место для небольшого приложения и reverse proxy, но не для базы данных под реальной нагрузкой. Сборка образов на сервере такого размера приведет к сбоям или завершению других процессов, поэтому выполняйте сборку на другой машине и скачивайте готовый образ.
Защищает ли UFW контейнеры Docker?
Нет, если порты опубликованы. Docker создает собственные правила DNAT и forward, поэтому пакет, направленный на опубликованный порт контейнера, перенаправляется в контейнер, минуя правила INPUT, которыми управляет UFW. ufw deny 5432 может быть активен, в то время как порт будет отвечать на запросы из Интернета. Публикуйте порты на loopback с помощью 127.0.0.1:5432:5432, оставляйте внутренние сервисы неопубликованными или используйте фильтрацию в цепочке DOCKER-USER.
Будут ли мои контейнеры перезапускаться после перезагрузки VPS?
Только если для них была задана политика перезапуска. Установите restart: unless-stopped для каждого сервиса, выполните docker compose up -d, чтобы контейнеры были пересозданы с этой политикой, и убедитесь, что systemctl is-enabled docker выводит enabled. Затем выполните принудительную перезагрузку и проверьте docker compose ps. Политика перезапуска, которую вы не протестировали, не является надежной.
Как часто нужно очищать образы Docker?
Для большинства небольших серверов достаточно делать это раз в месяц или когда docker system df показывает объем пространства, который вы хотите освободить. docker image prune -a и docker builder prune безопасны во время работы сервисов, так как используемые образы и кэш пропускаются. Избегайте docker system prune --volumes, если вы точно не знаете, какие тома не используются, так как эта команда удаляет анонимные тома любого остановленного стека, а docker volume prune --all удаляет также и неиспользуемые именованные тома.