SSD Nodes Learn 🎉 VPS от $5.50/мес
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-13

Установка Docker на Rocky Linux и AlmaLinux

Пошаговое руководство по установке Docker Engine через dnf. Узнайте, как разрешить конфликт с Podman, настроить SELinux для bind-mount и избежать открытия портов в интернет.

Установка Docker на Rocky Linux и AlmaLinux

Для установки Docker на Rocky Linux или AlmaLinux необходимо добавить официальный репозиторий Docker, установить движок вместе с плагином compose, а затем активировать службу. Этот процесс состоит из четырех команд и идентичен для обоих дистрибутивов, так как они являются пересборками Red Hat Enterprise Linux (RHEL) и используют общую структуру пакетов. CentOS Stream работает аналогичным образом.

Процесс установки краток, поэтому большая часть этого руководства посвящена отличиям Enterprise Linux (EL) от Ubuntu. Возможно, команда docker в вашем образе уже занята Podman. SELinux блокирует файлы, смонтированные через bind-mount, пока они не получат соответствующую метку. Firewalld не фильтрует порты, опубликованные Docker, поэтому порт контейнера может быть открыт в интернет, в то время как firewall-cmd будет сообщать, что порты закрыты.

Не используйте вспомогательный скрипт с get.docker.com. В официальной документации Docker указано, что он не рекомендуется для использования в production. Скрипт перезаписывает конфигурацию репозиториев без запроса подтверждения, и его нельзя безопасно запускать повторно для обновления. Добавление репозитория вручную гарантирует, что dnf upgrade будет обрабатывать Docker так же, как и любой другой пакет в системе.

Отвечает ли Podman на команду docker?

В Rocky Linux и AlmaLinux Podman входит в стандартные репозитории, и многие образы VPS устанавливают его автоматически. Некоторые образы идут дальше и устанавливают podman-docker, который размещает скрипт оболочки по пути /usr/bin/docker, вызывающий Podman. В результате каждая введенная вами команда docker запускает Podman, поэтому руководство, написанное для Docker, начинает выдавать неожиданные результаты.

Первый признак — это баннер. Скрипт /usr/bin/docker проверяет наличие файла /etc/containers/nodocker, и если этот файл отсутствует, он выводит одну строку перед выполнением любых действий:

Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.

Кто-то мог создать этот файл, чтобы скрыть баннер, поэтому не стоит полагаться только на него. Узнайте у базы данных пакетов, какой пакет владеет этим бинарным файлом:

command -v docker
rpm -qf "$(command -v docker)"

Ответ, начинающийся с podman-docker, означает, что на команды отвечает Podman. Ответ, начинающийся с docker-ce-cli, означает, что установлен настоящий Docker. Если rpm -qf сообщает, что ни один пакет не владеет файлом, значит, он был установлен вручную, и вам следует изучить скрипт, прежде чем доверять ему.

Podman запускает те же образы OCI и является разумным выбором. Если он вам подходит, остановитесь на этом. Если вам нужен Docker Engine, сначала удалите конфликтующие пакеты. Это список, который Docker рекомендует для RHEL:

sudo dnf remove docker docker-client docker-client-latest docker-common \
  docker-latest docker-latest-logrotate docker-logrotate docker-engine podman runc

Изучите, что именно dnf собирается удалить, прежде чем подтверждать действие. На свежем образе VPS этот список будет коротким. На сервере, который уже использовался, удаление podman может привести к удалению cockpit-podman или другого инструмента, зависящего от него.

В принципе, можно сохранить Podman вместе с Docker: удалите только podman-docker, чтобы освободить имя docker, и runc, который заменяется пакетом containerd.io. Документация Docker рассматривает Podman как конфликтующий пакет, поэтому такая конфигурация не поддерживается Docker. Если при установке всё ещё сообщается о конфликте, используйте полный список для удаления, приведенный выше.

Добавление репозитория Docker с помощью dnf config-manager

Docker публикует RPM-пакеты для Enterprise Linux по адресу download.docker.com. Файл репозитория указывает на дерево CentOS, которое используют Rocky Linux и AlmaLinux для разрешения зависимостей. По состоянию на август 2026 года Docker рекомендует этот репозиторий для CentOS Stream 9 и CentOS Stream 10.

