SSD Nodes Learn 🎉 VPS dès $5.50/mois
Guides Matt ConnorPar Matt Connor

Installer Docker sur Rocky Linux et AlmaLinux

Installez Docker Engine avec dnf et Compose, puis corrigez les pièges EL : Podman fournit déjà docker, et SELinux bloque les bind mounts sans relabel.

Installer Docker sur Rocky Linux et AlmaLinux

Pour installer Docker sur Rocky Linux ou AlmaLinux, ajoutez le dépôt dnf de Docker, installez le moteur avec le plugin Compose, puis activez le service. Cette installation se fait avec quatre commandes. Elle est identique sur les deux distributions, car elles sont toutes deux des reconstructions de Red Hat Enterprise Linux (RHEL) et utilisent la même organisation des paquets. CentOS Stream fonctionne de la même manière.

L’installation est courte. L’essentiel de ce guide porte donc sur les différences entre Enterprise Linux (EL) et Ubuntu. Podman peut déjà fournir la commande docker dans votre image. SELinux bloque les fichiers montés avec bind mount tant qu’ils ne portent pas le bon label. Firewalld ne filtre pas les ports publiés par Docker. Un port de conteneur peut donc être accessible depuis Internet alors que firewall-cmd indique qu’aucun port n’est ouvert.

N’utilisez pas le script d’installation pratique de Docker depuis get.docker.com. La documentation de Docker indique qu’il n’est pas recommandé en production. Il réécrit la configuration de vos dépôts sans demander confirmation et ne peut pas être réexécuté sans risque pour effectuer une mise à niveau. Ajouter le dépôt manuellement permet à dnf upgrade de gérer Docker comme n’importe quel autre paquet du système.

Podman répond-il déjà à la commande docker ?

Rocky Linux et AlmaLinux fournissent podman dans leurs dépôts par défaut, et de nombreuses images VPS l’installent automatiquement. Certaines images vont plus loin et installent podman-docker, qui place un script shell dans /usr/bin/docker et appelle podman. Chaque commande docker que vous saisissez exécute alors podman à la place. Un guide prévu pour Docker produit donc une sortie inattendue.

Le premier indice est une bannière. Le script /usr/bin/docker vérifie la présence du fichier /etc/containers/nodocker. Si ce fichier est absent, il affiche une ligne avant d’exécuter quoi que ce soit :

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

Quelqu’un a peut-être créé ce fichier pour supprimer la bannière. Ne vous fiez donc pas uniquement à ce contrôle. Demandez à la base de données des paquets quel paquet possède le binaire :

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

Une réponse qui commence par podman-docker signifie que podman répond à la commande. Une réponse qui commence par docker-ce-cli signifie qu’il s’agit du véritable Docker. Si rpm -qf indique qu’aucun paquet ne possède le fichier, quelqu’un l’a installé manuellement. Vous devez alors lire le script avant de lui faire confiance.

Podman exécute les mêmes images OCI et constitue un choix raisonnable. Si vous souhaitez l’utiliser, vous pouvez vous arrêter ici. Si vous voulez installer Docker Engine, supprimez d’abord les paquets en conflit. Voici la liste indiquée par la documentation Docker pour RHEL :

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

Lisez la liste des éléments que dnf prévoit de supprimer avant de confirmer. Sur une image VPS neuve, la liste est courte. Sur un serveur déjà utilisé, la suppression de podman peut également supprimer cockpit-podman ou un autre outil qui en dépend.

Il est en principe possible de conserver podman avec Docker : supprimez uniquement podman-docker, afin de libérer le nom docker, ainsi que runc, que le paquet containerd.io remplace. La documentation Docker considère podman comme un paquet en conflit. Cette configuration n’est donc pas prise en charge par Docker. Si l’installation signale encore un conflit, utilisez la liste complète de suppression ci-dessus.

Ajouter le dépôt Docker avec dnf config-manager

Docker publie des paquets RPM pour Enterprise Linux à l’adresse download.docker.com. Le fichier du dépôt pointe vers l’arborescence CentOS, utilisée par Rocky Linux et AlmaLinux. Vérifié en août 2026, la documentation Docker indique que ce dépôt est prévu pour CentOS Stream 9 et 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

