docker-nginx-reverse-proxy

Nginx como Proxy Inverso en Docker

  • 4 min

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

Copied!

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;
        }
    }
}

Copied!

Analizando la configuración:

  1. server_name: Nginx lee la cabecera HTTP de la petición para saber qué dominio ha tecleado el usuario.
  2. 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_web y http://mi_api) gracias al DNS interno de Docker.
  3. 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.