Respuesta corta: no se puede. Docker no tiene una forma compatible de agregar un puerto publicado a un contenedor que ya está en ejecución. Los mapeos de puertos se fijan en la creación del contenedor, y ni docker run ni docker container update pueden cambiarlos después. La opción --publish-add solo existe para servicios de Swarm, no para contenedores independientes.
Si no puedes reiniciar el contenedor, estas son las opciones que realmente funcionan:
| Enfoque | ¿Agrega un mapeo de puerto Docker real? | ¿Funciona en Docker Desktop (macOS/Windows)? | ¿Sobrevive al reinicio? |
|---|---|---|---|
Contenedor sidecar con socat |
No (reenvío TCP) | Sí | Sí, si el sidecar se reinicia |
| Proxy inverso (Nginx/Traefik/HAProxy) | No (proxy) | Sí | Sí |
Regla DNAT de iptables del host |
No (regla NAT del host) | No, solo Linux nativo | No, a menos que se persista |
Recrear el contenedor con -p |
Sí | Sí | Sí |
El único enfoque que produce un puerto publicado real gestionado por Docker — que aparece en docker ps y docker port — es recrear el contenedor. Todo lo demás reenvía tráfico en una capa superior o inferior al registro de puertos de Docker. Elige en función de si puedes tolerar un reinicio y lee las advertencias a continuación antes de ejecutar algo en producción.
Este artículo explica cada método, los comandos exactos y dónde falla cada uno.
Antecedentes: cómo funciona el mapeo de puertos en Docker
Principios fundamentales del mapeo de puertos de contenedores
En Docker, la conexión entre un puerto interno del contenedor y el puerto de la máquina host se facilita mediante el mapeo de puertos. Normalmente, especificamos los mapeos de puertos usando los parámetros -p o --publish al iniciar un contenedor, como se muestra a continuación:
docker run -d -p 8080:80 nginx
El comando anterior mapea el puerto 8080 de la máquina host al puerto 80 dentro del contenedor. Como resultado, los usuarios externos pueden acceder al servicio web que se ejecuta dentro del contenedor a través del puerto 8080 del host.
Por qué Docker no lo permite
Una vez que un contenedor se ha iniciado, Docker generalmente no admite agregar nuevos mapeos de puertos dinámicamente. En otras palabras, los mapeos de puertos iniciales permanecen fijos durante todo el ciclo de vida del contenedor. Si necesitas agregar más mapeos de puertos, el enfoque tradicional implica detener y reiniciar el contenedor, lo que puede interrumpir los servicios y no es aceptable en entornos de producción.
Las cuatro soluciones
Para agregar dinámicamente mapeos de puertos a un contenedor en ejecución, se pueden emplear varios métodos:
2.1 Contenedor sidecar que reenvía el puerto (recomendado)
Un contenedor separado publica el nuevo puerto del host y reenvía el tráfico al contenedor original a través de una red Docker compartida. Esta es la opción más segura porque el contenedor original nunca se toca.
Una corrección importante primero: no puedes combinar --network container:<name> con -p. La documentación de redes de Docker establece que --publish, --publish-all y --expose no son compatibles con contenedores que usan el modo de red container:, porque dicho contenedor no tiene su propio espacio de nombres de red para mapear puertos. Cualquier guía que te diga que ejecutes docker run -p 8081:81 --net container:tu-contenedor ... es incorrecta, y Docker lo rechazará.
El patrón funcional utiliza una red definida por el usuario para que el sidecar pueda alcanzar el objetivo por nombre de contenedor:
# 1. Crear una red y conectar el contenedor en ejecución a ella (sin necesidad de reinicio)
docker network create app-net
docker network connect app-net tu-contenedor
# 2. Iniciar un sidecar socat que publique 8081 y reenvíe al puerto 81 del objetivo
docker run -d --name port-sidecar \
--network app-net \
--restart unless-stopped \
-p 8081:81 \
alpine/socat \
TCP-LISTEN:81,fork,reuseaddr TCP:tu-contenedor:81
Ten en cuenta que docker network connect funciona en un contenedor en ejecución, por lo que el paso 1 no causa tiempo de inactividad. El sidecar escucha en el puerto 81 dentro de su propio espacio de nombres, y -p 8081:81 lo publica en el host.
Advertencias:
- Esto es un reenvío TCP, no un mapeo de puerto Docker. No aparecerá en
docker port tu-contenedor. alpine/socatreenvía solo TCP. Para UDP usaUDP-LISTEN/UDP, y para HTTP con enrutamiento basado en host prefiere Nginx, Traefik, Caddy o HAProxy.- Agrega
--restart unless-stopped(como arriba) o el reenvío desaparecerá al reiniciar. - El salto adicional cuesta una pequeña cantidad de latencia y agrega un proceso más a monitorear.
2.2 Regla DNAT de iptables del host (solo Linux nativo)
En un host Linux nativo, puedes agregar una regla DNAT que reenvíe un puerto del host a la IP interna del contenedor:
# Obtener la IP del contenedor
CONTAINER_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' tu-contenedor)
# Reenviar el puerto 8081 del host al puerto 81 del contenedor
sudo iptables -t nat -A DOCKER -p tcp --dport 8081 \
-j DNAT --to-destination "${CONTAINER_IP}:81"
Esto te da un control fino, pero conlleva el mayor riesgo operativo de todos los métodos aquí:
- Docker Desktop en macOS y Windows no funcionará de esta manera. Los contenedores se ejecutan dentro de una VM Linux, por lo que las reglas de
iptablesen tu máquina no afectan la ruta de red de Docker. Este método es solo para Linux nativo. - Las reglas no persisten. Se pierden al reiniciar, al recargar el cortafuegos o en una transición nftables/iptables. Usa el mecanismo de persistencia de cortafuegos de tu distribución si necesitas que sobrevivan.
- Docker es dueño de la cadena
DOCKER. Docker crea y gestiona estas reglas a partir de la configuración de puertos de los contenedores en ejecución, y su documentación dice que no debes modificar las reglas que Docker crea. Para filtrado personalizado, Docker designaDOCKER-USERcomo el lugar para reglas definidas por el usuario, porque las reglas añadidas aFORWARDse procesan después de las propias de Docker. - Las IPs de los contenedores no son estables. La dirección cambia cuando se recrea el contenedor, dejando una regla obsoleta que reenvía en silencio a ninguna parte.
- Omite el registro de Docker. El puerto no aparecerá en
docker psnidocker port.
2.3 Ejecutar socat directamente en el host
También puedes ejecutar socat como un proceso normal del host en lugar de en un contenedor:
socat TCP-LISTEN:8081,fork,reuseaddr TCP:<container_ip>:81
Esto funciona en Linux nativo, donde la IP del contenedor es enrutable desde el host. En Docker Desktop para macOS y Windows, la IP del contenedor no es alcanzable desde tu máquina, así que usa el sidecar de la sección 2.1. De cualquier manera, necesitas un supervisor de procesos (systemd, o --restart en el sidecar) para que sobreviva a reinicios, ya que un proceso socat simple muere con su shell.
2.4 Recrear el servicio con Docker Compose
Este es el único método que produce un puerto publicado real, gestionado por Docker. Agrega el mapeo a compose.yaml:
services:
app:
image: tu-imagen:tag
ports:
- "8081:81"
Luego recrea solo ese servicio:
docker compose up -d app
Compose recrea el contenedor, por lo que hay una breve interrupción; esto no es un cambio en vivo. Ten en cuenta que Docker moderno usa docker compose (un subcomando), no el binario independiente más antiguo docker-compose. Mantén el estado en volúmenes con nombre o montajes bind para que sobreviva a la recreación.
2.5 Editar los archivos de configuración internos de Docker (no recomendado)
Encontrarás consejos para editar manualmente /var/lib/docker/containers/<id>/config.v2.json y hostconfig.json para agregar una entrada PortBindings, y luego reiniciar el daemon. A veces funciona, pero trátalo como un último recurso:
- Estos son archivos de implementación internos sin garantías de estabilidad, no una API compatible. El formato puede cambiar entre versiones de Docker.
- El daemon mantiene el estado del contenedor en memoria. Editar archivos bajo un daemon en ejecución corre el riesgo de que tus cambios sean sobrescritos, y las ediciones parciales pueden dejar el estado de red del contenedor inconsistente con sus metadatos.
- Debes detener el daemon antes de editar, lo que afecta a todos los contenedores en el host.
- Si
live-restoreestá habilitado, los contenedores siguen ejecutándose durante un reinicio del daemon, pero eso no aplica un mapeo de puerto editado. Live-restore no cambia la regla de que un nuevo puerto publicado requiere recreación del contenedor.
Si has llegado al punto de editar el estado del daemon manualmente, recrear el contenedor con la opción -p correcta es más rápido y seguro.
Conclusión
Docker no admite agregar un puerto publicado a un contenedor en ejecución, y ninguna solución alternativa cambia eso. Lo que los métodos anteriores te ofrecen es una forma de enrutar nuevo tráfico a un contenedor que no puedes reiniciar.
Elige en este orden:
- ¿Puedes tolerar un reinicio corto? Recrea el contenedor con la opción
-pcorrecta, o agregaports:acompose.yamly ejecutadocker compose up -d. Este es el único método que produce un mapeo de puerto Docker real. - ¿No puedes reiniciar? Usa el sidecar
socatde la sección 2.1, o un proxy inverso si necesitas enrutamiento HTTP, TLS o verificaciones de salud. - ¿Linux nativo y necesitas un reenvío temporal rápido? Una regla DNAT de
iptablesfunciona, pero persístela deliberadamente y espera que Docker interfiera con sus propias cadenas. - Evita editar manualmente los archivos de configuración del daemon.
Si los mapeos de puertos cambian a menudo, eso suele ser una señal de diseño: coloca un proxy inverso delante del servicio desde el principio, y deja que él gestione los puertos orientados al host para que el ciclo de vida del contenedor y el enrutamiento permanezcan independientes.
Fuentes: Docker: Publicación de puertos, Docker: Modos de red de contenedores, Docker: Filtrado de paquetes y cortafuegos, Docker e iptables, docker container port.
Puedes visitar Novita AI para instancias GPU y APIs de modelos.