La version 5 de dnf a supprimé l’argument --add-repo. La deuxième commande échoue donc avec les versions récentes. Vérifiez la version installée, puis utilisez la syntaxe correspondante :

dnf --version

Si la commande affiche une version 5.x, utilisez plutôt la forme avec sous-commande :

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

Les deux formes écrivent le même fichier dans /etc/yum.repos.d/docker-ce.repo. La mauvaise forme échoue avec une erreur d’argument inconnu. Elle ne produit pas silencieusement une configuration incorrecte.

Ce fichier de dépôt définit baseurl sur un chemin contenant $releasever. dnf développe cette variable à partir de votre paquet de version. Rocky Linux et AlmaLinux lui attribuent le numéro de version majeure. Vous obtenez donc 9 avec EL 9 et 10 avec EL 10. C’est pourquoi un dépôt CentOS fonctionne correctement sur un système Rocky. Vérifiez le développement de la variable avant l’installation :

sudo dnf repoinfo docker-ce-stable

Lisez la ligne Repo-baseurl. Elle doit se terminer par /9/x86_64/stable ou /10/x86_64/stable. Si votre version définit $releasever sur une version intermédiaire telle que 9.6, dnf signale Status code: 404 pour cette URL lors de la récupération des métadonnées. Corrigez ce problème en modifiant /etc/yum.repos.d/docker-ce.repo et en remplaçant $releasever par le seul numéro de version majeure.

Installer le moteur et le plugin Compose

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

Cinq paquets, chacun avec un rôle précis. docker-ce est le daemon, dockerd. docker-ce-cli est la commande docker que vous saisissez. containerd.io est le runtime de conteneurs piloté par le daemon. docker-buildx-plugin construit les images. docker-compose-plugin fournit docker compose comme sous-commande.

Ces paquets n’installent pas de binaire docker-compose avec un trait d’union. Il s’agissait de Compose v1, arrivé en fin de vie en juillet 2023. Toute commande qui appelle docker-compose avec un trait d’union doit être remplacée par docker compose avec un espace.

La première installation s’interrompt pour importer la clé de signature de Docker et en affiche l’empreinte. La clé provient de gpgkey=https://download.docker.com/linux/centos/gpg dans le fichier du dépôt que vous venez d’ajouter. Comparez donc l’empreinte affichée par dnf avec cette URL avant de l’accepter.

Une erreur revient assez souvent pour mériter d’être signalée. Si dnf indique que containerd.io nécessite container-selinux et qu’aucun paquet ne le fournit, votre dépôt AppStream est désactivé. Exécutez dnf repolist et vérifiez que appstream est répertorié, car c’est là que container-selinux est publié sur EL 9 et EL 10.

Démarrer Docker et vérifier qu’il fonctionne

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

Les paquets RPM de Docker laissent le daemon arrêté et désactivé après l’installation. C’est pourquoi cette étape apparaît sur la page CentOS de Docker, mais pas sur sa page Ubuntu, où le paquet deb démarre le service automatiquement. Si vous ignorez enable, Docker fonctionne jusqu’au prochain redémarrage, puis reste arrêté et arrête tous les conteneurs avec lui.

systemctl status doit afficher Active: active (running). Le conteneur hello-world doit afficher This message shows that your installation appears to be working correctly., puis se terminer. S’il affiche plutôt une erreur de permission sur /var/run/docker.sock, vous avez omis sudo. La section ci-dessous consacrée au groupe docker corrige ce problème.

Vérifiez séparément le plugin Compose, car il s’agit d’un autre paquet. Il peut être absent alors que le moteur fonctionne :

docker compose version

Une réponse correcte ressemble à Docker Compose version v2.x.x. Récupérer vos services après un redémarrage est une question distincte de l’activation du daemon, et les restart policies déterminent si les services Compose redémarrent au boot.

Pourquoi un bind mount renvoie-t-il « permission denied » ?

Rocky Linux et AlmaLinux exécutent SELinux (Security-Enhanced Linux) en mode enforcing par défaut. Vérifiez-le avec getenforce, qui affiche Enforcing.

