El root del contenedor con UID 0 se mapea a un UID sin privilegios en el host a través del namespace de usuario de dockremap

Docker ejecuta muchos contenedores con UID 0 en su interior. Aunque ese proceso está aislado mediante namespaces y otras medidas del kernel, continúa siendo conveniente reducir el impacto que tendría una fuga del contenedor.

userns-remap traduce los usuarios del contenedor a un rango de UID y GID sin privilegios en el host. Por ejemplo:

Contenedor                 Host
UID 0     (root)   →       UID 231072
UID 1000  (app)    →       UID 232072
UID 1001           →       UID 232073

El proceso sigue viendo UID 0 dentro del contenedor, pero en el host se ejecuta como un UID numérico alto sin privilegios.

userns-remap no convierte Docker en rootless. El daemon continúa ejecutándose como root. Esta función debe combinarse con usuarios no privilegiados dentro de las imágenes, seccomp, AppArmor, reducción de capabilities y protección del socket de Docker. Docker Docs.

Qué cambia al activar userns-remap

Docker utiliza /etc/subuid y /etc/subgid para determinar qué rangos puede asignar al usuario de remapeo. Con esta entrada:

dockremap:231072:65536

Docker puede mapear los UID internos 0 a 65535 sobre los UID del host 231072 a 296607.

La fórmula es:

UID del host = inicio subordinado + UID del contenedor

Por eso, el usuario 1000 del contenedor se convierte en el UID 232072 del host.

Requisitos previos

  • Docker Engine sobre Linux. No aplica de la misma forma a Docker Desktop.
  • Ubuntu 24.04 LTS o una distribución compatible.
  • Acceso mediante sudo y consola alternativa.
  • Backup de configuraciones, volúmenes y datos de las aplicaciones.
  • Ventana de mantenimiento para reiniciar Docker.
  • Inventario de bind mounts, contenedores privilegiados y namespaces compartidos.

Paso 1: registrar el estado actual

docker version
docker info
docker ps -a
docker image ls
docker volume ls
docker network ls
docker info --format '{{.DockerRootDir}}'

Conserva también la configuración actual:

sudo install -d -m 0755 /etc/docker

if sudo test -f /etc/docker/daemon.json; then
  sudo cp -a /etc/docker/daemon.json \
    "/etc/docker/daemon.json.bak.$(date +%Y%m%d%H%M%S)"
fi

Paso 2: auditar incompatibilidades

Obtén un resumen de las opciones sensibles de los contenedores existentes:

docker ps -aq | xargs -r docker inspect \
  --format '{{.Name}} privileged={{.HostConfig.Privileged}} pid={{.HostConfig.PidMode}} network={{.HostConfig.NetworkMode}} userns={{.HostConfig.UsernsMode}} binds={{json .HostConfig.Binds}}'

Revisa especialmente:

  • --network=host.
  • --pid=host.
  • Contenedores con --privileged.
  • Bind mounts con escritura.
  • Drivers externos de volúmenes.
  • Montajes de /var/run/docker.sock.
  • Aplicaciones que utilizan mknod, dispositivos o binarios setuid.

Compartir los namespaces PID o NET del host es incompatible con un daemon que utiliza user namespaces. Los contenedores privilegiados requieren además --userns=host, lo que desactiva el remapeo para ese contenedor. Limitaciones oficiales.

Montar el socket de Docker permite controlar el daemon y no queda protegido por el remapeo de usuarios.

Paso 3: planificar la migración de datos

Al activar userns-remap, Docker almacena los objetos remapeados en un subdirectorio dentro de su directorio de datos. Como resultado, las imágenes, contenedores y volúmenes existentes dejan de aparecer mientras el remapeo está activo.

Los datos no se eliminan: quedan ocultos para esa configuración del daemon. De forma inversa, los objetos creados con userns-remap dejan de ser visibles si se desactiva.

Por eso, Docker recomienda activar esta función en una instalación nueva. En un servidor existente:

  1. Detén las aplicaciones de forma controlada.
  2. Realiza backups consistentes de bases de datos y volúmenes.
  3. Documenta las imágenes y archivos Compose.
  4. Prepara la recreación de los contenedores.
  5. Migra los datos mediante herramientas compatibles con cada aplicación.

No copies en caliente /var/lib/docker como único método de respaldo.

Paso 4: configurar el daemon

Edita /etc/docker/daemon.json y agrega:

{
  "userns-remap": "default"
}

