
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 232073El 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:65536Docker 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 contenedorPor 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
sudoy 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)"
fiPaso 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 binariossetuid.
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:
- Detén las aplicaciones de forma controlada.
- Realiza backups consistentes de bases de datos y volúmenes.
- Documenta las imágenes y archivos Compose.
- Prepara la recreación de los contenedores.
- 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.jsonContinúa únicamente si aparece:
configuration OKLa 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-pagerSi 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/subgidEl resultado debe incluir un rango no superpuesto en cada archivo:
dockremap:231072:65536El 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 \
dockremapDespué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 300Dentro del contenedor, el proceso aparece como root:
docker exec userns-prueba idObté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 65536Esto demuestra que UID 0 dentro del contenedor corresponde al UID 231072 del host.
Elimina la prueba:
docker rm -f userns-pruebaPaso 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/datosNo 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/pruebaEl archivo debe aparecer con el UID y GID calculados. Elimínalo después:
sudo rm /srv/miapp/datos/pruebaPara 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.
- Detén los contenedores remapeados.
- Elimina
userns-remapdedaemon.json. - Valida la configuración.
- Reinicia Docker.
- 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 lsLos 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-pagerBusca 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/subuidy/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=hostpermanentes. - 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-remappreferentemente 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.