Mapeo de puertos entre host y contenedor con la opción '-p' de Docker

DUGLAS MORENODUGLAS MORENO 👁 17

Introducción

Cuando trabajás con contenedores Docker, una de las primeras cosas que necesitás hacer es exponer los servicios que corren dentro del contenedor para que puedan ser accesibles desde tu máquina host o desde la red. La forma más directa de lograr esto es usando la bandera -p (o --publish) al momento de crear o ejecutar un contenedor. En este artículo vamos a explicar en detalle cómo funciona el mapeo de puertos, cuál es su sintaxis, qué opciones tenés disponibles y cómo aplicarlo en un caso práctico: levantar un contenedor de Nginx y acceder a su página de bienvenida desde el navegador.

Este tutorial está pensado para desarrolladores, devops y cualquier persona que ya tenga Docker instalado y quiera comprender mejor la comunicación entre el host y los contenedores. Al final vas a saber:

  • Qué significa publicar un puerto y por qué es necesario.
  • Cómo escribir la regla -p <host>:<contenedor> y sus variantes.
  • Qué pasa si omitís alguna de las partes o usás el protocolo UDP.
  • Cómo verificar el mapeo con comandos de Docker.
  • Qué salida esperada obtener al hacer un request HTTP a Nginx.
  • Buenas prácticas y errores comunes a evitar.

¿Qué es el mapeo de puertos en Docker?

Por defecto, los contenedores están aislados del host en cuanto a red se refiere. Cada contenedor tiene su propia pila de red, con una dirección IP interna y un conjunto de puertos que solo son accesibles desde dentro del propio contenedor. Si un proceso dentro del contenedor escucha en el puerto 80 (como hace Nginx), ese puerto no está disponible en tu máquina host a menos que le indiques explícitamente a Docker que lo publique.

La opción -p crea una regla de reenvío (port forwarding) en el motor de Docker: todo tráfico que llegue a una dirección y puerto del host será redirigido al puerto especificado dentro del contenedor. De esta forma, podés usar localhost:8080 en tu navegador y llegar al servicio que corre en el puerto 80 del contenedor.

Sintaxis básica de -p

La forma más simple es:

docker run -p <puerto_host>:<puerto_contenedor> <imagen>

Donde:

  • <puerto_host> es el número de puerto en la interfaz de red del host donde querés escuchar.
  • <puerto_contenedor> es el puerto dentro del contenedor al que el proceso está vinculado.

Ambos valores son obligatorios si querés especificar una correspondencia exacta. También podés omitir uno de ellos y dejar que Docker elija automáticamente:

  • -p <puerto_host>:<puerto_contenedor> – mapeo explícito (lo más usado).
  • -p <puerto_contenedor> – Docker asigna un puerto aleatorio en el host y lo vincula al puerto del contenedor.
  • -p <puerto_host>: – Docker asigna un puerto aleatorio dentro del contenedor y lo escucha en el puerto especificado del host (poco común).

Además, podés indicar el protocolo agregando /udp o /tcp al final del puerto del contenedor (el valor por defecto es tcp):

docker run -p 8080:80/udp <imagen>

Mapeo de múltiples puertos

Un contenedor puede exponer más de un servicio. Para publicar varios puertos simplemente repetís la bandera -p tantas veces como sea necesario:

docker run -p 8080:80 -p 8443:443 -p 9000:9000 <imagen>

En este ejemplo, el contenedor tendría un servidor HTTP en el puerto 80, un HTTPS en el 443 y otro servicio cualquiera en el 9000, todos accesibles desde el host mediante los puertos 8080, 8443 y 9000 respectivamente.

Publicación automática con -P

Si no te importa qué puertos específicos se usan en el host y querés que Docker asigne puertos libres automáticamente, podés usar la opción -P (mayúscula). Docker mapeará todos los puertos expuestos en la imagen (esos declarados con EXPOSE en el Dockerfile) a puertos aleatorios del host en el rango efímero (por lo general 32768‑60999). Luego podés consultar los puertos asignados con docker port <contenedor>.

Ejemplo práctico: levantar Nginx y acceder desde el navegador

Vamos a poner todo esto en práctica con un contenedor oficial de Nginx. Nginx escucha por defecto en el puerto 80 dentro del contenedor. Vamos a mapearlo al puerto 8080 de nuestra máquina host para que podamos acceder a http://localhost:8080 desde el navegador.

Paso 1: Ejecutar el contenedor

Abrí una terminal y ejecutá:

docker run -d --name mi_nginx -p 8080:80 nginx:latest

Explicación de cada parte:

  • docker run: crea y arranca un nuevo contenedor.
  • -d: ejecuta el contenedor en modo detached (en segundo plano), así la terminal queda libre.
  • --name mi_nginx: asigna un nombre amigable al contenedor para poder referirnos a él más fácilmente.
  • -p 8080:80: publica el puerto 80 del contenedor en el puerto 8080 del host.
  • nginx:latest: la imagen que vamos a usar (Docker la descargará desde Docker Hub si no la tenés localmente).

Paso 2: Verificar que el contenedor está corriendo

Podés listar los contenedores activos con:

docker ps

La salida esperada se parece a esto:

CONTAINER ID   IMAGE         COMMAND                  CREATED          STATUS          PORTS                    NAMES
a1b2c3d4e5f6   nginx:latest  
"nginx -g 'daemon of…"
   2 minutes ago   Up 2 minutes   0.0.0.0:8080->80/tcp   mi_nginx