Si el archivo ya contiene otras opciones, incorpora userns-remap dentro del mismo objeto JSON:

{
  "log-driver": "local",
  "userns-remap": "default"
}

El valor default indica a Docker que cree y utilice el usuario y grupo dockremap.

Valida antes de reiniciar:

sudo dockerd --validate \
  --config-file=/etc/docker/daemon.json

Continúa únicamente si aparece:

configuration OK

La validación detecta JSON inválido y directivas desconocidas sin iniciar otro daemon. Referencia de dockerd.

Paso 5: aplicar la configuración

Detén las cargas según su procedimiento operativo y reinicia Docker:

sudo systemctl restart docker
sudo systemctl is-active docker
sudo journalctl -u docker --since "5 minutes ago" --no-pager

Si Docker no inicia, no borres su directorio de datos. Revisa daemon.json, la existencia de dockremap y sus rangos subordinados.

Paso 6: comprobar dockremap y sus rangos

getent passwd dockremap
getent group dockremap

grep '^dockremap:' /etc/subuid
grep '^dockremap:' /etc/subgid

El resultado debe incluir un rango no superpuesto en cada archivo:

dockremap:231072:65536

El valor inicial puede ser distinto. Utiliza siempre el asignado en tu servidor.

Cada entrada de /etc/subuid contiene el usuario, el primer UID subordinado y la cantidad de IDs autorizados. Manual de subuid.

Si Docker creó dockremap pero no asignó rangos, debes agregar rangos no superpuestos. Este ejemplo solo es válido si comprobaste que 231072-296607 está libre:

sudo usermod \
  --add-subuids 231072-296607 \
  --add-subgids 231072-296607 \
  dockremap

Después, reinicia Docker y vuelve a verificar. No reutilices un rango asignado a otro usuario.

Paso 7: demostrar el mapeo

Inicia un contenedor temporal:

docker run -d \
  --name userns-prueba \
  alpine:latest \
  sleep 300

Dentro del contenedor, el proceso aparece como root:

docker exec userns-prueba id

Obtén el PID visto desde el host:

PID=$(docker inspect \
  --format '{{.State.Pid}}' \
  userns-prueba)

ps -o pid,uid,gid,user,group,cmd -p "$PID"

Comprueba los mapas aplicados por el kernel:

sudo cat "/proc/$PID/uid_map"
sudo cat "/proc/$PID/gid_map"

Con el rango del ejemplo, la salida será similar a:

         0     231072      65536

Esto demuestra que UID 0 dentro del contenedor corresponde al UID 231072 del host.

Elimina la prueba:

docker rm -f userns-prueba

Paso 8: preparar un bind mount

Los volúmenes administrados por Docker se crean dentro de su almacenamiento remapeado. Los bind mounts apuntan directamente al sistema de archivos del host y requieren propietarios compatibles.

Obtén el inicio de los rangos:

SUBUID=$(awk -F: \
  '$1=="dockremap" {print $2; exit}' \
  /etc/subuid)

SUBGID=$(awk -F: \
  '$1=="dockremap" {print $2; exit}' \
  /etc/subgid)

printf 'SUBUID=%s SUBGID=%s\n' "$SUBUID" "$SUBGID"

Supongamos que la aplicación utiliza UID y GID 1000 dentro del contenedor:

APP_UID=1000
APP_GID=1000

HOST_UID=$((SUBUID + APP_UID))
HOST_GID=$((SUBGID + APP_GID))

printf 'HOST_UID=%s HOST_GID=%s\n' \
  "$HOST_UID" "$HOST_GID"

Con un inicio subordinado de 231072, el resultado será 232072.

Crea el directorio con esos propietarios:

sudo install -d \
  -m 0750 \
  -o "$HOST_UID" \
  -g "$HOST_GID" \
  /srv/miapp/datos

No asignes el directorio al UID real de la cuenta dockremap. Lo importante es el UID subordinado que representa al usuario de la aplicación dentro del namespace.

Paso 9: probar la escritura

docker run --rm \
  --user 1000:1000 \
  --mount type=bind,src=/srv/miapp/datos,dst=/datos \
  alpine:latest \
  sh -c 'id && touch /datos/prueba && ls -ln /datos/prueba'

Comprueba desde el host:

sudo ls -lnd /srv/miapp/datos
sudo ls -ln /srv/miapp/datos/prueba

El archivo debe aparecer con el UID y GID calculados. Elimínalo después:

sudo rm /srv/miapp/datos/prueba