Les conteneurs Docker s’exécutent sous le type SELinux container_t. Ce type peut uniquement lire et écrire les fichiers portant le label container_file_t. Un répertoire créé sur l’hôte reçoit le label de son chemin parent, qui n’est pas container_file_t. Le conteneur est donc bloqué, même si le propriétaire, le groupe et les permissions semblent corrects depuis l’hôte. Reproduisez le problème avec trois commandes :

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

Le conteneur affiche :

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

Deux commandes permettent d’en trouver la cause. ls -ldZ /srv/site affiche le label. Pour un chemin sous /srv, il s’agit de system_u:object_r:var_t:s0, et non de container_file_t. Ensuite, sudo ausearch -m avc -ts recent affiche l’enregistrement d’audit du noyau. Celui-ci contient avc: denied { read }, un champ scontext= qui indique container_t, ainsi qu’un champ tcontext= qui indique le label que vous venez de voir sur le répertoire. Le décalage entre ces deux champs explique entièrement le problème.

La correction consiste à ajouter un suffixe à l’argument du volume. Docker applique le bon label au chemin :

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

:z en minuscules applique un label shared au contenu, afin que plusieurs conteneurs puissent utiliser le même répertoire. :Z en majuscules lui applique un label private et unshared, associé à un seul conteneur. Un second conteneur qui lit le même chemin est alors bloqué. Utilisez :z pour tout répertoire également utilisé par un sidecar ou un conteneur de sauvegarde. Utilisez :Z pour un répertoire de base de données appartenant à un seul conteneur.

La documentation Docker contient un avertissement important, car le changement de label est récursif. Monter un répertoire système tel que /home ou /usr avec :Z « rend votre machine hôte inutilisable et vous devrez peut-être réappliquer manuellement les labels aux fichiers de la machine hôte ». Utilisez ces suffixes uniquement avec les répertoires que vous avez créés pour le conteneur, jamais avec un chemin système.

Dans Compose, le suffixe s’ajoute à la même chaîne :

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

Deux limites sont faciles à rencontrer. L’option --mount ne peut pas définir de label SELinux. Utilisez donc -v lorsque vous en avez besoin. Les volumes nommés ne nécessitent aucun suffixe, car Docker applique lui-même les labels aux répertoires qu’il crée sous /var/lib/docker/volumes.

Ne désactivez pas SELinux. Utilisez sudo setenforce 0 uniquement comme test d’une minute : si le conteneur fonctionne alors, le problème vient d’un label et :z est la solution. Réactivez-le immédiatement avec sudo setenforce 1. Sur Enterprise Linux, permission denied sur un bind mount peut avoir deux causes distinctes qui semblent identiques depuis l’intérieur du conteneur. La première est le label SELinux. La seconde est la propriété numérique habituelle de l’utilisateur et du groupe, problème que les variables PUID et PGID permettent de résoudre. ls -lnZ affiche le mode, le propriétaire numérique et le label sur une seule ligne. Vous pouvez ainsi déterminer lequel des deux problèmes vous devez corriger.

Pourquoi un port publié reste-t-il accessible alors que firewalld semble fermé ?

Firewalld est le pare-feu par défaut de Rocky Linux et AlmaLinux. Vérifiez qu’il fonctionne avec sudo systemctl is-active firewalld. Publiez ensuite un port et vérifiez ce que firewalld considère comme ouvert :

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

firewall-cmd affiche une ligne vide. Depuis une autre machine, curl -I http://YOUR_SERVER_IP:8080/ renvoie HTTP/1.1 200 OK. Le port est ouvert sur Internet, alors que votre pare-feu ne signale rien.

La cause vient du chemin suivi par le paquet. Les règles de zone de firewalld filtrent le trafic destiné à l’hôte lui-même. Un port publié n’est pas destiné à l’hôte : Docker installe une règle de destination NAT (traduction d’adresse réseau) qui réécrit la destination vers l’adresse du conteneur avant que le paquet n’atteigne le chemin d’entrée de l’hôte. Le kernel transmet donc le paquet au lieu de le livrer localement. Docker place ensuite ses interfaces bridge dans une zone firewalld appelée docker, dont la cible est ACCEPT, et ajoute une politique de forwarding appelée docker-forwarding qui autorise le forwarding depuis n’importe quelle zone vers la zone docker. Vos règles de zone ne voient jamais le paquet.

