Un Proxy Inverso es un servidor intermedio que se coloca en la frontera de tu red para recibir todas las peticiones de los usuarios y repartirlas hacia tus contenedores.
Imagina que acabas de alquilar tu servidor VPS. Quieres desplegar tu primera aplicación (una web en React) y la expones en el puerto 80. Todo funciona de maravilla.
Pero al día siguiente decides añadir una segunda aplicación: una API en Node.js. Vas a tu docker-compose.yml, le dices que también exponga el puerto 80 para que el público pueda entrar, ejecutas el comando y… ¡BAM! Error:
Bind for 0.0.0.0:80 failed: port is already allocated
El puerto 80 (el estándar de internet) es como la puerta principal de tu servidor. Físicamente, solo un programa puede tener la llave de esa puerta a la vez. Si tu web ya la está usando, tu API se queda fuera.
La solución cutre es poner la API en el puerto 8080 y obligar a tus usuarios a escribir mi-api.com:8080. La solución razonable es usar Nginx.
Hoy vamos a aprender cómo configurar este servidor web clásico como Proxy Inverso nativo en Docker para manejar múltiples dominios en una sola máquina 👇.
La Arquitectura: Nginx al rescate
Para solucionar la colisión de puertos, vamos a cambiar por completo la topología de nuestra red.
En lugar de que nuestras aplicaciones den la cara a internet, vamos a esconderlas. Ninguna de tus aplicaciones (ni la web, ni la API) expondrá puertos al exterior.
En su lugar, crearemos un nuevo contenedor dedicado exclusivamente a Nginx. Este contenedor será el único que exponga el puerto 80.
Su trabajo será escuchar todas las peticiones, leer el dominio que el usuario ha escrito en su navegador (ej. api.midominio.com) y enrutar el tráfico internamente hacia el contenedor correspondiente.
¿Para qué necesitas un Proxy Inverso?
- Eliminar las colisiones de puertos: Puedes tener 50 aplicaciones diferentes en tu servidor, todas respondiendo por el puerto 80.
- Seguridad perimetral: Tus aplicaciones están aisladas del internet público. Solo reciben tráfico filtrado a través de Nginx.
- Punto de entrada único: Tienes una sola puerta de entrada, lo cual facilita implementar caché, balanceo de carga o certificados de seguridad en el futuro.
El archivo docker-compose.yml
Vamos a ver cómo se traduce esto a código. Crearemos un entorno con tres contenedores: la Web, la API y nuestro portero Nginx.
services:
# 1. Nuestra web principal (Escondida)
mi_web:
image: nginx:alpine
# NO usamos "ports". Solo existe en la red interna.
# 2. Nuestra API (Escondida)
mi_api:
image: node:18-alpine
command: node index.js
# Tampoco usamos "ports".
# 3. El Proxy Inverso (El único visible)
proxy:
image: nginx:alpine
ports:
- "80:80" # El único contenedor que habla con internet
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro # Inyectamos la configuración
depends_on:
- mi_web
- mi_api
Fíjate en el detalle clave: mi_web y mi_api no tienen la directiva ports. Solo el proxy la tiene.
Configurando el enrutamiento
Ahora tenemos que decirle a Nginx cómo repartir el tráfico. Para ello, hemos montado un volumen que inyecta nuestro archivo nginx.conf dentro del contenedor del proxy.
Vamos a configurarlo usando bloques server (lo que clásicamente se conoce como Virtual Hosts):
events {}
http {
# Regla 1: Si alguien entra por web.midominio.com
server {
listen 80;
server_name web.midominio.com;
location / {
# Redirigimos el tráfico al contenedor "mi_web" al puerto 80 interno
proxy_pass http://mi_web:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Regla 2: Si alguien entra por api.midominio.com
server {
listen 80;
server_name api.midominio.com;
location / {
# Redirigimos el tráfico al contenedor "mi_api" al puerto 3000 interno
proxy_pass http://mi_api:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
Analizando la configuración:
server_name: Nginx lee la cabecera HTTP de la petición para saber qué dominio ha tecleado el usuario.proxy_pass: Es la directiva estrella. Le dice a Nginx: “coge esta petición y envíasela a este otro sitio”. Como puedes ver, usamos directamente el nombre de los contenedores (http://mi_webyhttp://mi_api) gracias al DNS interno de Docker.proxy_set_header: Asegura que Nginx pase la información original del usuario (como su IP o el dominio solicitado) al contenedor final, para que tu aplicación sepa quién la está visitando realmente.
