SSD Nodes Learn Hosting plans →
Guías Matt ConnorPor Matt Connor · Actualizado 2026-09-04

Instalar Docker en Rocky Linux y AlmaLinux

Instala Docker Engine con dnf en Rocky Linux y AlmaLinux y corrige dos fallos habituales: Podman ocupa docker y SELinux bloquea los bind mounts.

Instalar Docker en Rocky Linux y AlmaLinux

Para instalar Docker en Rocky Linux o AlmaLinux, añada el repositorio dnf de Docker, instale el motor con el complemento de Compose y habilite el servicio. Esta parte requiere cuatro comandos y es idéntica en ambas distribuciones porque las dos son reconstrucciones de Red Hat Enterprise Linux (RHEL) y comparten su organización de paquetes. CentOS Stream funciona de la misma forma. Todo lo que aparece a continuación se aplica a ambas, así que, si todavía está eligiendo entre ellas, los factores decisivos son la garantía de compatibilidad que ofrece cada proyecto y si su CPU antigua sigue teniendo soporte.

La instalación es breve, por lo que la mayor parte de esta guía explica qué hace Enterprise Linux (EL) de forma diferente a Ubuntu. Podman puede tener ya asignado el comando docker en su imagen. SELinux bloquea los archivos montados mediante bind hasta que tienen 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 indica que no hay ningún puerto abierto.

No use el script de conveniencia de Docker disponible en get.docker.com. La documentación de Docker indica que no se recomienda para producción. Reescribe la configuración del repositorio sin solicitar confirmación y no se puede volver a ejecutar de forma segura para actualizar. Añadir el repositorio manualmente hace que dnf upgrade trate Docker como cualquier otro paquete del sistema. Esto también incluye el motor en el ámbito de dnf-automatic, si lo tiene configurado para aplicar actualizaciones de seguridad mediante un temporizador, así que decida pronto si quiere que Docker se actualice sin intervención o que permanezca bloqueado hasta una ventana de mantenimiento. En cualquier caso, una actualización sustituye el binario del paquete mientras el dockerd anterior sigue ejecutándose, y needs-restarting es el comando que indica qué servicios todavía ejecutan el código que acaba de sustituir.

¿podman ya está respondiendo 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 y llama a podman. Por tanto, cada comando docker que escriba ejecuta podman. Una guía escrita para Docker empezará a mostrar resultados inesperados.

La primera señal es un aviso. El script /usr/bin/docker comprueba si existe el archivo /etc/containers/nodocker. Si falta, muestra una línea antes de ejecutar cualquier acción:

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

Es posible que alguien haya creado ese archivo para ocultar el aviso. Por eso, no debe basarse sólo en él. Consulte en la base de datos de paquetes qué paquete es el propietario del binario:

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

Una respuesta que empiece por podman-docker significa que podman está respondiendo. Una respuesta que empiece por docker-ce-cli significa que se trata de Docker real. Si rpm -qf informa de que ningún paquete es propietario del archivo, alguien lo instaló manualmente. En ese caso, lea el script antes de confiar en él.

Podman ejecuta las mismas imágenes OCI y es una opción válida. Si quiere usarlo, deténgase aquí. Ambos son motores de contenedores Linux. Si la decisión sobre la plataforma sigue abierta, conviene saber que las jaulas de FreeBSD aíslan un userland completo en lugar de ejecutar imágenes por capas descargadas de un registro. Si quiere 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 runc

Lea lo que dnf planea eliminar antes de confirmar. En una imagen de VPS recién instalada, la lista es corta. En un sistema que alguien ya haya utilizado, eliminar podman puede retirar cockpit-podman u otra herramienta que dependa de él.

En principio, es posible mantener podman junto con Docker: elimine sólo podman-docker, para liberar el nombre docker, y runc, que el paquete containerd.io reemplaza. La documentación de Docker considera podman un paquete en conflicto, por lo que Docker no admite esta disposición. Si la instalación sigue informando de un conflicto, use la lista completa de eliminación 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. Apuntar un sistema Rocky a un repositorio de CentOS parece un error hasta que se conoce cómo ambas distribuciones surgieron de la línea de CentOS después de que Red Hat convirtiera CentOS en Stream en 2020. 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.repo

La versión 5 de dnf eliminó el argumento --add-repo, por lo que el segundo comando falla en las versiones más recientes. Compruebe cuál tiene y use la forma correspondiente:

dnf --version