sudo dnf -y install dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

В 5-й версии dnf аргумент --add-repo был удалён, поэтому вторая команда в новых релизах завершается ошибкой. Проверьте версию вашего менеджера пакетов и выберите подходящий вариант:

dnf --version

Если выводится версия 5.x, используйте вариант с подкомандой:

sudo dnf config-manager addrepo --from-repofile=https://download.docker.com/linux/centos/docker-ce.repo

Обе команды создают одинаковый файл в /etc/yum.repos.d/docker-ce.repo. Неверный вариант выдаст ошибку о неизвестном аргументе, а не выполнит действие некорректно, поэтому вы не пропустите проблему.

Этот файл репозитория устанавливает baseurl в путь, содержащий $releasever, а dnf подставляет в эту переменную значение из вашего пакета релиза. Rocky Linux и AlmaLinux устанавливают её как номер мажорной версии, то есть 9 в EL 9 и 10 в EL 10, поэтому репозиторий CentOS корректно работает на системе Rocky. Проверьте подстановку перед установкой:

sudo dnf repoinfo docker-ce-stable

Изучите строку Repo-baseurl. Она должна заканчиваться на /9/x86_64/stable или /10/x86_64/stable. Если ваш релиз устанавливает $releasever как минорную версию, например 9.6, то при получении метаданных dnf сообщит об ошибке Status code: 404 для этого URL. Исправьте это, отредактировав /etc/yum.repos.d/docker-ce.repo и заменив $releasever на номер мажорной версии.

Установка движка и плагина compose

sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Пять пакетов, каждый из которых выполняет свою задачу. docker-ce — это демон, dockerd. docker-ce-cli — это команда docker, которую вы вводите. containerd.io — это среда выполнения контейнеров, которой управляет демон. docker-buildx-plugin собирает образы. docker-compose-plugin предоставляет docker compose в качестве подкоманды.

Эти пакеты не устанавливают бинарный файл docker-compose с дефисом. Это был Compose v1, жизненный цикл которого завершился в июле 2023 года. Любой вызов docker-compose через дефис требует обновления до docker compose через пробел.

При первой установке процесс прерывается для импорта ключа подписи Docker и отображения его отпечатка. Ключ поступает из gpgkey=https://download.docker.com/linux/centos/gpg в файле репозитория, который вы только что добавили, поэтому сверьте отпечаток, выведенный dnf, с этим URL, прежде чем подтвердить его.

Одна ошибка встречается достаточно часто, чтобы о ней упомянуть. Если dnf сообщает, что containerd.io требует container-selinux, а пакет, предоставляющий эту зависимость, отсутствует, значит, ваш репозиторий AppStream отключен. Запустите dnf repolist и убедитесь, что appstream присутствует в списке, так как именно там container-selinux поставляется в EL 9 и EL 10.

Запуск Docker и проверка работоспособности

sudo systemctl enable --now docker
sudo systemctl status docker --no-pager
sudo docker run --rm hello-world

RPM-пакеты Docker оставляют демон остановленным и отключенным после установки. Именно поэтому данный шаг присутствует на странице Docker для CentOS, в отличие от страницы для Ubuntu, где deb-пакет запускает службу автоматически. Если пропустить enable, Docker будет работать только до следующей перезагрузки, после чего выключится вместе со всеми контейнерами.

systemctl status должен выводить Active: active (running). Контейнер hello-world должен вывести This message shows that your installation appears to be working correctly. и завершить работу. Если вместо этого выводится ошибка прав доступа к /var/run/docker.sock, значит, вы пропустили sudo, что исправляется разделом о группе docker ниже.

Проверьте плагин compose отдельно, так как это другой пакет, который может отсутствовать, даже если движок работает исправно:

docker compose version

Корректный ответ выглядит как Docker Compose version v2.x.x. Автоматический запуск служб после перезагрузки — это отдельный вопрос, не связанный с включением демона, и политики перезапуска определяют, будут ли службы Compose запускаться при загрузке системы.

Почему при bind mount возникает ошибка permission denied?

В Rocky Linux и AlmaLinux SELinux (Security-Enhanced Linux) по умолчанию работает в режиме enforcing. Проверьте это с помощью getenforce, которая выводит Enforcing.