Para un proceso que utiliza UID 0 dentro del contenedor, el propietario del bind mount debe corresponder directamente a $SUBUID:$SUBGID.

No soluciones errores de acceso mediante chmod 777. Calcula el propietario correcto o diseña permisos de grupo y ACL específicos.

Excepciones con userns=host

Es posible desactivar el remapeo para un contenedor:

docker run --userns=host ...

Esta opción reduce el aislamiento y debe reservarse para excepciones documentadas. Además, como las capas de imagen siguen compartiéndose con contenedores remapeados, programas que esperan binarios propiedad de UID 0, como algunos usos de sudo o setuid, pueden comportarse de forma inesperada.

Cómo revertir la configuración

Antes de desactivar userns-remap, exporta o respalda los datos creados mientras estaba activo.

  1. Detén los contenedores remapeados.
  2. Elimina userns-remap de daemon.json.
  3. Valida la configuración.
  4. Reinicia Docker.
  5. Comprueba qué conjunto de objetos vuelve a estar visible.
sudo dockerd --validate \
  --config-file=/etc/docker/daemon.json

sudo systemctl restart docker
sudo systemctl is-active docker

docker ps -a
docker image ls
docker volume ls

Los objetos anteriores a la activación volverán a aparecer. Los creados bajo el remapeo quedarán ocultos.

No elimines directorios numéricos dentro del data-root hasta haber confirmado que sus datos ya no son necesarios.

Problemas frecuentes

Docker no inicia después del cambio

sudo dockerd --validate \
  --config-file=/etc/docker/daemon.json

sudo journalctl -u docker -b --no-pager

Busca JSON inválido, opciones duplicadas entre flags y daemon.json, un usuario inexistente o rangos subordinados ausentes.

Desaparecieron imágenes y contenedores

Es el comportamiento esperado al activar el remapeo en una instalación existente. Los objetos anteriores están ocultos, no necesariamente eliminados. Recrea las cargas y restaura los datos según el plan de migración.

Un bind mount devuelve Permission denied

Confirma:

  • UID y GID utilizados dentro del contenedor.
  • Inicio de los rangos en /etc/subuid y /etc/subgid.
  • Propietario numérico del directorio del host.
  • Permisos de todos los directorios superiores.
  • Si el volumen es realmente un bind mount o un volumen administrado.

Un contenedor con host networking no inicia

--network=host y --pid=host son incompatibles con el remapeo global. Rediseña la carga o documenta una excepción con --userns=host.

Un contenedor privilegiado falla

--privileged requiere desactivar el remapeo para ese contenedor mediante --userns=host. Evalúa si realmente necesita privilegios completos; normalmente es preferible agregar únicamente las capabilities requeridas.

Root no puede crear dispositivos dentro del contenedor

Es una restricción conocida de los user namespaces. Incluso UID 0 dentro del contenedor no puede ejecutar determinadas operaciones, como mknod, porque el kernel reconoce que el proceso pertenece a un namespace sin privilegios sobre el host.

Buenas prácticas

  • Ejecuta las aplicaciones con un usuario no root dentro del contenedor.
  • Evita bind mounts escribibles cuando un volumen administrado sea suficiente.
  • No montes el socket de Docker dentro de contenedores no confiables.
  • Mantén los rangos subordinados sin superposición.
  • Evita excepciones --userns=host permanentes.
  • Aplica no-new-privileges, seccomp y AppArmor.
  • Elimina capabilities que la aplicación no necesite.
  • Prueba restauración y rollback antes de usarlo en producción.
  • Habilita userns-remap preferentemente en instalaciones nuevas.

Conclusión

userns-remap reduce el impacto de ejecutar procesos como root dentro de un contenedor al traducirlos a UID y GID altos sin privilegios en el host.

La implementación queda validada cuando se cumplen cuatro condiciones: Docker utiliza dockremap, los rangos subordinados no se superponen, /proc/<PID>/uid_map demuestra la traducción y los bind mounts funcionan con propietarios calculados, sin permisos excesivos.

Cloud Servers by Donweb

Ya aislaste el root del contenedor. Docker, aun así, sigue corriendo como root en el host.
Alojamiento ultra rápido, escalable y con alta disponibilidad.

  • Performance que te sorprenderá
  • Rápida escalabilidad y sin limitaciones
  • Arquitectura de alta disponibilidad
  • Soporte experto y ejecutivos de cuenta
  • Pagos en tu moneda y facturación local

Aísla tus cargas en un servidor propio