Si muestra una versión 5.x, use la forma con subcomando:

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

Ambas formas 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 producir un resultado incorrecto de forma silenciosa, por lo que lo detectará.

Ese archivo del repositorio establece baseurl en una ruta que contiene $releasever, y dnf expande esa variable a partir del paquete de versión instalado. Rocky Linux y AlmaLinux la establecen en el número de versión principal: 9 en EL 9 y 10 en EL 10. Por eso un repositorio de CentOS se resuelve correctamente en un sistema Rocky. Confirme la expansión antes de instalar:

sudo dnf repoinfo docker-ce-stable

Lea la línea Repo-baseurl. Debe terminar en /9/x86_64/stable o /10/x86_64/stable. Si la versión instalada establece $releasever en una versión específica, como 9.6, dnf informa Status code: 404 para esa URL al descargar los metadatos. Corríjalo editando /etc/yum.repos.d/docker-ce.repo y reemplazando $releasever por el número de versión principal sin más.

Instalar el motor y el complemento de Compose

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

Cinco paquetes, y cada uno cumple una función. docker-ce es el daemon, dockerd. docker-ce-cli es el comando docker que se escribe. containerd.io es el entorno de ejecución de contenedores que controla el daemon. docker-buildx-plugin crea imágenes. docker-compose-plugin proporciona docker compose como subcomando.

Estos paquetes no instalan un binario docker-compose con guion. Ese era Compose v1, cuyo ciclo de vida terminó en julio de 2023. Todo lo que invoque 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 muestra su huella digital. La clave procede de gpgkey=https://download.docker.com/linux/centos/gpg en el archivo del repositorio que acaba de añadir. Compare la huella que muestra dnf con esa URL antes de aceptarla.

Hay un fallo que aparece con suficiente frecuencia como para mencionarlo. Si dnf informa de que containerd.io requiere container-selinux y ningún paquete lo proporciona, el repositorio AppStream está deshabilitado. Ejecute dnf repolist y confirme que aparece appstream, porque ahí se publica container-selinux en EL 9 y EL 10.

Inicie Docker y confirme que funciona

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

Los paquetes RPM de Docker dejan el daemon detenido y deshabilitado después de la instalación. Por eso 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. Omita enable y Docker funcionará hasta el siguiente reinicio. Después permanecerá detenido y dejará todos los contenedores fuera de servicio.

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 muestra un error de permisos en /var/run/docker.sock, omitió sudo. La sección sobre el grupo docker, más abajo, corrige este problema.

Compruebe el plugin de Compose por separado. Es un paquete distinto y puede faltar aunque el motor funcione correctamente:

docker compose version

Una respuesta correcta tiene este aspecto: Docker Compose version v2.x.x. Recuperar los servicios después de un reinicio es una cuestión distinta de habilitar el daemon, y las políticas de reinicio determinan si los servicios de Compose vuelven a iniciarse durante el arranque.

¿Por qué un bind mount devuelve «permission denied»?

Rocky Linux y AlmaLinux ejecutan SELinux (Security-Enhanced Linux) en modo enforcing de forma predeterminada. Confírmelo con getenforce, que muestra Enforcing.

Los contenedores Docker se ejecutan con el tipo SELinux container_t, y ese tipo sólo puede leer y escribir archivos etiquetados como container_file_t. Un directorio que cree en el host conserva la etiqueta que le asigna su ruta padre, que no es container_file_t. El contenedor deniega el acceso aunque el propietario, el grupo y los permisos parezcan correctos desde el host. Reprodúzcalo 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.html

El contenedor muestra:

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

Dos comandos muestran la causa. ls -ldZ /srv/site muestra la etiqueta, que para una ruta bajo /srv es system_u:object_r:var_t:s0, no container_file_t. Después, sudo ausearch -m avc -ts recent muestra el registro de auditoría del kernel, que contiene avc: denied { read }, un campo scontext= que identifica container_t y un campo tcontext= que identifica la etiqueta que acaba de ver en el directorio. La discrepancia entre esos dos campos explica todo el problema.

La solución es añadir un sufijo al argumento del volumen. Docker cambia la etiqueta de la ruta automáticamente:

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 etiqueta el contenido como compartido, de modo que varios contenedores pueden usar el mismo directorio. :Z en mayúsculas lo etiqueta como privado y no compartido, vinculado a un solo contenedor; otro contenedor que lea la misma ruta recibirá una denegación. Use :z para cualquier directorio que también utilicen un sidecar o un contenedor de backup. Use :Z para un directorio de base de datos cuyo propietario sea un solo contenedor.

