Cómo asegurar Docker en una máquina Linux
El acceso al grupo Docker otorga privilegios de root. Proteja Docker en Linux con el modo sin privilegios, reglas sudo, Podman, contenedores reforzados y una lista de auditoría.
Añadir un usuario al grupo docker le otorga acceso root a la máquina. No es “casi root”, sino root completo, porque cualquiera que pueda comunicarse con el socket de Docker puede montar el sistema de archivos del host en un contenedor y modificarlo.
Ejecutaste usermod -aG docker $USER para dejar de escribir sudo, y la propia guía de post-instalación de Docker te indicó que lo hicieras. La misma página de post-instalación de Docker Engine en Linux también advierte, en un recuadro que la mayoría pasa por alto, que pertenecer a este grupo equivale a ser root. Si todavía te estás familiarizando con los conceptos básicos, la guía para principiantes sobre imágenes y contenedores de Docker los explica. Este artículo demuestra el riesgo con un solo comando. Después repasa las soluciones, ordenadas según el nivel de protección que ofrecen, y termina con una checklist para auditar una máquina heredada.
Puntos clave
- Cualquier miembro del grupo
docker, y cualquier proceso que se ejecute como ese usuario, puede obtener una shell de root en el host condocker run --rm -it -v /:/host alpine chroot /host sh. - Docker en modo rootless ejecuta tanto
dockerdcomo tus contenedores dentro de un user namespace. Así, si el daemon se ve comprometido o un contenedor logra escapar, el atacante termina con un UID sin privilegios en el host en lugar de root. - Una regla de sudoers restringida a
/usr/bin/dockersigue siendo equivalente a root, pero exige una contraseña para usar Docker y registra cada uso. - Podman no tiene daemon y se ejecuta en modo rootless por defecto cuando lo usas como usuario normal. Su CLI es en gran medida compatible con la de Docker, así que
alias docker=podmancubre la mayoría de los flujos de trabajo diarios. - En un host heredado, comprueba quién pertenece al grupo
docker, qué usuario ejecutadockerd, los permisos del socket y si el daemon escucha en el puerto TCP 2375.
¿Por qué el grupo de Docker otorga acceso root?
El grupo docker equivale a root porque el daemon de Docker se ejecuta como root y el grupo controla el acceso de escritura a su socket Unix. Poder escribir en ese socket significa poder ordenar a un proceso root que haga cualquier cosa. Si quieres entender el mecanismo subyacente, la guía de permisos de archivos en Linux explica cómo los permisos de grupo de un archivo determinan quién puede abrirlo. En el caso de Docker, el único detalle relevante es que el grupo docker tiene permiso de escritura sobre el socket.
Esta es la prueba:
docker run --rm -it -v /:/host alpine chroot /host sh
Este comando monta el / del host en el contenedor mediante un bind mount y luego ejecuta chroot sobre él. Con eso obtienes una shell de root sobre el sistema de archivos real del host. Desde ahí puedes editar /etc/shadow o /etc/sudoers, o añadir claves SSH en /root. No necesitas contraseña ni exploit alguno. Cualquier script, dependencia o extensión del editor comprometida que se ejecute con tu usuario puede hacer lo mismo. La documentación de Docker lo explica en la sección sobre la superficie de ataque del daemon de Docker.
Configurar Docker en modo rootless
El modo rootless ejecuta dockerd y todos los contenedores dentro de un user namespace perteneciente a tu cuenta, por lo que es la solución directa al problema del grupo docker. El socket se ubica en $XDG_RUNTIME_DIR/docker.sock en lugar de /var/run/docker.sock. RootlessKit crea el namespace. Los binarios auxiliares setuid newuidmap y newgidmap asignan el UID 0 del namespace a tu propio UID. Para los demás UID del contenedor, asignan un rango tomado de /etc/subuid y /etc/subgid. La red funciona mediante una pila de red en modo usuario, como slirp4netns, gvisor-tap-vsock o pasta.
Instala los requisitos previos y luego comprueba tus rangos de IDs subordinados. La documentación del modo rootless exige un rango de al menos 65.536 IDs subordinados para tu usuario, tanto en /etc/subuid como en /etc/subgid.
sudo apt-get install -y uidmap docker-ce-rootless-extras
grep ^$(whoami): /etc/subuid /etc/subgid
Desactiva el daemon de root y ejecuta la herramienta de configuración como usuario normal:
sudo systemctl disable --now docker.service docker.socket
dockerd-rootless-setuptool.sh install
systemctl --user enable --now docker
sudo loginctl enable-linger $USER
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
La línea enable-linger mantiene el daemon en ejecución después de cerrar sesión. Cuando Docker rootless funcione, sal del grupo antiguo con sudo gpasswd -d $USER docker. La documentación oficial del modo rootless cubre otras distribuciones. Si después el cliente no logra conectarse al daemon, la guía para solucionar errores de “Docker daemon not running” explica cómo revisar el socket y systemd.
El modo rootless requiere user namespaces sin privilegios. Ubuntu 24.04 y versiones posteriores los restringen por defecto mediante AppArmor. El paquete apparmor de Ubuntu ya incluye el perfil que necesita rootlesskit, así que instalar docker-ce-rootless-extras con apt funciona sin pasos adicionales. En cambio, si instalas con el script get.docker.com/rootless, tendrás que añadir ese perfil manualmente. No desactives la restricción en todo el sistema con kernel.apparmor_restrict_unprivileged_userns=0. La página de solución de problemas del modo rootless cubre los demás casos de fallo.
¿Qué desventajas tiene el modo rootless?
Estas son las principales limitaciones frente a un daemon con privilegios de root:
| Limitación | Alternativa |
|---|---|
| Por defecto no se pueden usar puertos inferiores a 1024 | sysctl net.ipv4.ip_unprivileged_port_start=80, o un proxy inverso |
--privileged no otorga privilegios reales en el host | Solo se aplican las capabilities dentro del user namespace |
| La red en modo usuario es más lenta que el driver bridge | El driver experimental lxc-user-nic, o hacer benchmarks antes de pasar a producción |
| Los kernels anteriores a 5.11 necesitan fuse-overlayfs | El kernel 5.11+ admite overlay2 de forma nativa |
La página de solución de problemas enumera algunas más, como la falta de soporte para AppArmor, checkpoints y redes overlay.
Usar sudo con una regla restringida
Exigir sudo para usar Docker, en lugar de pertenecer al grupo docker, es una solución intermedia cuando el modo rootless no es compatible con tu carga de trabajo. Sigue siendo equivalente a root, porque sudo docker puede ejecutar el mismo comando chroot. Lo que ganas es una solicitud de contraseña y una entrada en el registro de auditoría por cada invocación. Además, el código que se ejecuta silenciosamente con tu usuario ya no tiene acceso al socket.
sudo gpasswd -d alice docker
sudo visudo -f /etc/sudoers.d/docker
alice ALL=(root) /usr/bin/docker
Esta regla solo permite ejecutar el binario de Docker, no comandos arbitrarios. Puedes revisar su uso con journalctl _COMM=sudo. La documentación de post-instalación también describe un método basado en newgrp, que protege el acceso a Docker con una contraseña de grupo mientras el CLI sigue ejecutándose con tu propio usuario.
Migrar a Podman
Podman no tiene daemon y, cuando lo ejecutas como usuario normal, funciona en modo rootless por defecto. Los contenedores se ejecutan como procesos hijos de tu usuario, dentro de un user namespace y con las mismas asignaciones de /etc/subuid. Su CLI imita a la de Docker lo suficiente como para que la mayoría de los comandos funcionen sin cambios.
sudo apt-get install -y podman
alias docker=podman
podman run --rm -p 8080:80 docker.io/library/nginx
Las diferencias aparecen en los casos límite: los nombres de imagen completamente cualificados, el soporte de Compose y las herramientas que esperan un socket de Docker. Prueba tus scripts antes de migrar a todo un equipo.
Reforzar la seguridad del propio contenedor
Independientemente del daemon que utilices, limita lo que puede hacer el proceso dentro del contenedor. Cuatro ajustes cubren la mayor parte del riesgo:
- un
USERque no sea root - ninguna capability más allá de las que la aplicación necesita
- un sistema de archivos raíz de solo lectura
- ninguna escalada de privilegios mediante binarios setuid
FROM node:22-alpine
WORKDIR /app
COPY --chown=node:node . .
USER node
CMD ["node", "server.js"]
docker run -d \
--cap-drop=ALL \
--read-only --tmpfs /tmp \
--security-opt no-new-privileges \
-p 8080:8080 myapp
La instrucción USER del Dockerfile define el UID en tiempo de ejecución. --cap-drop=ALL elimina todas las capabilities. Si una aplicación realmente necesita alguna, puedes volver a añadirla con --cap-add. Esta aplicación escucha en el puerto 8080, así que no necesita ninguna. La combinación de --read-only con --tmpfs hace que solo /tmp sea escribible. no-new-privileges impide que los binarios setuid eleven privilegios. Todas estas opciones están documentadas en la referencia de docker run.
Auditar una máquina heredada
En un VPS configurado por otra persona, responde cuatro preguntas:
- ¿Quién pertenece al grupo
docker? - ¿Qué usuario ejecuta
dockerd? - ¿Quién puede escribir en el socket?
- ¿El daemon escucha en la red?
getent group docker
ps -eo user,cmd | grep [d]ockerd
ls -l /var/run/docker.sock
sudo ss -ltnp | grep -E ':2375|:2376'
systemctl cat docker | grep -- '-H'
grep -s hosts /etc/docker/daemon.json
Grupo docker: todos los nombres que muestra getent tienen acceso root.
Daemon: si ps muestra root, el daemon se ejecuta con privilegios de root.
Socket: los permisos deberían ser srw-rw---- root docker. Si tiene permiso de escritura para cualquier usuario, corrígelo de inmediato.
Red: un listener en el puerto 2375 significa que la API de Docker está expuesta sin TLS, y cualquiera que pueda alcanzar ese puerto tiene acceso root. Las versiones actuales de Docker Engine se niegan a arrancar con esa configuración, pero un engine antiguo en un host heredado podría seguir ejecutándola. Un listener TLS en el puerto 2376 es más seguro, aunque cualquiera que tenga las claves de cliente sigue teniendo acceso root. Sigue la guía para proteger el socket del daemon y migrar a SSH o a TLS mutuo.
Contenedores: revisa también si hay contenedores que monten el socket, ya que cada uno de ellos es una vía de acceso a root:
docker ps -q | xargs -r docker inspect \
--format '{{.Name}} {{range .Mounts}}{{.Source}} {{end}}' | grep docker.sock
El acceso al socket de Docker es acceso root, sin matices. Empieza por ejecutar getent group docker en cada máquina que utilices. Después, migra a esos usuarios a Docker rootless o a Podman, o bien protege el socket con una regla de sudo que quede registrada.
Preguntas frecuentes
¿Cuál es la diferencia entre Docker rootless y userns-remap?
Con userns-remap, dockerd sigue ejecutándose como root y solo los contenedores se ejecutan con UID subordinados reasignados, como el usuario dockremap que se crea al establecer userns-remap en 'default' en /etc/docker/daemon.json. Esto no cierra la brecha del grupo docker: un miembro del grupo puede pasar --userns=host para desactivar la reasignación en un contenedor concreto y ejecutar el escape con chroot. El modo rootless, en cambio, ejecuta el propio daemon con tu usuario.
¿Pueden coexistir Docker rootless y Docker con privilegios de root en la misma máquina?
Sí. Los dos daemons usan sockets distintos: /var/run/docker.sock para el daemon con privilegios de root y $XDG_RUNTIME_DIR/docker.sock para el rootless. Al ejecutar dockerd-rootless-setuptool.sh install se crea un contexto de CLI llamado 'rootless' y se activa automáticamente. Usa docker context use default para dirigirte al daemon de root y docker context use rootless para volver al rootless. Mientras el daemon con privilegios de root siga en ejecución, cualquiera que pueda escribir en su socket seguirá teniendo acceso root.
¿Mis imágenes y contenedores existentes se transfieren a Docker rootless?
No. El daemon rootless mantiene su propio directorio de datos, por defecto ~/.local/share/docker, separado del directorio /var/lib/docker que usa el daemon de root. Las imágenes, contenedores y volúmenes creados con Docker con privilegios de root no son visibles para él. Vuelve a descargar las imágenes, o expórtalas con docker save desde el daemon de root e impórtalas con docker load en el rootless. Actualiza también las tareas de copia de seguridad que solo cubran /var/lib/docker.
¿Por qué los archivos escritos por contenedores rootless aparecen con propietarios extraños en el host?
Docker rootless asigna los UID del contenedor a UID del host. El usuario root dentro del contenedor se corresponde con tu propio UID, por lo que los archivos que escribe en un bind mount aparecen como tuyos. Cualquier otro UID del contenedor se asigna dentro de tu rango de /etc/subuid. Con un rango que empieza en 100000, el UID 1000 del contenedor se convierte en el UID 100999 del host, y tu usuario no puede editar esos archivos directamente. Ejecutar chown a 0:0 dentro de un contenedor devuelve la propiedad de los archivos a tu usuario.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k