Docker — отличный инструмент, но «отличный» не значит «единственный» и тем более не значит, что он подходит для любой задачи. На одиночном сервере с одним-двумя приложениями Docker может принести больше проблем, чем пользы. Демон, образы, слои, сети и тома упрощают упаковку и запуск приложений, но на одиночке или небольшом они избыточны. Под катом о том, в каких случаях Docker не нужОн, чем его заменить и как.
За что Docker любят разработчики
Docker появился в марте 2013 года, когда французский разработчик Соломон Хайкс показал его на конференции PyCon в Санта-Кларе. Не удивляйтесь, но до этого контейнеры также существовали, например, LXC развивался с 2008 года, а отдельные механизмы изоляции появлялись в ядре Linux ещё раньше. Первый namespace добавили в 2002 году, а cgroups вошли в основное ядро в 2008 году.
Docker сделал контейнеры доступными, ведь до этого собирать файловую систему, настраивать изоляцию процессов, создавать сеть и отдельно продумывать доставку приложения на другой сервер нужно было практически вручную.
В Docker всего лишь нужно описать окружение в Dockerfile, собрать образ и получить результат. В этом и заключается основная ценность платформы — она сильно упрощает сборку, доставку и воспроизведение окружения. По этой причине статья создана не с целью критики, а для того, чтобы показать новичкам, что не Docker единым…
Поговорим о траблах
Во-первых, Docker тратит ресурсы. Да, контейнеры легче виртуальных машин — они делят ядро хоста, не требуют эмуляции железа и стартуют за секунды, но это не означает «бесплатно».
Docker Engine держит в памяти dockerd, containerd и процессы runtime. Каждый контейнер также требует служебных структур ядра и процессов управления, однако фиксированного оверхеда на контейнер не существует. Основную память потребляют PostgreSQL, Redis, nginx, бэкенд и другие запущенные приложения. Разницу лучше измерять прямо на сервере:
На сервере с 32 ГБ RAM расходы обычно незначительны, но на VPS с 1–2 ГБ даже несколько десятков мегабайт сокращают запас памяти и повышают риск использования свопа или срабатывания OOM killer.
Но RAM ещё полбеды. Во многих Linux-установках слои образов и записываемые слои контейнеров хранятся через overlay2 (на свежих установках Docker Engine 29 может использоваться containerd image store). Когда контейнер впервые изменяет файл из нижнего слоя, OverlayFS копирует его в верхний записываемый слой. Дальнейшие операции выполняются уже с этой копией.
Кроме того, со временем в хранилище остаются старые версии образов, промежуточные слои сборки, остановленные контейнеры и неиспользуемые тома. Проверяйте состояние хранилища через команды:
# Посмотреть драйвер
docker info | grep -E 'Storage Driver|containerd'
# Проверить занятое место
docker system df
docker system df -v
# Найти остановленные контейнеры
docker ps -a
# Посмотреть кэш Buildx
docker buildx du
Во-вторых, демон Docker работает с правами root. Клиент передаёт ему команды через сокет /var/run/docker.sock, и, чтобы не использовать sudo при каждом запуске, учётную запись часто добавляют в группу docker. Такая учётка фактически получает привилегии уровня root. Частично эту проблему решает rootless-режим, однако он требует отдельной настройки и имеет ограничения.
В-третьих, Docker переписывает iptables, добавляя свои цепочки DOCKER, DOCKER-FORWARD и DOCKER-USER. Если вы привыкли управлять фаерволом через ufw или nftables — готовьтесь к тому, что придётся лезть в DOCKER-USER цепочку. Права Docker имеют приоритет, и ваши могут работать не так, как нужно.
В-четвёртых, Docker-контейнеры по замыслу эфемерны. Если вы храните данные внутри контейнера, они пропадут при пересоздании, нужно использовать volumes, но это ещё одна абстракция над обычными директориями.
В общем, главная проблема Docker на одиночном сервере — это то, что он добавляет ещё одну систему управления, которую нужно понимать не хуже самого Linux. В итоге он вреден в пяти ситуациях:
У вас один бэкенд и одна база на VPS. Node.js-приложение, PostgreSQL и nginx можно запустить обычными системными сервисами.
Вы поднимаете игровой сервер. Minecraft, Rust, CS и другие игровые серверы активно работают с сетью и файловой системой. Docker bridge добавляет NAT и усложняет настройку портов.
У вас VPS с 1–2 ГБ RAM. При небольшом запасе даже несколько десятков мегабайт могут повысить вероятность свопинга или срабатывание OOM killer.
Вы запускаете сервис, чувствительный к задержке (VoIP, видеосвязь, IoT-брокеры). Здесь Docker стоит использовать только после замеров.
Вы администрируете сервер в свободное время. Если у вас личный блог или домашнее хранилище, то с Docker вам придётся постоянно следить за образами, логами, политиками перезапуска и очисткой диска.
Что тогда использовать вместо Docker? Сейчас расскажу.
Чем заменить Docker на одиночном сервере
Если контейнеры не нужны — systemd, venv, nginx
Если на сервере работает одно приложение, база данных и nginx, контейнеризация вообще не нужна. В этом случае системные пакеты ставятся из репозитория, приложение запускается пользователем, а жизненным циклом процесса управляет systemd.
Важно, для Python-проекта сначала нужно установить модуль venv, создать системного пользователя и подготовить каталог приложения. Все команды ниже выполняются от root:
После копирования кода и requirements.txt в /opt/myapp можно создать виртуальное окружение и установить зависимости. venv изолирует пакеты проекта от системного Python, а для запуска приложения активировать окружение необязательно. Достаточно использовать полный путь до интерпретатора:
Не забудьте вместо ExecStart указать команду запуска приложения, например Gunicorn, Uvicorn или собственный исполняемый файл.
После сохранения unit-файла systemd должен перечитать конфигурацию. Затем сервис можно запустить и добавить в автозагрузку:
systemctl daemon-reload
systemctl enable --now myapp
systemctl status myapp
Если приложение пишет сообщения в стандартный вывод и поток ошибок, они попадут в journald. PostgreSQL, Redis и nginx на Debian или Ubuntu можно установить из репозитория дистрибутива:
apt install nginx postgresql redis-server
Если нужны OCI-образы — Podman
Иногда контейнеры всё же нужны, например, если приложение уже поставляется в OCI-образе, требуется несколько версий одной среды или вы используете контейнеры локально и хотите сохранить тот же способ развёртывания на сервере. В таком случае подойдёт Podman — он работает с теми же образами и поддерживает знакомые команды, но не требует постоянно запущенного центрального демона.
Главное преимущество Podman для одиночника — rootless mode. Вы можете запускать контейнеры от обычного пользователя, без sudo и добавления в группу docker. Контейнер думает, что внутри него root, но снаружи он работает от вашего UID.
Для того, чтобы запустить nginx используйте:
podman pull docker.io/library/nginx:alpine
podman run -d
--name web
-p 8080:80
docker.io/library/nginx:alpine
Проверить, действительно ли Podman работает без root, можно командой:
podman info --format '{{.Host.Security.Rootless}}'
Образы в Podman собираются через podman build. Команда работает с обычным Dockerfile или Containerfile и по синтаксису почти не отличается от docker build:
Но с Compose есть нюанс. Команда podman compose сама не реализует спецификацию Compose, а вызывает внешний провайдер, например podman-compose или docker compose. Соответствующий инструмент нужно установить отдельно:
podman compose up -d
Однако Podman — чуть лучший Docker, но это не принципиально другой подход.
Если нужна отдельная пользовательская среда — systemd-nspawn.
Когда приложению нужна не только изоляция процесса, но и полноценная файловая система со своим systemd, пакетным менеджером и набором служб, можно использовать systemd-nspawn.
Утилита входит в состав systemd и работает с каталогом, где развёрнута корневая файловая система. Контейнер запускается как обычный systemd-сервис, а его состояние и логи доступны через стандартные команды.
На Debian или Ubuntu окружение можно подготовить через debootstrap:
К слову, этот вариант больше подходит для тестовых сред, старых приложений с нестандартными библиотеками и сервисов, которым нужен собственный набор системных пакетов.
Если нужны системные контейнеры и снапшоты — Incus
Если Docker и Podman в первую очередь запускают отдельные приложения, то Incus работает с системными контейнерами. Внутри такого контейнера находится почти полноценная Linux-система со своим пакетным менеджером, systemd, пользователями и набором служб. По модели работы это ближе к лёгкой ВМ, хотя контейнеры по-прежнему используют ядро хоста.
Incus появился как форк LXD и использует LXC в качестве низкоуровневой основы. Сам LXC отвечает за namespaces, cgroups и другие механизмы изоляции, а Incus добавляет управление образами, сетями, хранилищами, снапшотами и жизненным циклом контейнеров.
Создать две изолированные системы с Debian можно командами:
incus launch images:debian/12 production
incus launch images:debian/12 staging
Посмотреть список экземпляров и открыть оболочку внутри одного из них:
incus list
incus exec production -- bash
Каждому контейнеру можно назначить собственный IP-адрес, ограничить число ядер и объём памяти:
incus config set production limits.cpu 2
incus config set production limits.memory 2GiB
Для серверной эксплуатации обычно используют непривилегированные контейнеры. В этом режиме root внутри контейнера отображается на непривилегированный UID хоста. Если процесс вырвется за пределы контейнера, он не получит права root на основной системе.
Текущую карту идентификаторов можно посмотреть так:
incus config get production volatile.idmap.current
К слову, Incus оправдан, когда на одном физическом или виртуальном сервере нужно разместить несколько изолированных Linux-систем, дать им отдельные сетевые интерфейсы, настроить лимиты ресурсов и делать снапшоты.
Если нужно ограничить один процесс — Firejail
Firejail — это SUID-программа, которая создаёт песочницу для запуска приложений через namespaces и seccomp. Она появилась в 2015 году и изначально создавалась для десктопных приложений (запускать браузер или PDF-ридер в изоляции). Но она неплохо работает и на сервере.
Для установки и запуска используйте:
# Установить
apt install firejail
# Запустить без доступа к сети
firejail --net=none /usr/bin/myapp
# Запустить с временным домашним каталогом
firejail --private /usr/bin/myapp
Firejail использует профили — текстовые файлы, которые описывают, что приложению разрешено. Некоторые уже включены в поставку, но вы можете написать свой:
cat > /etc/firejail/myapp.profile << 'EOF'
include /etc/firejail/disable-common.inc
include /etc/firejail/disable-devel.inc
whitelist /var/lib/myapp
whitelist /var/log/myapp
caps.drop all
nonewprivs
protocol unix,inet,inet6
seccomp
EOF
Готовый профиль нужно проверить под конкретное приложение. Для запуска с профилем есть команда:
Этот способ не заменяет Docker, но если вам нужно просто изолировать одно приложение на сервере, ограничить его доступ к файловой системе и сети, Firejail — ваш выбор.
Если нужна изоляция файловой системы — chroot + namespaces
Иногда лучший контейнер — это тот, которого нет. Если вам нужна всё-таки изоляция файловой системы, chroot решает эту задачу с 1979 года. Добавьте к нему Linux namespaces (clone с флагами), и вы получите минимальный контейнер без всякого Docker.
Проще не копировать бинарники и библиотеки вручную, а сразу подготовить минимальную систему через debootstrap:
Команда создаёт отдельные mount, PID и network namespaces.
chroot требует ручной работы (настройку UID mapping, сети, файловых монтирований, capabilities, seccomp, cgroups и жизненного цикла процесса), но если вам нужно изолировать одно приложение на сервере, этот вариант даст минимальный оверхед.
Когда Docker всё же нужОн
Есть сценарии, где Docker — правильный выбор даже для одного сервера, например:
ваш проект требует Redis, PostgreSQL, Elasticsearch, Celery и пяти микросервисов — в этом случае docker compose сэкономит часы на настройке.
вам нужно, чтобы окружение на сервере точно совпадало с окружением разработчика — Dockerfile позволяет воспроизводить пользовательское окружение, но архитектура процессора, ядро и настройки хоста по-прежнему будут влиять на работу приложения;
ваш пайплайн собирает Docker-образ и деплоит его (логично, да?);
ваше приложение уже упаковано в Docker, переписывать его под systemd бессмысленно (также логично).
О том, что лучше выбрать… Если вы сисадмин с 10–15-летним стажем, у вас стоит сервер с Debian, на нём работают nginx, PostgreSQL и приложение на Python, а логи, резервные копии и мониторинг через node_exporter и Prometheus давно настроены, то вы точно не читаете эту статью. Вы и так все знаете без моих советов.
Если вы разработчик, который хочет поднять что-нибудь на дешёвом VDS, и вам важнее, чтобы работало и легко поддерживалось, начните с systemd, virtualenv и nginx. Если нужна простая песочница для одного процесса, посмотрите на Firejail. Для отдельной системной среды со своим systemd подойдёт systemd-nspawn.
Если нужны системные контейнеры с отдельными сетями и снапшотами, используйте Incus. Если вы привыкли к Docker, но хотите обойтись без центрального root-демона, попробуйте Podman. А chroot и unshare выбирайте только для доверенных приложений и задач, где вы готовы настраивать изоляцию вручную.
Делитесь в комментариях, чем вы пользуетесь на своих серверах и почему.