Docker на OpenVZ выдаёт Operation not permitted: почему нужен KVM
Docker на OpenVZ действительно может упираться в Operation not permitted даже тогда, когда команда выполняется от root. Причина часто находится не в правах пользователя внутри VPS, а уровнем выше: OpenVZ использует ядро физического сервера, а доступные контейнеру capabilities, cgroups, сетевые функции, файловые системы и другие возможности ядра определяет администратор хост-ноды.
Но по одной строке Operation not permitted нельзя сразу делать вывод, что виноват OpenVZ и сервер нужно переносить. Docker способен работать внутри некоторых конфигураций OpenVZ и Virtuozzo, если хост подготовлен для вложенной контейнеризации. Такое же сообщение можно получить из-за неправильных прав на каталог, неудачного mount, настроек Docker, cgroups, сети или конкретного приложения.
Нормальная диагностика выглядит не как перебор chmod 777, --privileged и случайных параметров из форумов. Сначала нужно определить тип виртуализации и этап, на котором ломается Docker, затем найти операцию непосредственно перед ошибкой и только после этого проверять соответствующий уровень: namespaces, cgroups, capabilities, storage или networking.

Если выясняется, что Docker требует функцию ядра, которую OpenVZ-контейнер не получил от физического хоста, исправить это изнутри VPS обычно невозможно. KVM меняет именно этот уровень: виртуальная машина загружает собственное гостевое ядро Linux и даёт администратору намного больше контроля над средой, в которой работает Docker.
Почему Docker пишет Operation not permitted, хотя в VPS есть root
root внутри OpenVZ-контейнера не означает полный контроль над ядром физического сервера. Пользователь может устанавливать пакеты, изменять конфигурацию системы, запускать службы и управлять файлами, но часть системных операций всё равно проходит через общее ядро хост-ноды и может быть запрещена снаружи.
Типичный тупик выглядит так: Docker установился без ошибок, docker.service находится в состоянии active, образы скачиваются, но при создании контейнера появляется OCI runtime create failed, а ближе к концу сообщения — Operation not permitted. В другом случае hello-world запускается нормально, но один сервис Compose падает, как только пытается применить sysctl, создать особую сеть, обратиться к device или получить дополнительную capability. Переустановка Docker в такой ситуации почти ничего не проверяет.
Первый шаг — понять не только саму ошибку, но и стадию, на которой она возникает.
| Этап | Симптом | Куда смотреть сначала |
|---|---|---|
| Запуск Docker daemon | docker.service failed, команда docker info не подключается к daemon |
systemctl status docker, journalctl, storage, конфигурация daemon, kernel |
| Получение image | docker pull не скачивает образ |
DNS, сеть VPS, TLS, registry; OpenVZ здесь далеко не первый подозреваемый |
| Создание контейнера | OCI runtime create failed, runc/containerd, EPERM |
Namespaces, cgroups, capabilities, mount, devices |
| Запуск процесса | Контейнер создался и сразу завершился | docker logs, entrypoint, приложение, переменные окружения |
| Создание сети | Ошибка bridge, veth, iptables или nftables | Netfilter, network namespace, forwarding, capabilities |
| Монтирование данных | Ошибка bind mount, volume или filesystem | UID/GID, права каталогов, mount, storage driver |
Минимальный тест выглядит так:
whoami
id
systemctl status docker --no-pager
docker version
docker info
docker run --rm hello-world
Если docker info даже не получает ответ от daemon, сначала разберитесь с запуском самого dockerd:
journalctl -u docker --no-pager -n 100
Если daemon работает, но минимальный контейнер не создаётся, полезны и сообщения containerd:
journalctl -u containerd --no-pager -n 100
Если hello-world запускается, это только базовая проверка. Такой контейнер почти не тестирует сложные volumes, нестандартные namespaces, bridge-сеть, дополнительные capabilities, devices и системные параметры. Один успешный контейнер — слабый тест.
Например, вполне возможна ситуация, когда обычный Nginx в Docker работает, а VPN-контейнер или сервис мониторинга, которому нужен NET_ADMIN, падает. Это не противоречие: второй image требует от ядра больше возможностей.
Главный ориентир: UID 0 ещё не доказывает, что процессу разрешена конкретная операция ядра. Root внутри OpenVZ — это не root над ядром физического сервера.
Не любую ошибку permissions нужно списывать на виртуализацию. Если приложение пытается прочитать каталог другого пользователя, bind mount указывает не туда или файлы принадлежат неверному UID, проблема находится в обычных Unix-правах. OpenVZ становится серьёзным подозреваемым, когда отказ возникает при создании namespace, cgroup, mount, сетевого интерфейса, правила netfilter, device или другого системного объекта.
Как проверить, действительно ли VPS работает на OpenVZ
Связывать Operation not permitted с OpenVZ имеет смысл только после подтверждения типа виртуализации. Название «Linux VPS» не говорит, используется ли OpenVZ, Virtuozzo Containers, KVM или другая технология.
Быстрее всего начать с:
systemd-detect-virt
Типичные варианты вывода могут выглядеть так:
openvz
или:
kvm
Если команда явно возвращает openvz, направление диагностики уже понятно. При kvm искать именно OpenVZ-ограничение бессмысленно: нужно проверять Docker, гостевое ядро, firewall, cgroups и приложение внутри KVM.
Если systemd-detect-virt отвечает none или результат выглядит неожиданно, одной команды недостаточно. Сопоставьте ещё несколько признаков:
hostnamectl
uname -r
test -d /proc/vz && echo "OpenVZ/Virtuozzo marker found"
Если установлена утилита virt-what:
virt-what
| Проверка | Что показывает | Как трактовать |
|---|---|---|
systemd-detect-virt |
Среду виртуализации, которую распознал systemd | openvz или kvm — сильный прямой признак |
hostnamectl |
Системную информацию, иногда включая virtualization | Использовать как независимое подтверждение |
/proc/vz |
Характерный маркер семейства OpenVZ/Virtuozzo | Полезен вместе с другими признаками, особенно на старых системах |
uname -r |
Запущенное ядро | Сам по себе не доказывает тип виртуализации |
| Информация провайдера | Технологию конкретного тарифа или контейнера | Если локальные признаки противоречат друг другу, это самый прямой вопрос поддержке |
Достаточным подтверждением обычно можно считать согласованную картину: systemd-detect-virt определяет OpenVZ, присутствуют характерные маркеры контейнерной среды, а провайдер указывает OpenVZ или Virtuozzo Containers для этого VPS. Если одна команда говорит одно, а остальные признаки другое, не стройте дальнейшую диагностику на предположении.
В поддержке подобное часто всплывает только после установки Docker. Nginx, PHP, MySQL, cron и SSH годами могут не требовать тех функций ядра, на которых ломается вложенная контейнеризация. Пользователь впервые узнаёт о типе VPS именно тогда, когда приложение просит возможность, которой root внутри контейнера не управляет.
Если определить виртуализацию уверенно не удалось, спросите провайдера прямо: «Использует ли мой VPS OpenVZ/Virtuozzo Containers или KVM и поддерживается ли запуск Docker внутри этой среды?» Это полезнее, чем несколько часов подгонять Docker под неверное предположение.
Какие возможности Linux нужны Docker и что OpenVZ может ограничить
Docker зависит не только от dockerd. При создании контейнера задействуются механизмы ядра Linux: namespaces изолируют процессы и сеть, cgroups управляют ресурсами, capabilities разрешают отдельные привилегированные действия, storage driver работает с файловыми слоями, а Docker networking использует сетевые namespaces, bridge и netfilter.
Поэтому состояния «Docker установлен» и «Docker может использовать всё необходимое для этого Compose-проекта» — разные вещи.
Зачем Docker нужны namespaces и что проверять при EPERM
Namespaces дают контейнеру отдельное представление процессов, сети, mount-точек, hostname и других ресурсов. Docker обычно задействует PID, network, mount, IPC и UTS namespaces, а некоторые конфигурации используют и user namespaces.
Посмотреть существующие namespaces можно так:
lsns
readlink /proc/1/ns/mnt
readlink /proc/self/ns/net
Здесь есть важная граница: lsns показывает существующие namespaces, но не отвечает на вопрос, разрешено ли Docker создать любой новый namespace с нужными параметрами. Поэтому при ошибке вида failed to create ... namespace: operation not permitted лог runc/containerd обычно информативнее самого списка lsns.
Если EPERM возникает ещё на стадии container create, до запуска entrypoint приложения, смотреть код Node.js, PHP или сам веб-сервис рано. Процесс приложения ещё не успел стартовать.
Как проверить cgroups и понять результат
Docker использует cgroups для учёта и ограничения CPU, RAM, количества процессов и других ресурсов. Начните с информации, которую видит сам daemon:
docker info | grep -i -E 'Cgroup Driver|Cgroup Version'
mount | grep cgroup
stat -fc %T /sys/fs/cgroup
На системе с cgroup v2 последняя команда обычно показывает:
cgroup2fs
Для cgroup v1 картина mount будет другой, поэтому один stat не нужно использовать как универсальный тест. Смотрите все три результата вместе.
Нормальная ситуация: Docker определяет Cgroup Driver и Cgroup Version, hierarchy смонтирована, а контейнеры с обычными resource limits создаются. Наличие /sys/fs/cgroup само по себе ещё ничего не гарантирует. Каталог может существовать, но нужная операция записи внутри внешнего OpenVZ-контейнера будет запрещена.
Сильнее говорят логи:
failed to create cgroup
permission denied
read-only file system
operation not permitted
Формулировка зависит от runtime и окружения, но диагностическая мысль одна: если Docker на стадии создания контейнера не может создать или изменить cgroup, проверка chmod каталога приложения к этой ошибке отношения не имеет.
Отдельный тест можно сделать на контейнере с ограничением памяти:
docker run --rm --memory=128m alpine echo ok
Если обычный контейнер работает, а создание контейнера с resource limit стабильно падает на cgroup-операции, это полезный признак. Он ещё не доказывает вину OpenVZ без проверки логов и виртуализации, но сильно сужает область поиска.
Как проверить Linux capabilities, а не только UID 0
Linux разбивает традиционные полномочия root на отдельные capabilities. CAP_NET_ADMIN, например, нужна для многих операций с сетевыми интерфейсами и маршрутизацией; CAP_SYS_ADMIN охватывает широкий набор чувствительных системных действий.
Сырой набор capabilities текущего процесса можно увидеть так:
grep '^Cap' /proc/self/status
Вывод содержит поля наподобие:
CapInh:
CapPrm:
CapEff:
CapBnd:
Значения там представлены битовыми масками. Для человека такой hex-вывод неудобен: по строке вроде CapEff нельзя на глаз надёжно понять, есть ли CAP_NET_ADMIN. Намного удобнее:
capsh --print
Если нужно декодировать конкретную маску, можно использовать:
capsh --decode=<hex-mask>
Смотрите прежде всего на effective/bounding set и на capability, которой требует проблемный контейнер. Если Docker-контейнеру добавляют NET_ADMIN, а внешний OpenVZ-контейнер сам не может выполнить нужную сетевую операцию, внутренний --cap-add не создаст это право из воздуха.
Как отличить capability-проблему от обычной ошибки приложения
Хороший признак — место возникновения ошибки. Если Compose создаёт контейнер, запускает процесс, а уже приложение пишет Permission denied при чтении /app/config, сначала проверяйте UID/GID и права файлов. Если runc падает до запуска процесса на mount, setns, mknod, network или sysctl, область поиска уже другая.
Ещё один сценарий: hello-world работает, обычный Nginx работает, а контейнер с VPN или сетевым мониторингом падает только без NET_ADMIN. Это не значит, что Docker «то работает, то нет». Просто разные images требуют разные kernel-возможности.
| Механизм | Для чего нужен Docker | Характерный симптом | Что действительно проверить |
|---|---|---|---|
| Namespaces | Изоляция процессов, сети и mount-точек | OCI/runc EPERM при создании контейнера | Runtime log и операция, которая не смогла создать namespace |
| Cgroups | Управление CPU, RAM и процессами | failed to create cgroup, read-only hierarchy |
Cgroup Driver/Version, mounts, journalctl |
| Capabilities | Разрешение отдельных привилегированных действий | EPERM при network, mount, device или sysctl | capsh --print и требование конкретного контейнера |
| Filesystem / storage | Слои image и writable layer | Ошибка overlay, mount, snapshot/layer | docker info, /proc/filesystems, daemon log |
| Netfilter | NAT, bridge, публикация портов | iptables/nftables EPERM, не создаётся сеть | Docker network, forwarding, NAT и доступность netfilter |
После ошибки cgroup нет смысла начинать с chmod каталога сайта. После отказа iptables не нужно первым делом менять storage driver. Чем точнее определена системная операция, тем меньше случайных изменений придётся делать на сервере.
Как по тексту ошибки определить, что именно блокирует Docker
Operation not permitted — финальное сообщение об отказе, а не диагноз. В длинной цепочке docker compose → dockerd → containerd → runc полезная часть обычно находится в той же строке или несколькими строками выше: нужно найти действие, которое выполнялось непосредственно перед EPERM.
Для Compose-проекта удобнее получить ошибку в foreground:
docker compose up
После неудачной попытки:
docker ps -a
docker inspect <container>
journalctl -u docker --no-pager -n 100
journalctl -u containerd --no-pager -n 100
Не зацикливайтесь на всей многострочной ошибке целиком. Сначала классифицируйте операцию.
| Фрагмент ошибки | Вероятный уровень | Первая проверка |
|---|---|---|
failed to mount, mounting proc, mounting sysfs |
Mount namespace, capability, filesystem | Runtime log, capabilities, тип mount |
failed to create cgroup, ошибка в /sys/fs/cgroup |
Cgroups | docker info, cgroup mounts, journalctl |
failed to mount overlay, overlayfs |
Storage / filesystem / kernel | Storage Driver, OverlayFS, daemon log |
iptables, nft, can't initialize table |
Networking / netfilter | NAT, bridge, capabilities, доступность netfilter |
mknod, /dev/* |
Devices / capabilities | Devices, privileges и политика внешнего контейнера |
sysctl ... permission denied |
Kernel/sysctl restriction | Какой sysctl запрашивает Compose и доступен ли он в этой среде |
Например, общий рисунок ошибки storage может выглядеть так:
... failed to mount overlay ...
... operation not permitted
А сетевой сбой — так:
... iptables ...
... permission denied ...
... failed to create network ...
Это не строки, которые нужно искать буквально символ в символ: формулировки меняются между версиями Docker, containerd, runc и окружениями. Смысл примеров — показать, какую операцию нужно вычленить из сообщения.
Если лог говорит о cgroup, переходите к cgroups. Если падает iptables, storage пока можно отложить. Если контейнер создан, но entrypoint завершился с кодом 1, сначала нужен docker logs, а не исследование OpenVZ.
События Docker иногда помогают увидеть порядок действий:
docker events
Это удобно, когда Compose одновременно поднимает несколько сервисов и один из них быстро создаётся, падает и перезапускается.
Рабочий приём: смотрите не на слово Operation not permitted, а на глагол перед ним: create, mount, set, mknod, iptables, sysctl. Он определяет следующую ветку диагностики.
Если после локализации выяснилось, что проблема связана с владельцем bind mount, OpenVZ может быть вообще ни при чём. Если один и тот же тест на подтверждённом OpenVZ стабильно падает на kernel-операции, недоступной root внутри VPS, версия с ограничением хоста становится намного убедительнее.
Почему файловая система и storage driver становятся проблемой на OpenVZ
Docker хранит слои образов и writable layer контейнеров через storage backend, который зависит от ядра и файловой системы. В OpenVZ пользователь видит не всё окружение, на котором фактически выполняются эти операции: часть возможностей определяется хост-нодой.
Как определить storage driver и прочитать результат
Начните с:
docker info
Или получите только driver:
docker info | grep -i "Storage Driver"
docker info --format '{{.Driver}}'
Например, результат может быть:
Storage Driver: overlay2
Сам по себе overlay2 не говорит о проблеме. Важна связка: какой driver используется, на какой операции Docker падает и что пишет daemon/runtime.
Проверить, известно ли ядру OverlayFS:
grep overlay /proc/filesystems
Если строка overlay присутствует, ядро знает эту файловую систему. Но это не подтверждает, что конкретному OpenVZ-контейнеру разрешено вложенное использование OverlayFS во всех нужных Docker-сценариях.
Как выглядит проблема именно с storage
Если docker.service не запускается, сначала смотрите daemon log:
systemctl status docker --no-pager
journalctl -u docker --no-pager -n 150
Подозрительны ошибки, в которых рядом находятся storage driver, overlay, mount, snapshot или создание layer. Если daemon запускается, но конкретный контейнер падает при создании writable layer, полезная запись может появиться уже во время docker run или docker compose up.
Диагностическая картина может быть такой:
Storage Driver: overlay2
...
failed to mount overlay ...
operation not permitted
Здесь проверка storage оправданна. Если вместо этого лог говорит:
bind source path does not exist
или приложение получает Permission denied на конкретном каталоге, сначала смотрите сам bind mount, путь и UID/GID. Это другой класс проблемы.
Как отличить проблему storage driver от bind mount
Полезный пограничный случай: named volume работает, а bind mount падает. Это скорее направляет диагностику к пути на host filesystem, владельцу файлов, SELinux/AppArmor в тех системах, где они участвуют, и параметрам самого mount, а не к OverlayFS как таковому.
Посмотреть mounts контейнера:
docker inspect <container>
Для named volume:
docker volume inspect <volume_name>
Если не работает именно слой контейнера до запуска приложения, а не доступ приложения к отдельному каталогу, storage driver становится более вероятной причиной.
Почему vfs полезен как тест, но плох как универсальный рецепт
Старый совет «переключить Docker на vfs» иногда действительно помогает локализовать проблему. Если ошибка, связанная с OverlayFS, исчезла после смены driver, это сильная подсказка. Но vfs не добавляет OpenVZ недоступные namespaces, cgroups, netfilter или capabilities и обычно имеет значительно менее эффективную модель хранения данных.
Не меняйте storage driver вслепую на рабочем сервере. После изменения Docker может перестать видеть контейнеры и образы из старого data-root в прежнем виде. До эксперимента нужно понимать, где находятся постоянные данные и как приложение будет восстановлено.
Правка /etc/docker/daemon.json не может включить функцию ядра, которой управляет только физический хост. Переустановка Docker здесь тоже ничего не добавит ядру.
Если обход с другим driver сработал, повторите полный сценарий: реальный Compose, volumes, network и reboot. Работоспособность одного контейнера ещё не делает среду надёжной для всего проекта.

Почему Docker запускается, но сеть контейнеров не работает
hello-world может проходить, а docker compose up падать уже на создании bridge или правила NAT. Docker networking использует отдельные сетевые namespaces, bridge-интерфейсы, veth, forwarding и netfilter, поэтому успешный запуск процесса внутри контейнера проверяет лишь часть системы.
Типовые симптомы здесь разные:
- Compose не может создать network;
- контейнер запускается, но не выходит в интернет;
- IP-адрес снаружи доступен, а DNS-имя внутри контейнера не разрешается;
- исходящее соединение работает, но опубликованный через
-pпорт снаружи недоступен; - Docker сообщает об ошибке iptables или nftables;
- не создаётся bridge или veth;
- сетевые команды внутри самого OpenVZ VPS возвращают EPERM.
Если контейнер вообще не выходит в сеть
Начните с Docker network:
docker network ls
docker network inspect bridge
ip link
В docker network inspect bridge смотрите, существует ли сеть, какой subnet ей назначен и подключён ли нужный контейнер. Сам JSON большой; здесь не нужно анализировать каждое поле. Нас интересует, создал ли Docker bridge-сеть и видит ли контейнер как её участника.
Маршрут внутри уже запущенного контейнера:
docker exec <container> ip route
Если image не содержит утилиту ip, используйте диагностический image, где она есть, вместо установки пакетов в production-контейнер только ради теста.
Проверка исходящего IP-соединения:
docker run --rm alpine ping -c 1 1.1.1.1
Неудачный ping сам по себе ещё не доказывает проблему Docker или OpenVZ: ICMP может фильтроваться. Для серьёзной диагностики результат нужно сопоставлять с маршрутами, TCP-соединением приложения и состоянием NAT.
Если IP работает, а DNS нет
Отделите DNS от маршрутизации:
docker run --rm alpine nslookup example.com
Если соединение по IP проходит, а DNS lookup нет, исследовать OverlayFS или cgroups уже нет смысла. Смотрите DNS-настройки Docker, /etc/resolv.conf внутри контейнера и доступность используемого resolver.
Если контейнер выходит наружу, но опубликованный порт недоступен
Это отдельная ветка. Сначала убедитесь, что Docker действительно опубликовал порт:
docker ps
docker port <container>
Затем проверьте, на каком адресе слушает сам сервис внутри контейнера. Приложение, привязанное только к 127.0.0.1 внутри контейнера, может создать совсем другую проблему, чем запрет netfilter.
Для firewall и NAT, в зависимости от системы:
iptables -t nat -L -n
или:
nft list ruleset
Forwarding:
sysctl net.ipv4.ip_forward
Если iptables или nft внутри подтверждённого OpenVZ VPS сами отвечают Permission denied или Operation not permitted, это сильный признак того, что сетевые полномочия среды ограничены. Но даже тогда нужно проверить конфигурацию Docker и уточнить у провайдера, разрешены ли нужные netfilter-функции для контейнера.
| Что работает | Что не работает | Куда смотреть |
|---|---|---|
| Ничего из контейнера | IP и DNS | Route, bridge, NAT, forwarding, firewall |
| IP | DNS-имена | Resolver и Docker DNS |
| Исходящий трафик | Published port | docker port, listen address, NAT, firewall |
| Обычный Docker network | Custom/Compose network | Параметры сети, bridge/veth, capabilities |
| Сетевые команды | iptables/nft возвращают EPERM | Capabilities и политика OpenVZ-хоста |
Другими словами, фраза «у Docker не работает сеть» слишком широкая. Отделите DNS от маршрутизации, исходящий трафик от публикации портов, а конфигурацию приложения — от возможности OpenVZ создавать bridge и правила NAT.
Запустившийся hello-world не проверяет весь Docker networking. Проверять нужно именно тот сетевой сценарий, который использует приложение.
Почему --privileged, --cap-add и rootless Docker не всегда спасают OpenVZ
После Operation not permitted часто пробуют --privileged или добавляют конкретную capability. Иногда это действительно исправляет запуск. Но такой результат означает только то, что проблема находилась в полномочиях внутреннего Docker-контейнера, доступных Docker host.
В OpenVZ цепочка выглядит так:
Физический сервер
|
OpenVZ/Virtuozzo container
|
Docker daemon
|
Docker container
--privileged расширяет права последнего уровня относительно Docker host. Он не превращает OpenVZ-контейнер в KVM и не отменяет ограничения, которые физическое ядро уже наложило на внешний контейнер.
Для диагностики можно сравнить обычный запуск и запуск с требуемой capability либо --privileged. Но такой эксперимент нужно проводить только на тестовом контейнере, которому вы доверяете.
| Обычный запуск | --cap-add |
--privileged |
Что вероятно происходит |
|---|---|---|---|
| Падает | Работает | Работает | Контейнеру не хватало конкретной capability внутри Docker host |
| Падает | Падает | Работает | Нужен более широкий набор внутренних privileges; выясните каких именно |
| Падает | Падает | Падает на той же kernel-операции | Вероятно, ограничение находится выше Docker-контейнера — в Docker host/OpenVZ |
Последняя строка не является математическим доказательством OpenVZ-проблемы. То же поведение может дать security policy или ошибка конфигурации Docker host. Но вместе с подтверждённым OpenVZ и логом, где одна и та же операция получает EPERM, это сильный диагностический признак.
Почему --cap-add имеет внешний предел
Например:
docker run --rm --cap-add NET_ADMIN <image>
может дать Docker-контейнеру NET_ADMIN относительно Docker host. Но если сам внешний OpenVZ-контейнер не получил возможность выполнить требуемую сетевую операцию, передать её дальше не получится.
Поэтому не нужно последовательно добавлять SYS_ADMIN, NET_ADMIN и остальные capabilities наугад. Сначала найдите операцию в логе и определите, какое право ей действительно требуется.
Почему rootless Docker решает другую задачу
Rootless Docker предназначен для запуска daemon и контейнеров без обычных root-привилегий и использует user namespaces. Это полезная модель безопасности, но не способ получить запрещённые внешним OpenVZ-хостом kernel-возможности.
У rootless есть собственные требования к user namespaces, subordinate UID/GID и сетевой модели. Если первоначальная ошибка никак не связана с моделью root daemon, переход в rootless добавляет новые переменные в диагностику.
Не используйте chmod -R 777, отключение SELinux/AppArmor, очистку firewall, --privileged или широкий набор capabilities как универсальную последовательность «починки». Каждое изменение должно отвечать на конкретный вопрос.
Если внешний контейнер не получил нужное право от хоста, внутренний Docker-контейнер не сможет обойти эту границу одним параметром запуска.
Что меняется при переносе Docker с OpenVZ на KVM
KVM решает этот класс проблем не потому, что KVM обязательно быстрее OpenVZ. Для Docker важнее модель виртуализации: OpenVZ-контейнер использует ядро хост-системы, а KVM-виртуальная машина загружает собственное гостевое ядро Linux.
Администратор KVM VPS может обновлять своё ядро, управлять доступными внутри VM kernel modules, cgroups, sysctl, firewall и другими механизмами гостевой ОС без необходимости просить владельца OpenVZ-ноды включить функцию именно для системного контейнера.
Это не означает полный доступ к физическому серверу. KVM-гость остаётся виртуальной машиной с виртуальными CPU, RAM, дисками, сетевыми устройствами и ограничениями гипервизора. Но Docker взаимодействует с ядром собственной VM, а не работает как дополнительный уровень контейнеризации внутри ограниченного OpenVZ-контейнера.
| Параметр | OpenVZ/Virtuozzo container | KVM VM | Что это означает для Docker |
|---|---|---|---|
| Ядро Linux | Общее с хост-нодой | Собственное гостевое | В KVM администратор лучше контролирует kernel-среду |
| Kernel modules | Определяются хостом | Доступны в рамках гостевого ядра и виртуального оборудования | Меньше зависимость от конфигурации внешнего контейнера |
| Namespaces | Могут ограничиваться контейнерной платформой | Создаются внутри гостевого ядра | Среда предсказуемее для обычного Docker runtime |
| Cgroups | Зависят от конфигурации OpenVZ-хоста | Управляются внутри гостевой ОС | Проще использовать стандартные Docker resource limits |
| OverlayFS / storage | Зависит от возможностей хоста и контейнера | Зависит прежде всего от гостевого ядра и файловой системы VM | Администратор лучше контролирует storage stack |
| iptables / nftables | Часть операций может быть ограничена хостом | Firewall настраивается внутри VM | Bridge/NAT Docker меньше зависят от политики OpenVZ |
| sysctl | Часть параметров может быть недоступна | Большинство гостевых параметров контролирует root VM | Меньше внешних запретов на системные настройки контейнеров |
| Зависимость от провайдера | Высокая для функций общего ядра | В основном ресурсы, виртуальное оборудование и гипервизор | Docker host получается самостоятельнее |
Что KVM не исправит
После миграции легко получить ложное ощущение, что любая Docker-проблема теперь должна исчезнуть. Это не так.
| Проблема | Решит ли её сам переход на KVM |
|---|---|
| Неверный UID/GID bind mount | Нет |
| Ошибка в Compose-файле | Нет |
| Конфликт опубликованного порта | Нет |
| Неверный DNS Docker или приложения | Нет |
| Ошибочный firewall внутри гостевой ОС | Нет |
| Нехватка RAM и OOM | Нет, если ресурсов по-прежнему недостаточно |
| Неподходящая архитектура image | Нет |
| Запрещённая OpenVZ kernel-функция | Обычно устраняется сама внешняя зависимость, если функция поддерживается гостевой ОС KVM |
На новом KVM VPS повторите тот же тест, который падал на OpenVZ. Иначе вы не узнаете, исчезло ограничение или просто изменился сценарий.
systemd-detect-virt
uname -r
docker info
docker run --rm hello-world
docker compose up -d
docker network ls
Если проблемный Compose-проект на OpenVZ стабильно получает EPERM при одной и той же kernel-операции, а после переноса на чистую KVM VM этот же сценарий проходит, причина миграции подтверждается гораздо лучше, чем общим тезисом «Docker лучше запускать на KVM».
Как перенести Docker-проект с OpenVZ на KVM без потери данных
Когда ограничение OpenVZ подтверждено, опаснее всего переносить сервер в спешке. Сам Docker-контейнер обычно можно пересоздать из image. Реальная ценность находится в Compose-конфигурации, .env, bind mounts, named volumes, базах данных, сертификатах и пользовательских файлах.
Перед миграцией зафиксируйте состояние:
docker version
docker compose version
docker ps
docker images
docker volume ls
docker network ls
Для каждого контейнера определите, откуда он получает постоянные данные. В Compose-файле ищите volumes, а named volume можно проверить так:
docker volume inspect <volume_name>
Отдельно сохраните:
compose.ymlилиdocker-compose.yml;- файлы
.env; - bind-mounted каталоги;
- named volumes;
- backup баз данных;
- uploads и пользовательские файлы;
- конфигурацию Nginx, Traefik, Caddy или другого reverse proxy;
- TLS-сертификаты, если ими управляет не сам контейнер;
- secrets и ключи, которые не находятся в image.
Каталог /var/lib/docker не стоит считать универсальным переносимым backup. Внутреннее состояние Docker связано со storage driver, data-root и состоянием daemon. Надёжнее переносить декларативную конфигурацию и постоянные данные, а контейнеры пересоздавать на новом VPS.
Для MySQL, MariaDB и PostgreSQL безопаснее использовать корректный backup с учётом конкретной СУБД, а не копировать файлы работающей базы во время записи. Файлы могут скопироваться без ошибок, но состояние базы получится несогласованным.
Рабочая последовательность:
- Зафиксировать версии Docker и Compose и список контейнеров.
- Сохранить Compose-файлы,
.envи конфигурацию. - Определить все bind mounts и named volumes.
- Сделать backup базы и пользовательских данных.
- Развернуть чистый KVM VPS.
- Установить Docker и Compose из поддерживаемого источника.
- Перенести конфигурацию и постоянные данные.
- Запустить проект.
- Проверить контейнеры, volumes, сеть, базу и HTTP/HTTPS.
- Перезагрузить VPS и проверить автоматический запуск после reboot.
- Только после полной проверки переключить рабочий трафик.
После запуска:
docker compose ps
docker compose logs
Статуса Up недостаточно. Выполните операцию записи в приложении, проверьте, что данные действительно попали в нужный volume, убедитесь в работе БД, откройте сервис через тот же HTTP/HTTPS-маршрут и проверьте его после reboot.
Контейнер расходный, данные — нет.
Когда перестать чинить OpenVZ и перенести Docker на KVM
Перенос оправдан, если проблемную kernel-функцию может включить только администратор OpenVZ-хоста. Если же причина находится в Compose, UID/GID, bind mount, firewall или настройке приложения внутри VPS, сначала исправьте её: KVM не нужен как лекарство от любой ошибки Docker.
| Проблема | Можно исправить внутри VPS | Может требовать провайдера | Переход на KVM |
|---|---|---|---|
| Неверный владелец bind mount | Да | Нет | Не нужен |
| Ошибка в Compose-файле | Да | Нет | Не нужен |
| Конфликт порта приложения | Да | Обычно нет | Не нужен |
| Отсутствующая capability внешнего контейнера | Обычно нет | Да | Убирает зависимость от OpenVZ, если capability доступна в гостевой KVM ОС |
| Недоступная kernel-функция | Нет | Да | Разумный вариант |
| Ограничение cgroups OpenVZ | Не всегда | Да | Разумный вариант |
| Запрет требуемой filesystem-функции | Не всегда | Да | Разумный вариант |
| Недоступные netfilter/NAT-функции | Не всегда | Да | Разумный вариант |
Оставаться на OpenVZ можно, если провайдер официально поддерживает Docker в конкретной конфигурации, реальный Compose-проект протестирован, storage и networking работают, cgroups доступны, а приложению не нужны запрещённые kernel-функции. Само слово OpenVZ ещё не означает, что Docker обязательно сломан.
Совсем другая ситуация — сегодня проект работает, а после обновления image новый сервис требует недоступный sysctl, device или capability. Если для каждого такого изменения нужно выяснять, разрешит ли его администратор хост-ноды, workaround становится частью архитектуры. Для постоянной Docker-инфраструктуры это плохая зависимость.
Что отправить в поддержку VPS, если подозрение падает на OpenVZ
Запрос «Docker не работает, помогите» почти неизбежно потребует дополнительной переписки. Гораздо полезнее сразу приложить результаты базовой диагностики:
systemd-detect-virt
uname -r
docker version
docker info
docker run --rm hello-world
journalctl -u docker --no-pager -n 100
Если ошибка возникает только в Compose, приложите сам фрагмент ошибки из:
docker compose up
и укажите, на какой операции всё падает: cgroup, OverlayFS, iptables/nftables, sysctl, mount, device или namespace.
Вопрос поддержке лучше формулировать предметно:
«Поддерживается ли Docker внутри этого OpenVZ/Virtuozzo-контейнера и разрешена ли требуемая функция: cgroup, OverlayFS, netfilter, конкретная capability или системный параметр?»
Если провайдер отвечает, что функция отключена на уровне хоста и не может быть включена для VPS, дальнейшая переустановка Docker внутри контейнера уже не имеет смысла.
Docker выдаёт Operation not permitted: проверка за 10–15 минут
- Определить виртуализацию через
systemd-detect-virtи дополнительные признаки. - Проверить состояние
docker.service. - Выполнить
docker versionиdocker info. - Запустить
docker run --rm hello-world. - Определить стадию сбоя: daemon, create, start, network или volume.
- Получить
journalctlDocker и при необходимости containerd. - Найти операцию непосредственно перед
Operation not permitted. - При cgroup-ошибке проверить Cgroup Driver/Version и
/sys/fs/cgroup. - При storage-ошибке определить storage driver и сопоставить его с логом mount/overlay.
- При сетевой ошибке разделить DNS, outbound, bridge/NAT и published port.
- При capability-ошибке проверить
capsh --printи требования проблемного контейнера. - Сравнить минимальный контейнер с реальным
docker compose up. - Определить, может ли нужную настройку изменить root VPS или только администратор OpenVZ-хоста.
Если причина находится внутри Docker, Compose или приложения, исправляйте её на текущем VPS. Если воспроизводимый EPERM упирается в функцию общего ядра OpenVZ, которой root внутри VPS не управляет, KVM устраняет именно эту внешнюю зависимость. Не надо чинить изнутри то, что отключено снаружи.