Docker-контейнеры работают с типом SELinux container_t, и этот тип может читать и записывать только файлы с меткой container_file_t. Директория, созданная вами на хосте, наследует метку родительского пути, которая не является container_file_t. Контейнеру отказывается в доступе, даже если владелец, группа и права доступа выглядят корректно со стороны хоста. Воспроизвести проблему можно тремя командами:

sudo mkdir -p /srv/site
echo hello | sudo tee /srv/site/index.html
sudo docker run --rm -v /srv/site:/usr/share/nginx/html:ro nginx:alpine cat /usr/share/nginx/html/index.html

Контейнер выведет:

cat: can't open '/usr/share/nginx/html/index.html': Permission denied

Две команды покажут причину. ls -ldZ /srv/site выводит метку, которая для пути внутри /srv является system_u:object_r:var_t:s0, а не container_file_t. Затем sudo ausearch -m avc -ts recent выводит запись аудита ядра, содержащую avc: denied { read }, поле scontext= с именем container_t и поле tcontext= с меткой, которую вы только что увидели на директории. Несоответствие между этими двумя полями и является всей сутью проблемы.

Решение заключается в добавлении суффикса к аргументу volume. Docker перемаркирует путь за вас:

sudo docker run --rm -v /srv/site:/usr/share/nginx/html:ro,z nginx:alpine cat /usr/share/nginx/html/index.html

:z в нижнем регистре помечает содержимое как общее (shared), чтобы несколько контейнеров могли использовать одну и ту же директорию. :Z в верхнем регистре помечает её как частную и неразделяемую (private), привязанную к одному контейнеру; второму контейнеру, читающему тот же путь, будет отказано в доступе. Используйте :z для всего, к чему также обращается sidecar или контейнер резервного копирования. Используйте :Z для директории базы данных, которой владеет один контейнер.

Документация Docker содержит предупреждение, которое стоит повторить, так как перемаркировка выполняется рекурсивно. Bind-mount системной директории, такой как /home или /usr, с флагом :Z «делает хост-машину неработоспособной, и вам, возможно, придётся перемаркировать файлы хоста вручную». Применяйте эти суффиксы к директориям, которые вы создали для контейнера, но никогда не используйте их для системных путей.

В Compose суффикс добавляется к той же строке:

services:
  web:
    image: nginx:alpine
    volumes:
      - /srv/site:/usr/share/nginx/html:ro,z

Существует два ограничения, на которых легко оступиться. Флаг --mount вообще не может задать метку SELinux, поэтому используйте -v, если она вам нужна. Именованные тома (named volumes) не требуют суффикса, так как Docker сам маркирует директории, создаваемые в /var/lib/docker/volumes.

Не отключайте SELinux. Используйте sudo setenforce 0 только в качестве минутного теста: если после этого контейнер заработал, значит, проблема в метке, и решением является :z. Сразу же верните режим обратно с помощью sudo setenforce 1. В Enterprise Linux ошибка permission denied при bind mount имеет две разные причины, которые выглядят одинаково изнутри контейнера. Первая — это метка SELinux. Вторая — обычные числовые идентификаторы владельца и группы, для решения которой существуют переменные PUID и PGID. ls -lnZ показывает права доступа, числового владельца и метку в одной строке, чтобы вы могли понять, с чем именно боретесь.

Почему опубликованный порт доступен, если firewalld показывает, что он закрыт?

Firewalld является стандартным межсетевым экраном в Rocky Linux и AlmaLinux. Проверьте его статус с помощью sudo systemctl is-active firewalld. Теперь опубликуйте порт и посмотрите, какие порты, по мнению firewalld, открыты:

sudo docker run -d --name web -p 8080:80 nginx:alpine
sudo firewall-cmd --list-ports

firewall-cmd выводит пустую строку. С другой машины curl -I http://YOUR_SERVER_IP:8080/ возвращает HTTP/1.1 200 OK. Порт открыт для интернета, хотя ваш межсетевой экран сообщает об обратном.