La documentación de Docker incluye una advertencia que conviene repetir porque el cambio de etiqueta es recursivo. Montar mediante bind un directorio del sistema como /home o /usr con :Z «deja el equipo host inoperativo y puede ser necesario cambiar manualmente las etiquetas de los archivos del host». Apunte estos sufijos a directorios que haya creado para el contenedor, nunca a una ruta del sistema.

En Compose, el sufijo se añade a la misma cadena:

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

Es fácil encontrarse con dos limitaciones. El indicador --mount no puede establecer una etiqueta SELinux, por lo que debe usar -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 sólo como prueba de un minuto: si el contenedor funciona entonces, el problema es una etiqueta y :z es la solución. Restáurelo inmediatamente con sudo setenforce 1. En Enterprise Linux, permission denied en un bind mount tiene dos causas independientes que desde dentro del contenedor parecen idénticas. Una es la etiqueta SELinux. La otra es la propiedad numérica habitual del usuario y del grupo, que es lo que las variables PUID y PGID permiten resolver. ls -lnZ muestra los permisos, el propietario numérico y la etiqueta en una sola línea, para que pueda determinar cuál de los dos problemas está corrigiendo.

¿Por qué se puede acceder a un puerto publicado cuando firewalld parece cerrado?

Firewalld es el firewall predeterminado de Rocky Linux y AlmaLinux. Compruebe que esté en ejecución con sudo systemctl is-active firewalld. Si todavía no lo ha configurado en este equipo, primero debe abrir SSH y un puerto web con firewalld, porque la situación siguiente sólo tiene sentido cuando existe un conjunto de reglas de zona operativo con el que comparar. Publique ahora un puerto y compruebe qué considera abierto firewalld:

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

firewall-cmd imprime una línea vacía. Desde otro equipo, curl -I http://YOUR_SERVER_IP:8080/ devuelve HTTP/1.1 200 OK. El puerto está abierto a Internet y el firewall no muestra ninguna regla.

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 de destino (traducción de direcciones de red) que cambia 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. Después, Docker coloca sus interfaces bridge en una zona de firewalld llamada docker cuyo destino es ACCEPT, y añade una política de reenvío llamada docker-forwarding que permite reenviar tráfico desde cualquier zona hacia la zona docker. Las reglas de su zona nunca ven el paquete.

La solución más limpia no necesita ninguna regla del firewall. Enlace el lado del host de la publicación a 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 comando local curl devuelve HTTP/1.1 200 OK y la misma petición desde otro equipo deja de conectarse. Todo lo que no tenga una dirección del host en el argumento -p se publica en todas las interfaces, por lo que debe tratar un -p 8080:80 sin dirección como una decisión de exponer públicamente ese servicio.

Cuando necesite que un servicio sea accesible desde algunas direcciones y no desde otras, Docker reserva una cadena para este fin. DOCKER-USER se procesa antes que las reglas propias de aceptación de Docker, por lo que una regla colocada allí persiste aunque Docker se reinicie y vuelva a escribir sus cadenas:

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

Obtenga el nombre de la interfaz con ip route show default en lugar de suponer eth0, porque las imágenes actuales de EL usan 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 mediante él. Las reglas añadidas de esta forma desaparecen después de un reinicio si no las guarda, así que escríbalas en una unidad de systemd cuando haya comprobado que funcionan.

Docker Engine 28.0, publicado en 2025, cerró una vulnerabilidad relacionada: ahora se bloquea el acceso enrutado directo a los puertos de contenedor que nunca se publicaron en la cadena DOCKER. Este cambio no afecta a los puertos publicados, por lo que todo lo anterior sigue siendo aplicable en las versiones actuales. Conviene 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 vuelve a instalar las reglas de Docker.

Los administradores de Ubuntu encuentran el mismo problema mediante otra herramienta, como se explica en por qué los puertos publicados de Docker ignoran las reglas de ufw. En ambos casos, la causa es la ruta de NAT. Sólo cambia el firewall que se encuentra delante.

Añadir un usuario que no sea root al grupo docker

Escribir sudo antes de cada comando docker resulta tedioso. El grupo docker evita tener que hacerlo:

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

