En una instalación normal, el demonio de Docker se ejecuta como root y cualquier usuario con acceso a su socket puede obtener root en el servidor. En modo rootless, tanto el demonio como los contenedores se ejecutan con un usuario sin privilegios dentro de un espacio de nombres de usuario, de modo que un contenedor comprometido no obtiene más permisos que ese usuario. En este tutorial configurarás Docker rootless en Ubuntu 24.04, lo dejarás arrancando con systemd y resolverás las limitaciones habituales: puertos privilegiados y límites de CPU y memoria.
Requisitos previos
Para seguir esta guía necesitas:
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
- Docker Engine instalado desde el repositorio oficial de Docker (paquete
docker-ce). El paquetedocker-ce-rootless-extras, que incluye las herramientas rootless, se instala con él. - Un usuario no root con privilegios
sudo(en esta guíayour_user) que será quien ejecute Docker. Inicia sesión con él directamente por SSH: el modo rootless necesita una sesión de systemd de usuario, que no se crea consuni consudo -i.
Comprueba que el paquete de extras está instalado:
dpkg -l docker-ce-rootless-extras | grep ^ii
ii docker-ce-rootless-extras 5:28.x.x-1~ubuntu.24.04~noble amd64 Rootless support for Docker.
Paso 1: Instalar dependencias y comprobar los rangos de UID
Docker rootless mapea los UID de los contenedores a un rango de UID subordinados de tu usuario. Necesita las herramientas newuidmap y newgidmap, del paquete uidmap:
sudo apt update
sudo apt install uidmap
Comprueba que tu usuario tiene rangos asignados en /etc/subuid y /etc/subgid. Ubuntu los crea automáticamente al usar adduser:
grep "^$USER:" /etc/subuid /etc/subgid
/etc/subuid:your_user:100000:65536
/etc/subgid:your_user:100000:65536
Si no aparece nada, asigna un rango libre de al menos 65.536 identificadores:
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 your_user
Paso 2: Desactivar el demonio de Docker del sistema
Puedes tener ambos demonios a la vez, pero si el objetivo es que nadie use Docker como root, desactiva el del sistema y elimina su socket:
sudo systemctl disable --now docker.service docker.socket
sudo rm -f /var/run/docker.sock
Si tu usuario está en el grupo docker, sácalo; ya no lo necesita y ese grupo es el que da acceso al demonio con root:
sudo gpasswd -d your_user docker
Cierra la sesión SSH y vuelve a entrar para que el cambio de grupo se aplique.
Paso 3: Permitir espacios de nombres de usuario en AppArmor
Ubuntu 24.04 restringe con AppArmor la creación de espacios de nombres de usuario por parte de procesos sin privilegios, y RootlessKit, el componente que prepara el entorno rootless, los necesita. Comprueba si la restricción está activa:
sysctl kernel.apparmor_restrict_unprivileged_userns
kernel.apparmor_restrict_unprivileged_userns = 1
Con valor 1, crea un perfil de AppArmor que permita a rootlesskit crear espacios de nombres de usuario. Primero comprueba si ya existe uno:
ls /etc/apparmor.d/ | grep -i rootlesskit
Si no hay ninguno, crea el perfil:
sudo nano /etc/apparmor.d/usr.bin.rootlesskit
abi <abi/4.0>,
include <tunables/global>
/usr/bin/rootlesskit flags=(unconfined) {
userns,
include if exists <local/usr.bin.rootlesskit>
}
Carga el perfil reiniciando AppArmor:
sudo systemctl restart apparmor.service
Verifica que el perfil está cargado:
sudo aa-status | grep rootlesskit
/usr/bin/rootlesskit
Este perfil solo concede el permiso userns a ese binario concreto. No desactives la restricción de forma global con el sysctl: afecta a todos los programas del sistema.
Paso 4: Instalar el demonio rootless
Ejecuta la herramienta de instalación con tu usuario, sin sudo:
dockerd-rootless-setuptool.sh install
La herramienta crea el servicio de usuario ~/.config/systemd/user/docker.service, lo arranca y crea un contexto de Docker llamado rootless. Al final muestra algo como esto:
[INFO] Creating /home/your_user/.config/systemd/user/docker.service
[INFO] starting systemd service docker.service
...
[INFO] Installed docker.service successfully.
[INFO] To control docker.service, run: `systemctl --user (start|stop|restart) docker.service`
[INFO] To run docker.service on system startup, run: `sudo loginctl enable-linger your_user`
[INFO] Creating CLI context "rootless"
[INFO] Using CLI context "rootless"
Si la herramienta falla con un error de requisitos, lee su salida: indica qué falta (normalmente uidmap o el perfil de AppArmor) y el comando para resolverlo.
Habilita lingering para que el demonio arranque con el servidor y siga funcionando cuando cierres la sesión SSH:
sudo loginctl enable-linger "$USER"
Y asegúrate de que el servicio queda habilitado:
systemctl --user enable docker.service
Paso 5: Verificar la instalación
La CLI usa el contexto rootless, que apunta al socket de tu usuario en $XDG_RUNTIME_DIR/docker.sock. Compruébalo:
docker context ls
NAME DESCRIPTION DOCKER ENDPOINT ERROR
default Current DOCKER_HOST based configuration unix:///var/run/docker.sock
rootless * Rootless mode unix:///run/user/1000/docker.sock
Confirma que el demonio funciona en modo rootless:
docker info --format '{{.SecurityOptions}}'
[name=seccomp,profile=builtin name=rootless name=cgroupns]
Ejecuta un contenedor de prueba y mira quién es el propietario del proceso en el host:
docker run -d --name test-nginx -p 8080:80 nginx:alpine
ps -o user,pid,cmd -C nginx
USER PID CMD
your_user 5123 nginx: master process nginx -g daemon off;
100100 5160 nginx: worker process
El proceso que dentro del contenedor es root, en el host pertenece a tu usuario. Las imágenes, contenedores y volúmenes se guardan en ~/.local/share/docker, no en /var/lib/docker.
Elimina el contenedor de prueba:
docker rm -f test-nginx
Paso 6: Publicar puertos por debajo de 1024
Un usuario sin privilegios no puede abrir puertos por debajo de 1024, así que -p 80:80 falla en modo rootless. La forma más acotada de permitirlo es dar la capacidad CAP_NET_BIND_SERVICE al binario de RootlessKit:
sudo setcap cap_net_bind_service=ep "$(command -v rootlesskit)"
systemctl --user restart docker.service
Comprueba que ahora puedes publicar el puerto 80:
docker run -d --name web -p 80:80 nginx:alpine
curl -I http://localhost
HTTP/1.1 200 OK
Server: nginx/1.27.2
Si UFW está activo, abre el puerto con sudo ufw allow 80/tcp. Ten en cuenta que la capacidad se pierde cuando una actualización del paquete reemplaza el binario, así que tendrás que repetir el setcap tras actualizar Docker.
Elimina el contenedor:
docker rm -f web
Paso 7: Habilitar límites de CPU y memoria
Opciones como --memory o --cpus necesitan que systemd delegue esos controladores de cgroup v2 en la sesión de tu usuario. Ubuntu 24.04 usa cgroup v2, pero por defecto solo delega memory y pids. Compruébalo:
cat "/sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/cgroup.controllers"
memory pids
Crea un archivo de configuración para el servicio de usuario de systemd:
sudo mkdir -p /etc/systemd/system/[email protected]
sudo nano /etc/systemd/system/[email protected]/delegate.conf
[Service]
Delegate=cpu cpuset io memory pids
Recarga systemd:
sudo systemctl daemon-reload
La delegación se aplica al iniciar la sesión de usuario de systemd. Como el lingering la mantiene viva, reinicia el servidor con sudo reboot para aplicarla. Al volver, repite la comprobación:
cat "/sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/cgroup.controllers"
cpuset cpu io memory pids
Prueba un contenedor con límites:
docker run --rm --memory 256m --cpus 0.5 alpine cat /sys/fs/cgroup/memory.max /sys/fs/cgroup/cpu.max
268435456
50000 100000
Limitaciones del modo rootless
Antes de usarlo en producción, ten en cuenta lo que cambia respecto a Docker con root:
| Aspecto | Comportamiento en rootless |
|---|---|
| IP de origen | Con el controlador de puertos por defecto, los contenedores ven las conexiones como si llegaran desde la red interna, no con la IP real del cliente. Si la necesitas en los logs, usa un proxy inverso que añada X-Forwarded-For o ejecuta ese servicio con --network host. |
| Rendimiento de red | Algo menor que con la red en modo bridge del Docker con root, porque el tráfico pasa por un espacio de nombres de red de usuario. |
| Opciones no disponibles | --privileged no da acceso real al hardware del host; tampoco funcionan AppArmor por contenedor, checkpoint ni redes macvlan o ipvlan. |
| Almacenamiento | Controlador overlay2 en kernels 5.11 o posteriores, como el de Ubuntu 24.04. |
| Volúmenes bind | Los archivos creados por root dentro del contenedor pertenecen a tu usuario en el host; los creados con otro UID del contenedor pertenecen a un UID subordinado (por ejemplo 100999). |
Solución de problemas
[rootlesskit:parent] error: failed to start the child: fork/exec /proc/self/exe: operation not permitted. AppArmor está bloqueando los espacios de nombres de usuario. Revisa que el perfil del paso 3 existe y está cargado con sudo aa-status.
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. La CLI está usando el contexto default. Cambia al rootless con docker context use rootless, o define export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock en tu ~/.bashrc.
Failed to connect to bus: No medium found al ejecutar systemctl --user. La sesión no tiene un gestor de systemd de usuario, típico si has entrado con su. Conéctate por SSH directamente con ese usuario.
El demonio se detiene al cerrar SSH o no arranca tras reiniciar. Falta sudo loginctl enable-linger your_user o el servicio no está habilitado con systemctl --user enable docker.service.
Los logs del demonio. Consúltalos con journalctl --user -u docker.service.
Conclusión
Has configurado Docker en modo rootless en Ubuntu 24.04, con el perfil de AppArmor necesario, arranque automático mediante systemd, puertos privilegiados y límites de CPU y memoria funcionando. A partir de aquí puedes crear un usuario dedicado por proyecto o por cliente, cada uno con su propio demonio aislado, poner un proxy inverso delante para conservar la IP del cliente o comparar este enfoque con Podman, que funciona sin root y sin demonio de serie.