La correction la plus propre ne nécessite aucune règle de pare-feu. Liez le côté hôte de la publication à loopback et placez un reverse proxy devant :

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/

La commande locale curl renvoie HTTP/1.1 200 OK, et la même requête depuis une autre machine ne se connecte plus. Si aucune adresse hôte n’est indiquée dans l’argument -p, le port est publié sur toutes les interfaces. Considérez donc un simple -p 8080:80 comme une décision d’exposer publiquement le service.

Lorsqu’un service doit être accessible depuis certaines adresses mais pas depuis d’autres, Docker vous réserve une chain. DOCKER-USER est traitée avant les propres règles d’acceptation de Docker. Une règle ajoutée à cet endroit reste donc présente lorsque Docker redémarre et réécrit ses chains :

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

Récupérez le nom de l’interface avec ip route show default au lieu de supposer eth0, car les versions actuelles des images EL utilisent des noms comme enp1s0 ou ens3. Sur Rocky et AlmaLinux, la commande iptables est une couche de compatibilité au-dessus de nftables, et les chains de Docker y sont visibles. Les règles ajoutées de cette manière disparaissent après un redémarrage si vous ne les enregistrez pas. Ajoutez-les donc à une unité systemd lorsque leur configuration vous convient.

Docker Engine 28.0, publié en 2025, a corrigé un problème voisin : l’accès routé direct aux ports de conteneurs qui n’étaient jamais publiés est maintenant bloqué dans la chain DOCKER. Cette modification ne concerne pas les ports publiés. Tout ce qui précède s’applique donc toujours aux versions actuelles. Prenez une habitude opérationnelle : après chaque sudo firewall-cmd --reload, testez à nouveau un port publié. S’il ne répond plus, sudo systemctl restart docker réinstalle les règles de Docker.

Les administrateurs Ubuntu rencontrent le même problème avec un autre outil : pourquoi les ports Docker publiés ignorent les règles ufw. Dans les deux cas, la cause est le chemin NAT. Seul le pare-feu placé devant ce chemin change.

Ajouter un utilisateur non-root au groupe docker

Saisir sudo avant chaque commande docker devient vite contraignant. Le groupe docker évite cette répétition :

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

usermod -aG modifie /etc/group, mais votre shell actuel utilise déjà sa liste de groupes. La modification ne s’applique donc qu’après l’ouverture d’un nouveau shell. newgrp docker ouvre un shell avec le groupe ajouté, afin que vous puissiez tester immédiatement. Les nouvelles sessions SSH le prennent automatiquement en compte.

Vous devez bien comprendre les droits accordés par ce groupe. En être membre donne un accès en écriture à /var/run/docker.sock. Tout ce qui peut communiquer avec ce socket peut demander au daemon de démarrer un conteneur qui monte le système de fichiers de l’hôte. Une commande suffit à montrer ce que cela implique :

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

Cette commande lit un fichier que seul root peut lire, depuis un compte qui ne dispose d’aucun droit sudo. La documentation post-installation de Docker l’indique également : le groupe docker accorde des privilèges équivalents à ceux de root. Ajoutez-y un compte uniquement si vous accorderiez aussi sudo à ce compte. Si vous configurez des comptes sur un nouveau serveur, prenez cette décision en même temps que le reste de votre configuration d’utilisateurs avec le principe du moindre privilège sur un VPS, et non après coup.

Docker propose également un mode rootless, qui exécute le daemon avec un utilisateur non privilégié. Il s’agit d’une procédure d’installation distincte. Ce mode modifie aussi le comportement des storage drivers et des ports inférieurs à 1024. Traitez donc cette configuration comme un projet à part, et non comme une option à ajouter ultérieurement.

À suivre