En la columna PORTS vemos el mapeo 0.0.0.0:8080->80/tcp, lo que confirma que Docker está reenviando todo tráfico que llegue a cualquier interfaz del host en el puerto 8080 al puerto 80 del contenedor usando el protocolo tcp.

También podés usar el comando específico para inspeccionar puertos:

docker port mi_nginx

Que devolverá algo como:

80/tcp -> 0.0.0.0:8080

Paso 3: Acceder desde el navegador o con curl

Ahora, si abrís tu navegador y visitás http://localhost:8080, deberías ver la página de bienvenida de Nginx:

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/
ginx.org/"">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/
ginx.com/"">nginx.com</a>.

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Si preferís usar la línea de comandos, podés hacer un request con curl:

curl -s http://localhost:8080 | head -5

La salida esperada (las primeras cinco líneas) sería:

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>

Esto confirma que el servicio está respondiendo correctamente.

Paso 4: Detener y eliminar el contenedor (opcional)

Cuando hayas terminado, podés detener y borrar el contenedor con:

docker stop mi_nginx
docker rm mi_nginx

Buenas prácticas al usar -p

  1. Usá nombres descriptivos para los contenedores (--name) para que sea más fácil identificarlos en docker ps y en los comandos de inspección.
  2. Evitar puertos privilegiados (<1024) en el host si no tenés permisos de root. En Linux, los puertos bajo 1024 requieren privilegios elevados; es mejor mapear a puertos altos como 8080, 8443, etc.
  3. Documentar el mapeo en un archivo docker-compose.yml cuando trabajás con varios servicios. En Compose la sintaxis es idéntica:
    services:
      web:
        image: nginx:latest
        ports:
          - "8080:80"
  4. Verificar que el proceso dentro del contenedor realmente esté escuchando en el puerto especificado. A veces la imagen puede cambiar el puerto por defecto (por ejemplo, un contenedor de Tomcat que usa 8080 internamente). En esos casos ajustá el mapeo según corresponda.
  5. Usar el protocolo correcto. La mayoría de los servicios web usan TCP, pero si trabajás con DNS, SNMP o algún servicio UDP, agregá /udp a la regla.
  6. No exponer puertos innecesarios. Cada puerto publicado aumenta la superficie de ataque. Publicá solo lo que realmente necesitás.
  7. Revisar los logs si algo falla. Si el contenedor arranca pero no respondés, ejecutá docker logs <nombre> para ver si el proceso inició correctamente o si hubo errores de enlace al puerto.

Errores comunes y cómo solucionarlos

  1. "Error response from daemon: Bind for 0.0.0.0:8080 failed: port is already allocated"
    • Esto ocurre cuando otro proceso ya está usando el puerto 8080 en el host. Podés cambiar el puerto host (por ejemplo a 8081) o detener el proceso que lo está ocupando (lsof -i :8080 o netstat -tulpn).
  2. "Error: No such image: nginx:latest"
    • La imagen no está disponible localmente y Docker no pudo descargarla (quizás por problemas de red o nombre incorrecto). Verificá tu conexión a internet y que el nombre de la imagen esté bien escrito.
  3. El contenedor arranca pero la página no carga
    • Posibles causas:
      • El proceso dentro del contenedor no está escuchando en el puerto que creíste (revisá el Dockerfile o el entrypoint).
      • El firewall del host está bloqueando el puerto. En Linux con ufw podés hacer sudo ufw allow 8080/tcp.
      • El contenedor se detuvo inmediatamente debido a un error de entrada; revisá los logs con docker logs mi_nginx.
  4. Puerto mapeado pero el tráfico no llega al contenedor
    • Puede que el contenedor esté usando una red personalizada (por ejemplo, --network none) que aísla completamente su pila de red. En ese caso, -p no tiene efecto. Asegurate de no usar opciones de red que anulen el publicación de puertos.
  5. Usando -P y no sabiendo qué puerto se asignó
    • Luego de lanzar el contenedor con -P, ejecutá docker port <nombre> para ver el mapeo que Docker eligió.

Conclusión

El mapeo de puertos con la opción -p es una de las herramientas más fundamentales y poderosas de Docker para conectar el mundo exterior con los servicios aislados dentro de los contenedores. Hemos visto cómo funciona la sintaxis básica, cómo especificar múltiples puertos, cuándo usar la publicación automática con -P y cómo verificar el estado de los mapeos mediante docker ps y docker port.

El ejemplo práctico de levantar un contenedor de Nginx y acceder a su página de bienvenida desde el navegador (o con curl) ilustra todo el flujo: desde la ejecución del contenedor, pasando por la confirmación del mapeo, hasta la validación del servicio respondiendo correctamente.

Al aplicar las buenas prácticas descritas y prestar atención a los errores más habituales, podrás evitar problemas comunes y asegurar que tus aplicaciones contenedorizadas sean accesibles de forma segura y predecible.

Con este conocimiento ya estás listo para publicar cualquier tipo de servicio – web, APIs, bases de datos, juegos, etc. – dentro de Docker y hacer que llegue a tus usuarios o a otros componentes de tu arquitectura sin complicaciones.

¡A probarlo y a seguir explorando el ecosistema de contenedores!

Comentarios (0)

Sé el primero en comentar.

Dejá tu comentario