Причина кроется в пути, по которому проходит пакет. Правила зон firewalld фильтруют трафик, адресованный самому хосту. Опубликованный порт не адресуется хосту: Docker устанавливает правило destination NAT (трансляция сетевых адресов), которое перезаписывает адрес назначения на адрес контейнера до того, как пакет попадет на входной путь хоста. В результате ядро пересылает пакет, вместо того чтобы доставить его локально. Затем Docker помещает свои мостовые интерфейсы в зону firewalld под названием docker, целевым действием которой является ACCEPT, и добавляет политику пересылки docker-forwarding, разрешающую пересылку из любой зоны в зону docker. Ваши правила зон никогда не видят этот пакет.

Самое чистое решение не требует правил межсетевого экрана. Привяжите хостовую часть публикации к loopback и установите перед ней reverse proxy:

sudo docker rm -f web
sudo docker run -d --name web -p 127.0.0.1:8080:80 nginx:alpine
curl -I http://127.0.0.1:8080/

Локальный curl возвращает HTTP/1.1 200 OK, а тот же запрос с другой машины больше не проходит. Любой порт, опубликованный без указания адреса хоста в аргументе -p, становится доступен на всех интерфейсах. Поэтому рассматривайте обычный -p 8080:80 как решение сделать сервис публично доступным.

Если вам нужно, чтобы сервис был доступен с одних адресов, но не с других, Docker резервирует для вас цепочку. DOCKER-USER обрабатывается до собственных правил accept в Docker, поэтому правило, добавленное туда, сохранится после перезапуска Docker и перезаписи его цепочек:

sudo iptables -I DOCKER-USER -i enp1s0 ! -s 203.0.113.10 -j DROP
sudo iptables -S DOCKER-USER

Используйте имя интерфейса из ip route show default, а не полагайтесь на eth0, так как современные образы EL используют имена вроде enp1s0 или ens3. В Rocky и AlmaLinux команда iptables является уровнем совместимости поверх nftables, и цепочки Docker видны через нее. Правила, добавленные таким образом, исчезнут после перезагрузки, если их не сохранить, поэтому запишите их в systemd unit, как только убедитесь в их корректности.

Docker Engine 28.0, выпущенный в 2025 году, закрыл смежную уязвимость: прямой маршрутизируемый доступ к портам контейнеров, которые не были опубликованы, теперь блокируется в цепочке DOCKER. Это изменение не затрагивает опубликованные порты, поэтому всё вышесказанное остается актуальным для текущих версий. Стоит выработать одну рабочую привычку: после любого sudo firewall-cmd --reload повторно проверяйте опубликованный порт. Если он перестал отвечать, sudo systemctl restart docker переустановит правила Docker.

Администраторы Ubuntu сталкиваются с той же проблемой при использовании другого инструмента, подробнее об этом в почему опубликованные порты Docker игнорируют правила ufw. В обоих случаях причина заключается в пути NAT. Меняется только используемый межсетевой экран.

Добавление пользователя без прав root в группу docker

Ввод sudo перед каждой командой docker быстро утомляет, а группа docker позволяет этого избежать:

sudo usermod -aG docker $USER
newgrp docker
docker run --rm hello-world

usermod -aG изменяет /etc/group, но текущая оболочка уже загрузила список групп, поэтому изменения не вступят в силу до начала нового сеанса. newgrp docker запускает оболочку с уже примененными правами группы, чтобы вы могли сразу проверить работу. Новые SSH-сессии подхватывают изменения автоматически.

Четко осознавайте, какие права дает эта группа. Членство в ней предоставляет права на запись в /var/run/docker.sock, а любой процесс, имеющий доступ к этому сокету, может дать демону команду запустить контейнер с примонтированной файловой системой хоста. Одна команда демонстрирует, что это означает:

docker run --rm -v /:/host alpine wc -l /host/etc/shadow

Она считывает файл, доступный только пользователю root, из учетной записи без прав sudo. Официальная документация Docker по настройке после установки подтверждает это: группа docker предоставляет привилегии, эквивалентные правам root. Добавляйте учетную запись в эту группу только в том случае, если вы готовы предоставить ей права sudo. Если вы настраиваете учетные записи на новом сервере, решайте этот вопрос одновременно с остальной настройкой пользователей с минимальными привилегиями на VPS, а не постфактум.

