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 Serversby Donweb

Todo el poder de la nube a tus proyectos y aplicaciones.
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

Descubre la mejor solución de Cloud Hosting