Vous disposez maintenant du moteur, du plugin Compose, d’un service qui redémarre après un reboot et des trois comportements spécifiques à EL documentés ci-dessus. L’étape suivante consiste à créer un compose.yaml par service. L’anatomie d’un fichier Compose présente le format du fichier et les commandes qui le pilotent. S’il s’agit de votre premier hôte de conteneurs, Exécuter Docker sur un VPS traite les questions de dimensionnement, de stockage et d’hygiène des images qui ne sont pas abordées dans ce guide.

FAQ

Le dépôt CentOS de Docker fonctionne-t-il sur Rocky Linux et AlmaLinux ?

Oui. Ajoutez https://download.docker.com/linux/centos/docker-ce.repo avec dnf config-manager. Le baseurl de ce fichier contient $releasever, et Rocky Linux et AlmaLinux le remplacent par le numéro de version majeur. Ainsi, un système EL 9 utilise l’arborescence CentOS 9 et un système EL 10 l’arborescence CentOS 10. Vérifiez ce remplacement avec sudo dnf repoinfo docker-ce-stable et lisez la ligne Repo-baseurl. Un Status code: 404 lors de la récupération des métadonnées par dnf signifie que la variable a été remplacée par une version mineure. Modifiez /etc/yum.repos.d/docker-ce.repo pour utiliser uniquement le numéro de version majeur.

Docker et podman peuvent-ils être installés sur le même serveur ?

La documentation de Docker indique que podman et runc sont des paquets incompatibles et demande de les supprimer tous les deux avant d’installer Docker Engine. Le conflit concret concerne le paquet podman-docker, qui possède /usr/bin/docker et transforme chaque commande docker en commande podman. Exécutez rpm -qf "$(command -v docker)" pour voir quel paquet possède ce chemin. Si la sortie commence par podman-docker, c’est podman qui répond. Docker ne prend pas en charge la coexistence des deux moteurs. Sur un serveur où ce point est important, choisissez-en un seul.

Pourquoi mon conteneur reçoit-il une erreur « permission denied » sur un bind mount ?

SELinux est en mode enforcing par défaut sur Rocky Linux et AlmaLinux. Les conteneurs s’exécutent avec le type container_t et peuvent uniquement accéder aux fichiers étiquetés container_file_t. Un répertoire que vous avez créé possède donc le mauvais label, et l’accès est refusé quel que soit son propriétaire ou son mode. Vérifiez-le avec ls -ldZ sur le chemin hôte et sudo ausearch -m avc -ts recent, qui affiche avc: denied avec les deux contextes différents. Ajoutez :z à l’argument du volume pour le contenu partagé entre conteneurs, ou :Z pour le contenu privé d’un seul conteneur. Ne pointez jamais :Z vers /home ou /usr, car le relabel est récursif et endommagera l’hôte.

Dois-je ouvrir un port dans firewalld pour publier le port d’un conteneur ?

Non, et c’est justement le problème. La règle NAT de Docker réécrit l’adresse de destination avant que le paquet n’atteigne le chemin d’entrée de l’hôte. Les règles de zone de firewalld ne l’inspectent donc jamais. Docker place également ses bridges dans une zone firewalld appelée docker, avec la cible ACCEPT. Un conteneur démarré avec -p 8080:80 est accessible depuis Internet, tandis que sudo firewall-cmd --list-ports n’affiche rien. Publiez le service sur une adresse précise avec -p 127.0.0.1:8080:80 lorsque seul l’hôte doit y accéder, ou ajoutez les règles de filtrage dans la chaîne DOCKER-USER, que Docker traite avant ses propres règles d’acceptation.

Ajouter mon utilisateur au groupe docker est-il sûr ?

Cela donne les privilèges root. Un membre du groupe docker peut écrire dans /var/run/docker.sock, puis docker run --rm -v /:/host alpine wc -l /host/etc/shadow peut lire un fichier réservé à root depuis un compte qui ne possède aucun droit sudo. La documentation post-installation de Docker indique la même équivalence. N’ajoutez que les comptes auxquels vous accorderiez déjà sudo, et continuez à utiliser sudo docker pour les comptes partagés ou de service. Le mode rootless est l’alternative lorsque vous devez exécuter des conteneurs avec un utilisateur non privilégié. Il s’agit d’un parcours d’installation distinct, et non d’un simple paramètre.