В Docker также предусмотрен rootless-режим, в котором демон запускается от имени непривилегированного пользователя. Это отдельный путь установки, который меняет поведение драйверов хранилища и портов ниже 1024, поэтому планируйте его как самостоятельную задачу, а не как опцию, которую можно добавить позже.

Дальнейшие шаги

Теперь у вас есть движок, плагин compose, сервис, который автоматически запускается после перезагрузки, и три специфичных для EL поведения, описанных выше. Следующим шагом будет создание compose.yaml для каждого сервиса, а в анатомии Compose-файла подробно разобраны формат файла и управляющие им команды. Если это ваш первый контейнерный хост, в руководстве запуск Docker на VPS рассмотрены вопросы выбора ресурсов, хранения данных и гигиены образов, которые остались за рамками данного руководства.

FAQ

Работает ли репозиторий Docker для CentOS на Rocky Linux и AlmaLinux?

Да. Добавьте https://download.docker.com/linux/centos/docker-ce.repo с помощью dnf config-manager. Файл baseurl содержит $releasever, который Rocky Linux и AlmaLinux автоматически заменяют на номер мажорной версии. В результате система на базе EL 9 обращается к дереву CentOS 9, а на базе EL 10 — к дереву CentOS 10. Проверьте подстановку командой sudo dnf repoinfo docker-ce-stable и изучите строку Repo-baseurl. Если при получении метаданных через dnf возникает Status code: 404, это означает, что переменная раскрылась в конкретный минорный релиз. Исправьте это, отредактировав /etc/yum.repos.d/docker-ce.repo и указав там только номер мажорной версии.

Можно ли установить Docker и podman на один сервер?

Документация Docker указывает podman и runc как конфликтующие пакеты и рекомендует удалить их перед установкой Docker Engine. Основной конфликт вызывает пакет podman-docker, который владеет файлом /usr/bin/docker и перенаправляет любую команду docker на podman. Выполните rpm -qf "$(command -v docker)", чтобы узнать, какой пакет владеет этим путем. Если вывод начинается с podman-docker, значит, запросы обрабатывает podman. Docker не поддерживает одновременную работу обоих движков, поэтому на критически важных серверах выберите что-то одно.

Почему при использовании bind mount в контейнере возникает ошибка permission denied?

В Rocky Linux и AlmaLinux по умолчанию включен SELinux. Контейнеры запускаются с типом container_t и могут работать только с файлами, имеющими метку container_file_t. Созданная вами директория имеет другую метку, поэтому доступ запрещен независимо от прав доступа и владельца файла. Проверьте это с помощью ls -ldZ для пути на хосте и sudo ausearch -m avc -ts recent, которая выведет avc: denied с двумя несовпадающими контекстами. Добавьте :z к аргументу volume для контента, общего для нескольких контейнеров, или :Z для контента, используемого только одним контейнером. Никогда не указывайте :Z для /home или /usr, так как рекурсивная перемаркировка нарушит работу хостовой системы.

Нужно ли открывать порт в firewalld для публикации порта контейнера?

Нет, и в этом заключается проблема. Правило NAT в Docker перезаписывает адрес назначения до того, как пакет попадет в цепочку input хоста, поэтому правила зоны firewalld его не проверяют. Docker также помещает свои мосты в зону firewalld с именем docker и целью ACCEPT. Контейнер, запущенный с -p 8080:80, доступен из интернета, даже если sudo firewall-cmd --list-ports ничего не выводит. Публикуйте порты на конкретный адрес с помощью -p 127.0.0.1:8080:80, если доступ к сервису должен иметь только хост, либо добавляйте правила фильтрации в цепочку DOCKER-USER, которую Docker обрабатывает перед своими правилами accept.

Безопасно ли добавлять пользователя в группу docker?

Это равносильно предоставлению прав root. Участник группы docker может записывать данные в /var/run/docker.sock, а docker run --rm -v /:/host alpine wc -l /host/etc/shadow затем считывает файл, доступный только root, от имени пользователя без прав sudo. Документация Docker после установки подтверждает это соответствие. Добавляйте в группу только тех пользователей, которым вы уже доверяете права sudo, а для общих или сервисных учетных записей продолжайте использовать sudo docker. Если вам нужны контейнеры под непривилегированным пользователем, используйте Rootless mode — это отдельный способ установки, а не просто настройка.