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 paquete docker-ce-rootless-extras, que incluye las herramientas rootless, se instala con él.
  • Un usuario no root con privilegios sudo (en esta guía your_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 con su ni con sudo -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:

AspectoComportamiento en rootless
IP de origenCon 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 redAlgo 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.
AlmacenamientoControlador overlay2 en kernels 5.11 o posteriores, como el de Ubuntu 24.04.
Volúmenes bindLos 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.