usermod -aG edita /etc/group, pero el shell actual ya tiene su lista de grupos. Por eso el cambio no se aplica hasta iniciar una nueva sesión. newgrp docker inicia un shell con el grupo añadido para que pueda probarlo de inmediato. Las nuevas sesiones SSH lo incorporan automáticamente.

Tenga claro qué permisos concede ese grupo. Pertenecer al grupo proporciona acceso de escritura a /var/run/docker.sock. Cualquier proceso que pueda comunicarse con ese socket puede pedir al daemon que inicie un contenedor con el sistema de archivos del host montado. Un comando muestra lo que esto implica:

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

Ese comando lee un archivo que sólo root puede leer desde una cuenta sin permisos de sudo. La documentación oficial de Docker posterior a la instalación indica lo mismo: el grupo docker concede privilegios equivalentes a root. Añada una cuenta al grupo sólo si también le concederí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, no después.

Docker también ofrece 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 de los puertos inferiores a 1024. Planifíquelo como un proyecto independiente, no como una opción que se añade más adelante.

Siguientes pasos

Ya tiene el motor, el plugin de Compose, un servicio que sigue funcionando después de reiniciar y los tres comportamientos específicos de EL documentados anteriormente. El siguiente paso es una compose.yaml por servicio. La estructura de un archivo de Compose explica el formato del archivo y los comandos que lo administran. Si este es su primer host de contenedores, ejecutar Docker en un VPS explica las cuestiones de dimensionamiento, almacenamiento e higiene de imágenes que esta guía no aborda.

FAQ

¿Funciona el repositorio de Docker para CentOS en Rocky Linux y AlmaLinux?

Sí. Añada https://download.docker.com/linux/centos/docker-ce.repo con dnf config-manager. El baseurl de ese archivo contiene $releasever, y Rocky Linux y AlmaLinux lo expanden al número de la versión principal. Por tanto, un sistema EL 9 resuelve el árbol de CentOS 9 y un sistema EL 10 resuelve el árbol 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 cuando dnf obtiene los metadatos indica que la variable se expandió a una versión menor. Edite /etc/yum.repos.d/docker-ce.repo para usar sólo el número de la versión principal.

¿Se pueden instalar Docker y podman en el mismo servidor?

La documentación de Docker indica que podman y runc son paquetes en conflicto y ordena eliminar ambos antes de instalar Docker Engine. El conflicto concreto es el paquete podman-docker, que es propietario de /usr/bin/docker y convierte cada comando docker en un comando de podman. Ejecute rpm -qf "$(command -v docker)" para comprobar qué paquete es propietario de esa ruta. Si la salida empieza por podman-docker, podman está respondiendo. Mantener ambos motores no es una configuración compatible con Docker. En un servidor importante, elija uno.

¿Por qué mi contenedor recibe un error de permiso denegado en un bind mount?

SELinux está en modo enforcing de forma predeterminada en Rocky Linux y AlmaLinux. Los contenedores se ejecutan con el tipo container_t y sólo pueden acceder a archivos etiquetados como container_file_t. Por tanto, un directorio que haya creado tiene la etiqueta incorrecta y el acceso se deniega, independientemente de su propietario y sus permisos. Confírmelo con ls -ldZ sobre la ruta del host y con sudo ausearch -m avc -ts recent, que muestra avc: denied con los dos contextos diferentes. 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, porque el cambio de etiquetas es recursivo y dañará el host.

¿Tengo que abrir un puerto en firewalld para publicar un puerto del 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 tanto, las reglas de zona de firewalld nunca lo inspeccionan. Docker también coloca sus bridges en una zona de firewalld llamada docker, con el destino ACCEPT. Un contenedor iniciado con -p 8080:80 es accesible desde Internet, mientras sudo firewall-cmd --list-ports no muestra ninguna salida. Publique en una dirección específica con -p 127.0.0.1:8080:80 cuando sólo el host deba acceder al servicio, o inserte reglas de filtrado en la cadena DOCKER-USER, que Docker procesa antes de sus propias reglas de aceptación.

¿Es seguro añadir mi usuario al grupo docker?

Concede 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 puede leer después un archivo reservado para root desde una cuenta sin permisos de sudo. La documentación de Docker posterior a la instalación indica la misma equivalencia. Añada únicamente cuentas a las que ya confiaría sudo y siga usando sudo docker para cuentas compartidas o de servicio. El modo rootless es la alternativa cuando necesita ejecutar contenedores con un usuario sin privilegios. Es una ruta de instalación independiente, no un ajuste.