Instalar Docker en Rocky Linux o AlmaLinux
Guía para instalar Docker Engine mediante dnf en Rocky Linux y AlmaLinux. Resuelve conflictos con el alias de podman y errores de SELinux en bind mounts que fallan por permisos.
Instalar Docker en Rocky Linux y AlmaLinux
Para instalar Docker en Rocky Linux o AlmaLinux, añada el repositorio dnf oficial de Docker, instale el motor junto con el plugin compose y habilite el servicio. Este proceso consta de cuatro comandos y es idéntico en ambas distribuciones, ya que ambas son reconstrucciones de Red Hat Enterprise Linux (RHEL) y comparten su estructura de paquetes. CentOS Stream funciona de la misma manera.
La instalación es breve, por lo que la mayor parte de esta guía cubre las diferencias entre Enterprise Linux (EL) y Ubuntu. Es posible que Podman ya utilice el comando docker en su imagen. SELinux bloquea los archivos montados mediante bind-mount hasta que tengan la etiqueta correcta. Firewalld no filtra los puertos publicados por Docker, por lo que un puerto de contenedor puede estar abierto a internet mientras firewall-cmd informa que no hay nada abierto.
No utilice el script de conveniencia de Docker desde get.docker.com. La propia documentación de Docker indica que no se recomienda para entornos de producción. Este script sobrescribe la configuración de sus repositorios sin solicitar confirmación y no puede ejecutarse de forma segura para realizar actualizaciones. Añadir el repositorio manualmente permite que dnf upgrade gestione Docker como cualquier otro paquete del sistema.
¿Está podman respondiendo ya al comando docker?
Rocky Linux y AlmaLinux incluyen podman en sus repositorios predeterminados, y muchas imágenes de VPS lo instalan automáticamente. Algunas imágenes van más allá e instalan podman-docker, que coloca un script de shell en /usr/bin/docker que invoca a podman. Cada comando docker que usted escribe ejecuta entonces podman, por lo que una guía escrita para Docker comienza a producir resultados que usted no esperaba.
La primera señal es un banner. El script /usr/bin/docker comprueba la existencia del archivo /etc/containers/nodocker, y cuando ese archivo falta, imprime una línea antes de ejecutar cualquier otra cosa:
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.Es posible que alguien haya creado ese archivo para silenciar el banner, así que no confíe solo en él. Pregunte a la base de datos de paquetes qué paquete es el propietario del binario:
command -v docker
rpm -qf "$(command -v docker)"Una respuesta que comienza con podman-docker significa que podman está respondiendo. Una respuesta que comienza con docker-ce-cli significa que es Docker real. Si rpm -qf informa que ningún paquete es propietario del archivo, alguien lo instaló manualmente y usted debería leer el script antes de confiar en él.
Podman ejecuta las mismas imágenes OCI y es una opción razonable. Si usted desea usarlo, deténgase aquí. Si desea Docker Engine, elimine primero los paquetes en conflicto. Esta es la lista que Docker documenta para RHEL:
sudo dnf remove docker docker-client docker-client-latest docker-common \
docker-latest docker-latest-logrotate docker-logrotate docker-engine podman runcLea lo que dnf planea eliminar antes de confirmar. En una imagen de VPS recién instalada, la lista es corta. En un servidor que alguien ya ha utilizado, eliminar podman puede arrastrar a cockpit-podman u otra herramienta que dependa de él.
Mantener podman junto a Docker es posible en principio: elimine solo podman-docker, para que el nombre docker quede libre, y runc, que el paquete containerd.io reemplaza. La documentación de Docker trata a podman como un paquete en conflicto, por lo que esta configuración no es compatible con Docker. Si la instalación sigue informando de un conflicto, utilice la lista de eliminación completa anterior.
Añadir el repositorio de Docker con dnf config-manager
Docker publica paquetes RPM para Enterprise Linux en download.docker.com. El archivo del repositorio apunta al árbol de CentOS, que es el que utilizan Rocky Linux y AlmaLinux para resolver dependencias. Verificado en agosto de 2026, Docker documenta este repositorio para CentOS Stream 9 y CentOS Stream 10.
sudo dnf -y install dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repoLa versión 5 de dnf eliminó el argumento --add-repo, por lo que ese segundo comando falla en las versiones más recientes. Compruebe cuál tiene instalada y elija la forma correspondiente:
dnf --versionSi muestra una versión 5.x, utilice la forma de subcomando en su lugar:
sudo dnf config-manager addrepo --from-repofile=https://download.docker.com/linux/centos/docker-ce.repoAmbos escriben el mismo archivo en /etc/yum.repos.d/docker-ce.repo. La forma incorrecta falla con un error de argumento desconocido en lugar de realizar una acción errónea de forma silenciosa, por lo que no pasará desapercibido.
Ese archivo de repositorio establece baseurl en una ruta que contiene $releasever, y dnf expande esa variable a partir de su paquete de versión. Rocky Linux y AlmaLinux lo establecen en el número de versión mayor, por lo que es 9 en EL 9 y 10 en EL 10, razón por la cual un repositorio de CentOS se resuelve correctamente en un equipo con Rocky. Confirme la expansión antes de instalar:
sudo dnf repoinfo docker-ce-stableLea la línea Repo-baseurl. Debería terminar en /9/x86_64/stable o /10/x86_64/stable. Si su versión establece $releasever en una versión menor como 9.6, dnf informará de Status code: 404 para esa URL al obtener los metadatos. Soluciónelo editando /etc/yum.repos.d/docker-ce.repo y reemplazando $releasever con el número mayor simple.
Instalación del motor y el plugin compose
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginCinco paquetes, cada uno con una función específica. docker-ce es el daemon, dockerd. docker-ce-cli es el comando docker que usted escribe. containerd.io es el tiempo de ejecución de contenedores que controla el daemon. docker-buildx-plugin construye imágenes. docker-compose-plugin proporciona docker compose como subcomando.
Estos paquetes no instalan un binario docker-compose con guion. Ese era Compose v1, que llegó al fin de su vida útil en julio de 2023. Cualquier proceso que llame a docker-compose con un guion debe actualizarse a docker compose con un espacio.
La primera instalación se detiene para importar la clave de firma de Docker y le muestra su huella digital. La clave proviene de gpgkey=https://download.docker.com/linux/centos/gpg en el archivo de repositorio que acaba de añadir, así que compare la huella digital que imprime dnf con esa URL antes de aceptarla.
Un fallo aparece con suficiente frecuencia como para mencionarlo. Si dnf informa que containerd.io requiere container-selinux y nada lo proporciona, su repositorio AppStream está deshabilitado. Ejecute dnf repolist y confirme que appstream aparece en la lista, ya que ahí es donde se distribuye container-selinux en EL 9 y EL 10.
Iniciar Docker y confirmar su ejecución
sudo systemctl enable --now docker
sudo systemctl status docker --no-pager
sudo docker run --rm hello-worldLos paquetes RPM de Docker dejan el daemon detenido y deshabilitado tras la instalación. Por este motivo, este paso aparece en la página de Docker para CentOS y no en la de Ubuntu, donde el paquete deb inicia el servicio automáticamente. Si omite enable, Docker funcionará hasta el próximo reinicio, momento en el que se detendrá y dejará inactivos todos los contenedores.
systemctl status debería mostrar Active: active (running). El contenedor hello-world debería imprimir This message shows that your installation appears to be working correctly. y finalizar. Si en su lugar imprime un error de permisos en /var/run/docker.sock, significa que omitió sudo, lo cual se soluciona en la sección sobre el grupo docker más adelante.
Compruebe el plugin compose por separado, ya que es un paquete distinto y podría faltar aunque el motor funcione correctamente:
docker compose versionUna respuesta correcta se parece a Docker Compose version v2.x.x. Recuperar sus servicios tras un reinicio es una cuestión distinta a habilitar el daemon, y las políticas de reinicio determinan si los servicios de Compose se inician al arrancar el sistema.
¿Por qué un montaje de tipo bind devuelve un error de permiso denegado?
Rocky Linux y AlmaLinux ejecutan SELinux (Security-Enhanced Linux) en modo enforcing de forma predeterminada. Confírmelo con getenforce, que imprime Enforcing.
Los contenedores de Docker se ejecutan bajo el tipo de SELinux container_t, y dicho tipo solo puede leer y escribir archivos etiquetados como container_file_t. Un directorio que usted creó en el host hereda la etiqueta de su ruta padre, la cual no es container_file_t. El contenedor recibe una denegación aunque el propietario, el grupo y los permisos parezcan correctos desde el host. Reproduzca el problema con tres comandos:
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.htmlEl contenedor imprime:
cat: can't open '/usr/share/nginx/html/index.html': Permission deniedDos comandos le mostrarán la causa. ls -ldZ /srv/site imprime la etiqueta, que para una ruta bajo /srv es system_u:object_r:var_t:s0, no container_file_t. Luego, sudo ausearch -m avc -ts recent imprime el registro de auditoría del kernel, que contiene avc: denied { read }, un campo scontext= que nombra a container_t, y un campo tcontext= que nombra la etiqueta que acaba de ver en el directorio. La discrepancia entre esos dos campos es toda la explicación.
La solución consiste en añadir un sufijo al argumento del volumen. Docker reetiqueta la ruta por usted:
sudo docker run --rm -v /srv/site:/usr/share/nginx/html:ro,z nginx:alpine cat /usr/share/nginx/html/index.html:z en minúsculas reetiqueta el contenido como compartido, para que varios contenedores puedan usar el mismo directorio. :Z en mayúsculas lo reetiqueta como privado y no compartido, vinculado a un solo contenedor; si un segundo contenedor intenta leer la misma ruta, será denegado. Use :z para cualquier cosa a la que también accedan un sidecar o un contenedor de respaldo. Use :Z para un directorio de base de datos que pertenezca a un único contenedor.
La documentación de Docker incluye una advertencia que merece ser repetida, ya que el reetiquetado es recursivo. Realizar un montaje de tipo bind de un directorio del sistema como /home o /usr con :Z "deja su máquina host inoperable y es posible que deba reetiquetar los archivos de la máquina host manualmente". Aplique estos sufijos a directorios que usted haya creado para el contenedor, nunca a una ruta del sistema.
En Compose, el sufijo se añade en la misma cadena:
services:
web:
image: nginx:alpine
volumes:
- /srv/site:/usr/share/nginx/html:ro,zEs fácil tropezar con dos límites. El flag --mount no puede establecer una etiqueta de SELinux en absoluto, así que use -v cuando necesite una. Los volúmenes con nombre no necesitan sufijo, porque Docker etiqueta por sí mismo los directorios que crea bajo /var/lib/docker/volumes.
No desactive SELinux. Use sudo setenforce 0 solo como una prueba de un minuto: si el contenedor funciona después, el problema es la etiqueta y :z es la solución. Vuelva a activarlo con sudo setenforce 1 inmediatamente. En Enterprise Linux, permission denied en un montaje de tipo bind tiene dos causas distintas que parecen idénticas desde dentro del contenedor. Una es la etiqueta de SELinux. La otra es la propiedad numérica de usuario y grupo, que es lo que las variables PUID y PGID existen para resolver. ls -lnZ le muestra el modo, el propietario numérico y la etiqueta en una sola línea, para que pueda identificar a qué se está enfrentando.
¿Por qué un puerto publicado es accesible cuando firewalld parece cerrado?
Firewalld es el firewall predeterminado en Rocky Linux y AlmaLinux. Compruebe que se está ejecutando con sudo systemctl is-active firewalld. Ahora, publique un puerto y observe qué considera abierto firewalld:
sudo docker run -d --name web -p 8080:80 nginx:alpine
sudo firewall-cmd --list-portsfirewall-cmd imprime una línea vacía. Desde otra máquina, curl -I http://YOUR_SERVER_IP:8080/ devuelve HTTP/1.1 200 OK. El puerto está abierto a Internet y su firewall no informa nada.
La causa es la ruta que sigue el paquete. Las reglas de zona de firewalld filtran el tráfico dirigido al propio host. Un puerto publicado no está dirigido al host: Docker instala una regla de NAT (traducción de direcciones de red) de destino que reescribe el destino a la dirección del contenedor antes de que el paquete llegue a la ruta de entrada del host, por lo que el kernel reenvía el paquete en lugar de entregarlo localmente. Docker coloca sus interfaces de puente en una zona de firewalld llamada docker cuyo objetivo es ACCEPT, y añade una política de reenvío llamada docker-forwarding que permite el reenvío desde cualquier zona hacia la zona docker. Las reglas de su zona nunca ven el paquete.
La solución más limpia no requiere ninguna regla de firewall. Enlace el lado del host de la publicación a la interfaz de bucle local (loopback) y coloque un proxy inverso delante:
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/El curl local devuelve HTTP/1.1 200 OK y la misma solicitud desde otra máquina ya no conecta. Cualquier cosa sin una dirección de host en el argumento -p se publica en todas las interfaces, así que trate un -p 8080:80 simple como una decisión de exponer ese servicio públicamente.
Cuando necesite que un servicio sea accesible desde algunas direcciones y no desde otras, Docker reserva una cadena para usted. DOCKER-USER se procesa antes que las reglas de aceptación propias de Docker, por lo que una regla que coloque allí sobrevive al reinicio de Docker y a la reescritura de sus cadenas:
sudo iptables -I DOCKER-USER -i enp1s0 ! -s 203.0.113.10 -j DROP
sudo iptables -S DOCKER-USERTome el nombre de la interfaz de ip route show default en lugar de asumir eth0, ya que las imágenes actuales de EL utilizan nombres como enp1s0 o ens3. En Rocky y AlmaLinux, el comando iptables es una capa de compatibilidad sobre nftables, y las cadenas de Docker son visibles a través de él. Las reglas añadidas de esta forma desaparecen tras un reinicio a menos que las guarde, así que escríbalas en una unidad de systemd una vez que esté satisfecho con ellas.
Docker Engine 28.0, lanzado en 2025, cerró un agujero similar: el acceso enrutado directo a puertos de contenedores que nunca fueron publicados ahora está bloqueado en la cadena DOCKER. Ese cambio no afecta a los puertos publicados, por lo que todo lo anterior sigue siendo válido en las versiones actuales. Vale la pena adoptar un hábito operativo: después de cualquier sudo firewall-cmd --reload, vuelva a probar un puerto publicado. Si dejó de responder, sudo systemctl restart docker reinstala las reglas de Docker.
Los administradores de Ubuntu se encuentran con el mismo muro a través de una herramienta diferente, lo cual se explica en por qué los puertos de Docker publicados ignoran las reglas de ufw. La ruta NAT es la causa en ambos casos. Solo cambia el firewall que está delante.
Añadir un usuario sin privilegios root al grupo docker
Escribir sudo antes de cada comando docker resulta tedioso, y el grupo docker elimina esa necesidad:
sudo usermod -aG docker $USER
newgrp docker
docker run --rm hello-worldusermod -aG edita /etc/group, pero su shell actual ya tiene su lista de grupos, por lo que el cambio no se aplica hasta que inicie una nueva sesión. newgrp docker inicia un shell con el grupo asignado para que pueda probarlo de inmediato. Las nuevas sesiones SSH lo detectan automáticamente.
Tenga claro lo que otorga ese grupo. La pertenencia proporciona acceso de escritura a /var/run/docker.sock, y cualquier proceso que pueda comunicarse con ese socket puede solicitar al daemon que inicie un contenedor que monte el sistema de archivos del host. Un comando muestra lo que esto significa:
docker run --rm -v /:/host alpine wc -l /host/etc/shadowEsto lee un archivo que solo root puede leer, desde una cuenta sin derechos de sudo. La propia documentación de post-instalación de Docker indica lo mismo: el grupo docker otorga privilegios equivalentes a root. Añada una cuenta a este grupo solo si también le otorgaría sudo. Si está configurando cuentas en un servidor nuevo, decida esto junto con el resto de su configuración de usuarios con privilegios mínimos en un VPS en lugar de hacerlo a posteriori.
Docker también incluye un modo rootless que ejecuta el daemon como un usuario sin privilegios. Es una ruta de instalación independiente y cambia el comportamiento de los controladores de almacenamiento y los puertos inferiores a 1024, así que planifíquelo como un proyecto propio en lugar de como una opción que se añade más tarde.
Pasos siguientes
Ya dispone del motor, el plugin compose, un servicio que persiste tras el reinicio y los tres comportamientos específicos de EL documentados anteriormente. El siguiente paso es un compose.yaml por servicio, y la anatomía de un archivo Compose cubre el formato de archivo y los comandos que lo gestionan. Si este es su primer host de contenedores, ejecutar Docker en un VPS cubre las cuestiones de dimensionamiento, almacenamiento e higiene de imágenes que esta guía omite.
FAQ
¿Funciona el repositorio de CentOS de Docker en Rocky Linux y AlmaLinux?
Sí. Añada https://download.docker.com/linux/centos/docker-ce.repo con dnf config-manager. El baseurl en ese archivo contiene $releasever, y Rocky Linux y AlmaLinux lo expanden al número de versión mayor; por tanto, un sistema EL 9 resuelve al árbol de CentOS 9 y uno EL 10 al de CentOS 10. Confirme la expansión con sudo dnf repoinfo docker-ce-stable y lea la línea Repo-baseurl. Un Status code: 404 al obtener metadatos con dnf significa que la variable se expandió a una versión menor, y editar /etc/yum.repos.d/docker-ce.repo para usar solo el número mayor lo soluciona.
¿Se pueden instalar Docker y podman en el mismo servidor?
La documentación de Docker lista podman y runc como paquetes en conflicto y recomienda eliminar ambos antes de instalar Docker Engine. El conflicto concreto es el paquete podman-docker, que posee /usr/bin/docker y convierte cada comando docker en un comando de podman. Ejecute rpm -qf "$(command -v docker)" para ver qué paquete posee esa ruta. Si la salida comienza con podman-docker, podman es quien responde. Mantener ambos motores no es una configuración soportada por Docker, así que en un servidor de producción, elija uno.
¿Por qué mi contenedor recibe un error de permiso denegado en un montaje bind?
SELinux está en modo enforcing por defecto en Rocky Linux y AlmaLinux. Los contenedores se ejecutan con el tipo container_t y solo pueden acceder a archivos etiquetados como container_file_t; por tanto, un directorio creado por usted tiene la etiqueta incorrecta y el acceso se deniega independientemente del propietario y los permisos. Confírmelo con ls -ldZ en la ruta del host y sudo ausearch -m avc -ts recent, que imprime avc: denied con los dos contextos que no coinciden. Añada :z al argumento del volumen para contenido compartido entre contenedores, o :Z para contenido privado de uno solo. Nunca apunte :Z a /home o /usr, ya que el reetiquetado es recursivo y dañará el host.
¿Necesito abrir un puerto en firewalld para publicar el puerto de un contenedor?
No, y ese es el problema. La regla NAT de Docker reescribe la dirección de destino antes de que el paquete llegue a la ruta de entrada del host, por lo que las reglas de zona de firewalld nunca lo inspeccionan. Docker también coloca sus bridges en una zona de firewalld llamada docker con destino ACCEPT. Un contenedor iniciado con -p 8080:80 es accesible desde internet mientras sudo firewall-cmd --list-ports no imprime nada. Publique en una dirección específica con -p 127.0.0.1:8080:80 cuando solo el host deba acceder al servicio, o inserte reglas de filtrado en la cadena DOCKER-USER, que Docker procesa antes que sus propias reglas de aceptación.
¿Es seguro añadir mi usuario al grupo docker?
Equivale a tener acceso root. Un miembro del grupo docker puede escribir en /var/run/docker.sock, y docker run --rm -v /:/host alpine wc -l /host/etc/shadow lee entonces un archivo restringido a root desde una cuenta sin privilegios de sudo. La documentación de post-instalación de Docker establece la misma equivalencia. Añada solo cuentas en las que ya confiaría con sudo, y siga usando sudo docker para cuentas compartidas o de servicio. El modo rootless es la alternativa cuando necesita contenedores bajo un usuario sin privilegios, y es una ruta de instalación independiente en lugar de un simple ajuste.