Añadir usuarios a un contenedor Docker: guía de usuario no-root
Aprende a añadir usuarios a un contenedor Docker de forma segura: USER en el Dockerfile, fijar UID/GID, docker exec -u, user y group_add en Compose, y cómo corregir los permisos.
Actualizado Publicado 25 min de lectura

Los contenedores Docker se ejecutan como root por defecto. Bien para una prueba rápida, un riesgo en producción. Esta guía te muestra cómo añadir usuarios a un contenedor Docker de la forma correcta: integra usuarios no-root en tu imagen, fija el UID/GID, corrige los permisos de los volúmenes y verifica antes de desplegar.
Tres enfoques, en orden de preferencia:
- Dockerfile: crea usuarios en tiempo de compilación (recomendado para cualquier cosa que deba sobrevivir a un reinicio)
- Sobrescritura en tiempo de ejecución:
docker run -uydocker exec -u(depuración y tareas puntuales) - Mapeo de UID/GID: haz coincidir los usuarios del contenedor y del host para acabar con los quebraderos de cabeza de permisos en los bind mounts
Esta guía cubre Ubuntu 24.04 y Alpine 3.24, Docker Compose V2 y Docker Engine 29.x. Si necesitas un VPS Linux para practicar, puedes empezar con un VPS de Hetzner o un VPS de Hostinger.
Los precios de los VPS subieron en general en 2026 — si estás reconsiderando una máquina alquilada, mira qué cambió y cuándo gana un mini PC.
¿Por qué ejecutar contenedores como usuario no-root?
Ejecutar contenedores como root crea tres problemas:
- Seguridad: si la aplicación se ve comprometida, el atacante tiene root dentro del contenedor. En hosts mal configurados (sobre todo con
--privilegedo bind mounts escribibles), eso puede escalar a root en el host. - Permisos: un proceso root que escribe en un volumen montado crea archivos propiedad de root en el host. Tu usuario del host (UID 1000) no puede editarlos ni borrarlos sin
sudo. - Mínimo privilegio: el mismo principio que se aplica en todas partes. Los contenedores de producción no deberían ejecutarse como root.
La trampa del bind mount
Este es el problema nº 1 con el que se topan los autoalojadores. Un contenedor que corre como root escribe en un volumen montado y, de repente, todos los archivos del host pertenecen a root:root. Acabas ejecutando sudo chown en el host tras cada reinicio del contenedor. La solución: fija el UID del contenedor para que coincida con tu usuario del host (normalmente 1000). Lo cubrimos en el Método 3, más abajo.
Conoce tu imagen base
Antes de crear un usuario, comprueba si la imagen ya incluye uno. Algunas imágenes oficiales traen un usuario no-root de fábrica:
docker run --rm node id
# uid=1000(node) gid=1000(node) groups=1000(node)| Imagen | Usuario no-root por defecto | UID |
|---|---|---|
node |
node |
1000 |
postgres |
postgres (vía gosu en el entrypoint) |
999 |
nginx |
nginx (procesos worker) |
101 |
ubuntu / debian / alpine |
Ninguno, debes crearlo | - |
Comprueba antes de crear
Ejecuta docker run --rm <imagen> id para ver si la imagen ya tiene un usuario no-root. Si lo tiene, usa simplemente USER <nombre>. No crees un segundo usuario que pueda entrar en conflicto.
Método 1: añadir usuarios en el Dockerfile (recomendado)
Integrar los usuarios en la imagen es el enfoque duradero. Los usuarios pertenecen a las imágenes; los contenedores los heredan. Esto sobrevive a reinicios, redespliegues y ciclos de docker compose up.
Crear un usuario no-root con useradd
El patrón endurecido usa --no-log-init, UID/GID fijados y una shell sin login:
FROM ubuntu:24.04
# Crea un usuario de aplicación dedicado con UID/GID fijados.
# 1001 deja libre el 1000 para coincidir con el usuario del host en bind mounts.
RUN groupadd -g 1001 appgroup && \
useradd --no-log-init -u 1001 -g appgroup -m -s /usr/sbin/nologin appuser
# Ejecuta el contenedor como usuario sin privilegios (UID numérico: sobrevive a cambios en /etc/passwd)
USER 1001:1001
WORKDIR /home/appuserDesglose de los flags:
| Flag | Qué hace |
|---|---|
--no-log-init |
Evita el bug de agotamiento de disco por el archivo disperso faillog con UIDs grandes (problema de archive/tar de Go) |
-u 1001 |
Fija el UID, determinista entre recompilaciones |
-g appgroup |
Establece el grupo primario (debe existir antes; por eso groupadd va antes que useradd) |
-m |
Crea el directorio home |
-s /usr/sbin/nologin |
Shell sin login para cuentas de servicio; usa /bin/bash solo si necesitas acceso interactivo |
¿Por qué --no-log-init?
Sin este flag, useradd escribe en /var/log/faillog y /var/log/lastlog. Con un UID grande, estos archivos pueden crecer hasta gigabytes de bytes NULL por un bug de archivos dispersos en el archive/tar de Go. Pasa siempre --no-log-init en los Dockerfiles.
Puedes parametrizar el UID/GID mediante build args. Consulta ARG y ENV en Docker para el patrón completo.
La variante Alpine: adduser y addgroup
Alpine usa adduser/addgroup de busybox, no el useradd de shadow-utils. Los flags son diferentes:
Ubuntu/Debian
FROM ubuntu:24.04
RUN groupadd -g 1001 appgroup && \
useradd --no-log-init -u 1001 -g appgroup -m -s /usr/sbin/nologin appuser
USER 1001:1001
WORKDIR /home/appuserAlpine
FROM alpine:3.24
RUN addgroup -g 1001 appgroup && \
adduser -D -u 1001 -G appgroup -s /bin/sh appuser
USER 1001:1001
WORKDIR /home/appuserDiferencias de flags en Alpine: -D = sin contraseña (equivalente a --disabled-password), -G = grupo primario, -s /bin/sh = shell (Alpine no trae bash por defecto).
Blíndalo con el numérico USER 1001:1001
La propia documentación de Docker recomienda UIDs numéricos:
# Bien: sobrevive a cambios en /etc/passwd, no necesita resolución de nombres
USER 1001:1001
# Funciona pero es frágil: depende de que exista la entrada en /etc/passwd
USER appuserCuando especificas un grupo (la parte :1001), el usuario obtiene solo ese grupo. Sin grupos suplementarios de /etc/group. Normalmente es justo lo que quieres para una cuenta de servicio.
Establece la propiedad de los archivos con COPY --chown
Usa COPY --chown para establecer la propiedad en tiempo de compilación en lugar de ejecutar chown en una capa RUN aparte:
COPY --chown=1001:1001 app/ /home/appuser/app/
USER 1001:1001COPY --chown acepta nombres o UID/GID numéricos. Los nombres requieren resolución en /etc/passwd durante la compilación. Si el usuario aún no existe en esa capa, la compilación falla. Los UIDs numéricos son más seguros y portables.
Para mantener las capas del Dockerfile ligeras, consulta cómo copiar varios archivos en una sola capa de un Dockerfile.
Builds multi-etapa: compila como root, ejecuta como usuario
Los builds multi-etapa te permiten instalar paquetes y compilar como root en una etapa de build, y luego copiar solo los artefactos a una etapa de ejecución limpia que corre como usuario no-root:
# Etapa de build: corre como root, instala dependencias
FROM ubuntu:24.04 AS builder
RUN apt-get update && apt-get install -y --no-install-recommends build-essential
COPY . /src
WORKDIR /src
RUN make build
# Etapa de ejecución: imagen mínima, usuario no-root
FROM ubuntu:24.04
RUN groupadd -g 1001 appgroup && \
useradd --no-log-init -u 1001 -g appgroup -m -s /usr/sbin/nologin appuser
COPY --chown=1001:1001 --from=builder /src/app /home/appuser/app
USER 1001:1001
WORKDIR /home/appuser
CMD ["./app"]La imagen de ejecución nunca tiene build-essential, código fuente ni artefactos de compilación. Menor superficie de ataque, imagen más pequeña.
Método 2: añadir usuarios en tiempo de ejecución (solo depuración)
Los cambios de usuario en tiempo de ejecución son temporales y solo útiles para depurar o para tareas puntuales. Viven en la capa escribible del contenedor y se pierden cuando el contenedor se recrea.
Los cambios en tiempo de ejecución son efímeros
Cualquier usuario o cambio de permisos hecho dentro de un contenedor en ejecución se pierde cuando se recrea (docker compose up, docker rm). Integra los usuarios en la imagen para cualquier cosa que deba sobrevivir a un reinicio. Consulta actualizar contenedores con Docker Compose para ver cómo los redespliegues borran la capa escribible.
docker run -u / --user y --group-add
El flag -u / --user sobrescribe el usuario por defecto del contenedor en tiempo de ejecución. Los IDs numéricos no requieren una entrada en /etc/passwd:
# Ejecuta como UID 1001 con GID 1001
docker run --rm -it -u 1001:1001 myimage sh
# Añade el grupo suplementario 2000 (p. ej., un grupo de volumen compartido)
docker run --rm -it --user 1001:1001 --group-add 2000 myimage id
# Formatos aceptados:
# -u user # se busca en /etc/passwd
# -u user:group # ambos se buscan
# -u uid # numérico, sin búsqueda
# -u uid:gid # numérico, sin búsqueda
# -u user:gid # mixto
# -u uid:group # mixtoEsto es útil para probar si tu app funciona como usuario no-root antes de llevar el cambio al Dockerfile.
Inspecciona como otro usuario con docker exec -u
En lugar de modificar un contenedor en ejecución, usa docker exec -u para ejecutar comandos como otro usuario:
# Abre una shell como appuser dentro de un contenedor en ejecución
docker exec -it -u appuser mycontainer sh
# Ejecuta un único comando como un UID concreto
docker exec -u 1001:1001 mycontainer whoami
# appuser
# Comprueba a qué archivos puede acceder el usuario de la app
docker exec -u 1001:1001 mycontainer ls -la /appEsto no modifica el contenedor — solo ejecutas un proceso como otro usuario. Genial para diagnosticar problemas de permisos. Para depurar los logs de contenedores, consulta redirigir los logs de Docker a un solo archivo.
Método 3: mapea el UID/GID del host para arreglar los permisos de los volúmenes
Esta es la sección que resuelve el problema de propiedad de los bind mounts de la introducción. El problema de fondo: el UID 0 del contenedor (root) escribe archivos en un volumen montado y el host ve archivos propiedad de root que tu usuario del host (UID 1000) no puede editar.
La solución: fija el UID de la app para que coincida con el usuario del host.
Mapeo de UID/GID con build args
Pasa el UID/GID del usuario del host en tiempo de compilación:
FROM ubuntu:24.04
ARG USER_ID=1000
ARG GROUP_ID=1000
RUN groupadd -g "$GROUP_ID" appgroup && \
useradd --no-log-init -u "$USER_ID" -g "$GROUP_ID" -m -s /usr/sbin/nologin appuser
USER "$USER_ID:$GROUP_ID"Compila con los IDs de tu usuario del host:
docker build --build-arg USER_ID=$(id -u) --build-arg GROUP_ID=$(id -g) -t myapp .El valor por defecto 1000 coincide con el primer usuario estándar en la mayoría de los sistemas Linux. En un VPS de un solo usuario, esto casi siempre es correcto. Para más sobre build args, consulta ARG y ENV en Docker.
Docker Compose: user, group_add y PUID/PGID
La especificación de Compose soporta user y group_add directamente. No hace falta la clave version: (es obsoleta en Compose V2) y el comando es docker compose, no docker-compose:
services:
app:
build: .
user: "1001:1001"
group_add:
- "2000"
volumes:
- .:/app
environment:
- USER_ID=${USER_ID:-1001}
- GROUP_ID=${GROUP_ID:-1001}docker compose up -d --buildPara un VPS de un solo usuario, fija user: "1000:1000" para coincidir con el usuario de despliegue ubuntu. El truco de exportar ${UID}/${GID} en la shell que recomiendan algunos tutoriales falla en macOS y Docker Desktop, donde los UIDs de la VM difieren.
Convención PUID/PGID
Muchas imágenes autoalojadas populares (los contenedores de LinuxServer.io como Plex, Sonarr, Jellyfin) usan las variables de entorno PUID y PGID para establecer el usuario en tiempo de ejecución al arrancar. El script de entrypoint lee estos valores y crea o cambia al usuario correspondiente. Es una convención, no una función de Docker — revisa la documentación de tu imagen. Si estás ejecutando una app de Python en Docker Compose como usuario no-root, el campo user: es el enfoque más limpio.
Si estás configurando Docker por primera vez, consulta cómo instalar Docker en Ubuntu ARM.
Escalada de privilegios bien hecha: gosu en lugar de sudo
El consejo antiguo era instalar sudo en tu imagen y añadir usuarios al grupo sudo. Las propias buenas prácticas de Docker ahora dicen que lo evites:
Evita instalar o usar sudo, ya que tiene un comportamiento impredecible con el TTY y el reenvío de señales. En su lugar, usa gosu. — Buenas prácticas de build de Docker
El patrón gosu (el mismo de la imagen oficial de Postgres) arranca el entrypoint como root, corrige la propiedad de los directorios de datos y luego baja al usuario de la app:
FROM ubuntu:24.04
RUN apt-get update && \
apt-get install -y --no-install-recommends gosu && \
rm -rf /var/lib/apt/lists/*
RUN groupadd -g 1001 appgroup && \
useradd --no-log-init -u 1001 -g appgroup -m appuser
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["docker-entrypoint.sh"]#!/bin/sh
set -e
# Si corre como root, corrige la propiedad y baja a appuser
if [ "$(id -u)" = "0" ]; then
chown -R appuser:appgroup /data
exec gosu appuser "$@"
fi
exec "$@"El contenedor arranca como root (para poder hacer chown del volumen de datos) y luego exec gosu appuser reemplaza el proceso por el del usuario sin privilegios. La app nunca corre como root.
Nunca integres secretos en las capas de la imagen
Las contraseñas y claves en comandos RUN son visibles con docker history. El viejo patrón de echo 'appuser:password' | chpasswd deja una contraseña en texto plano en la imagen para siempre. Usa variables de entorno en tiempo de ejecución o protege los secretos en Docker Compose en su lugar.
Endurecimiento avanzado: userns-remap y Docker rootless
La instrucción USER cubre la mayoría de los casos. Si necesitas un aislamiento más fuerte (hosts multi-inquilino, imágenes no confiables, requisitos de cumplimiento), existen dos opciones:
Reasignación de espacios de nombre de usuario (userns-remap)
Mapea el root del contenedor (UID 0) a un UID sin privilegios del host. Añade a /etc/docker/daemon.json:
{ "userns-remap": "default" }sudo systemctl restart docker
id dockremap # usuario de reasignación creado por el daemon
grep dockremap /etc/subuid /etc/subgid # verifica el rango de 65536 IDs subordinadosAdvertencias:
- Actívalo solo en una instalación nueva — enmascara las imágenes y contenedores existentes. Tendrás que volver a descargarlo todo.
- Exclusión por contenedor:
docker run --userns=host(pero tiene efectos secundarios sobre la propiedad de los archivos). --privilegedrequiere--userns=host;mknodestá denegado en los user namespaces.- La propiedad de los bind mounts debe prepararse de antemano — el UID reasignado en el host no coincidirá con lo que esperas a menos que lo planifiques.
Docker 29 + userns-remap
En instalaciones nuevas de Docker 29.x, el almacén de imágenes de containerd es el predeterminado y es temporalmente incompatible con userns-remap (moby/moby#47377). Activar la reasignación hace que el daemon vuelva al almacén de imágenes heredado. Verifica el comportamiento en tu versión exacta de Docker antes de activarlo en producción.
Modo rootless
Ejecuta el propio daemon de Docker como un usuario sin privilegios. Requisitos: newuidmap/newgidmap y al menos 65.536 UIDs subordinados en /etc/subuid y /etc/subgid.
# Instala la herramienta de configuración rootless
dockerd-rootless-setuptool.sh install
# Arranca el daemon rootless
systemctl --user start dockerDocker 29.5.0 convirtió gvisor-tap-vsock en el controlador de red rootless por defecto, lo que mejora la compatibilidad.
Cuándo molestarse: si ejecutas imágenes no confiables, alojas a varios usuarios o tienes requisitos de cumplimiento. Para un VPS de un solo usuario con tus propias imágenes, USER en el Dockerfile es suficiente. Para más sobre seguridad en Docker, consulta cómo endurecer Docker en un VPS de Hetzner.
Verifica antes de desplegar
No lo des por hecho — verifica. Ejecuta estos comandos tras compilar y después de cada despliegue:
docker run --rm myimage id— esperauid=1001(appuser) gid=1001(appgroup)docker inspect --format '{{.Config.User}}' myimage— espera1001:1001docker exec -it mycontainer id— confirma dentro de un contenedor en ejecucióndocker compose exec app id— equivalente en Composedocker exec mycontainer touch /app/check.txt && ls -l ./check.txt— espera tu UID, no rootdocker build --check .— el linter de Dockerfile detecta unUSERausente (Dockerfile v1.8.0+)
Script rápido de verificación:
# Tras el build — confirma el usuario por defecto de la imagen
docker run --rm myimage id
# Inspecciona sin ejecutar
docker create --name tmp myimage && docker inspect --format '{{.Config.User}}' tmp && docker rm tmp
# Dentro de un contenedor en ejecución
docker exec -it mycontainer id
# Comprobación de propiedad del bind mount (lado host)
docker exec mycontainer touch /app/check.txt && ls -l ./check.txt
# Debería mostrar tu UID del host (p. ej., 1000), no rootSolución de problemas: modos comunes de fallo de permisos
| Síntoma | Causa | Solución |
|---|---|---|
| Los archivos del bind mount pertenecen a root | El contenedor corrió como root | Fija el UID de la app al del host (user: "1000:1000") o un sudo chown -R $(id -u):$(id -g) ./data puntual |
COPY --chown=name falla al compilar |
El nombre aún no está en /etc/passwd en esa capa |
Usa el UID numérico: COPY --chown=1001:1001 |
| Puerto < 1024 denegado para no-root | Un no-root no puede abrir puertos privilegiados | --cap-add NET_BIND_SERVICE o usa un puerto alto |
Los usuarios desaparecieron tras docker compose up |
Los cambios en tiempo de ejecución son efímeros | Integra el usuario en el Dockerfile |
| El entrypoint necesita root para hacer chown de volúmenes | El usuario de la app no puede corregir la propiedad al arrancar | Usa el patrón entrypoint con gosu o un hook pre_start de Compose con user: root |
userns-remap oculta las imágenes existentes |
La reasignación enmascara el almacén de imágenes/contenedores | Actívalo solo en hosts nuevos; vuelve a descargar las imágenes |
| El UID del host macOS no coincide con el del contenedor | Los UIDs de la VM de Docker Desktop difieren | Prueba en un VPS Linux; fija el UID para producción |
¿Por qué mis archivos pertenecen a root después de que Docker escribe en ellos?
Cuando un contenedor corre como root (el valor por defecto) y escribe en un volumen montado, los archivos se crean con UID 0 en el host. Tu usuario del host (normalmente UID 1000) no puede editarlos ni borrarlos sin sudo.
Solución en la imagen: define USER 1000:1000 en tu Dockerfile (o user: "1000:1000" en Compose) para que el proceso corra como tu usuario del host.
Solución a posteriori: ejecuta sudo chown -R $(id -u):$(id -g) ./data en el host. Para evitar que vuelva a pasar, fija el UID en tu Dockerfile.
¿Por qué falla COPY --chown con un nombre de usuario?
COPY --chown=name:group resuelve el nombre contra /etc/passwd y /etc/group durante la compilación. Si el usuario aún no se ha creado en ese punto del Dockerfile (o el archivo no existe en la etapa de build), la compilación falla.
Solución: usa UIDs numéricos: COPY --chown=1001:1001. Funciona independientemente de si /etc/passwd tiene la entrada.
¿Por qué mi contenedor no-root no puede abrir el puerto 80?
En Linux, los puertos por debajo de 1024 son privilegiados — solo root (o los procesos con CAP_NET_BIND_SERVICE) pueden abrirlos. Por eso la imagen oficial de Nginx mantiene un proceso maestro root.
Opciones de solución:
--cap-add NET_BIND_SERVICEen el contenedor (concede solo la capacidad de bind)- Usa un puerto alto (8080, 8443) y mapea en Compose:
ports: ["8080:80"] - Usa un proxy inverso (Caddy, Traefik) que gestione los puertos 80/443 y reenvíe al puerto alto de tu app
Para más comandos y patrones de Docker, guarda la referencia de comandos esenciales de Docker.
Referencia rápida: comandos de creación de usuarios
| Tarea | Ubuntu/Debian | Alpine |
|---|---|---|
| Crear usuario con directorio home | useradd --no-log-init -m -s /bin/bash user |
adduser -D -s /bin/sh user |
| Crear grupo | groupadd -g 1001 appgroup |
addgroup -g 1001 appgroup |
| Crear usuario con UID/GID específicos | useradd --no-log-init -u 1001 -g appgroup -m user |
adduser -D -u 1001 -G appgroup user |
| Añadir usuario a un grupo | usermod -aG sudo user |
addgroup user sudo |
| Usuario por defecto en el Dockerfile | USER 1001:1001 |
USER 1001:1001 |
| Ejecutar como usuario específico | docker run -u 1001:1001 img |
Igual |
| Exec como usuario específico | docker exec -u user container cmd |
Igual |
| Sobrescribir usuario en Compose | user: "1001:1001" |
Igual |
| Grupos suplementarios en Compose | group_add: ["2000"] |
Igual |
| Copiar con propiedad | COPY --chown=1001:1001 src/ dest/ |
Igual |
TL;DR
- Crea siempre usuarios no-root para contenedores de producción —
USER 1001:1001en el Dockerfile - Fija el UID/GID (p. ej., 1001:1001) para evitar quebraderos de cabeza de permisos con volúmenes montados
- Usa
COPY --chown=1001:1001con UIDs numéricos — los nombres pueden fallar en tiempo de compilación - Verifica con
docker run --rm myimage idyls -len los bind mounts antes de desplegar - Usa gosu (no sudo) cuando tu entrypoint necesite root al arrancar para corregir la propiedad de los volúmenes
Otros artículos de Docker que pueden ayudarte en tu camino con Docker:


