SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-25

Особенности работы 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 на своем сервере, прежде чем доверять любым цифрам, включая эти.

ChartTypical idle container memory, stock images, megabytes
The data behind this chart
[
  {
    "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 и для пиковых нагрузок при сборке образов или создании дампов баз данных.

ChartRAM budget by plan size, megabytes
The data behind this chart
[
  {
    "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 МБ, и ограничением обычно становится уже не память, а процессор или пропускная способность диска. Некоторые контейнеры определяют потребление памяти исходя из конфигурации, а не нагрузки: локальный сервер моделей резервирует 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 -h

free -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 5432

UFW может отображать 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 сохраняет все данные, пока вы не дадите команду на их удаление. Каждый скачанный тег образа, каждый остановленный контейнер, каждый анонимный том, оставшийся после пересоздания, и каждый слой кэша сборки остаются на диске. На корневой файловой системе объемом 40 ГБ или 80 ГБ, что является стандартом для таких тарифов, это приводит к сбою через месяцы, а не годы.

Переполнение диска не выглядит как аварийное завершение работы. Вы получите 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 df

docker image prune -a удаляет все образы, которые не используются ни одним контейнером. docker builder prune очищает кэш сборки. Обе команды безопасны во время работы сервисов, так как используемые данные пропускаются. Небезопасной является команда docker system prune --volumes, которая удаляет все тома, на которые в данный момент не ссылается ни один контейнер. Стек, который вы остановили на выходные, имеет именно такой статус, и том его базы данных будет удален. Прочитайте 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, если вы точно не знаете, какие тома не используются, так как эта команда удаляет данные любого стека, который в данный момент остановлен.