tutoriales.com

Configurando un Servidor Proxy Inverso con Nginx en Contenedores Docker: Mejora la Seguridad y el Rendimiento

Este tutorial te guiará paso a paso en la configuración de Nginx como un servidor proxy inverso dentro de contenedores Docker. Descubre cómo centralizar el acceso a tus aplicaciones, mejorar la seguridad con SSL/TLS y optimizar el rendimiento mediante el balanceo de carga, todo ello utilizando las ventajas de la contenerización.

Intermedio18 min de lectura11 views
Reportar error

Un proxy inverso es un componente esencial en la arquitectura de muchas aplicaciones web modernas, especialmente en entornos contenerizados. Actúa como un intermediario entre los clientes de internet y tus servidores de aplicación, reenviando las solicitudes de los clientes a los servidores apropiados y devolviendo las respuestas. En el contexto de Docker, Nginx es una elección popular y robusta para esta tarea, ofreciendo flexibilidad, alto rendimiento y una gran cantidad de características.

Este tutorial te proporcionará una guía completa para configurar Nginx como un proxy inverso dentro de contenedores Docker. Cubriremos desde los conceptos básicos hasta configuraciones avanzadas como SSL/TLS, balanceo de carga y gestión de nombres de dominio, asegurando que tus aplicaciones estén bien protegidas y sean altamente disponibles.

🎯 ¿Por qué usar un Proxy Inverso con Docker y Nginx?

La implementación de un proxy inverso con Nginx frente a tus aplicaciones Docker ofrece múltiples beneficios:

  • Seguridad Mejorada: Nginx puede manejar la terminación SSL/TLS, protegiendo tus aplicaciones backend de la exposición directa a Internet. También puede filtrar peticiones maliciosas.
  • Balanceo de Carga: Distribuye el tráfico entre múltiples instancias de tu aplicación, mejorando la disponibilidad y la capacidad de respuesta.
  • Centralización del Acceso: Permite acceder a múltiples servicios o microservicios que residen en diferentes contenedores o puertos a través de un único punto de entrada y un puerto estándar (como el 80 o el 443).
  • Almacenamiento en Caché (Caching): Nginx puede almacenar en caché contenido estático, reduciendo la carga en tus servidores de aplicaciones y acelerando la entrega de contenido.
  • Reescritura de URL y Redirecciones: Ofrece gran flexibilidad para manipular URL y redirigir tráfico.
  • Gestión de Múltiples Dominios/Subdominios: Puedes servir diferentes aplicaciones desde un único servidor Nginx usando server_name y location bloques.
💡 Consejo: Considera un proxy inverso como la 'puerta de entrada' de tu infraestructura, controlando quién entra y cómo se distribuye el tráfico internamente.

🛠️ Prerequisitos

Para seguir este tutorial, necesitarás:

  • Docker Desktop instalado (para Windows/macOS) o Docker Engine y Docker Compose (para Linux).
  • Conocimientos básicos de Docker y Docker Compose.
  • Un editor de texto (VS Code, Sublime Text, etc.).
  • Un navegador web para probar la configuración.

📖 Conceptos Clave

Antes de sumergirnos en la configuración, es útil entender algunos términos:

  • Proxy Inverso: Un servidor que retransmite las peticiones de los clientes a uno o más servidores, actuando como intermediario. El cliente solo conoce al proxy inverso.
  • Proxy Adelantado (Forward Proxy): Un servidor que actúa como intermediario para las peticiones de un grupo de clientes hacia Internet, protegiendo la identidad de los clientes.
  • Nginx: Un servidor web de alto rendimiento, proxy inverso, proxy para correo electrónico y balanceador de carga.
  • Contenedor Docker: Una unidad ligera, portable y ejecutable de software que empaqueta todo lo necesario para ejecutar una aplicación, incluyendo el código, un entorno de ejecución, herramientas del sistema, bibliotecas y configuraciones.
  • Docker Compose: Una herramienta para definir y ejecutar aplicaciones multi-contenedor Docker. Utiliza archivos YAML para configurar los servicios de la aplicación.

✨ Estructura del Proyecto

Organizaremos nuestro proyecto de la siguiente manera:

. 
├── docker-compose.yml
├── nginx
│   ├── Dockerfile
│   └── default.conf
├── app1
│   └── index.html
└── app2
    └── index.html

Aquí, nginx contendrá los archivos de configuración de Nginx y un Dockerfile para construir su imagen. app1 y app2 simularán dos aplicaciones web simples que Nginx dirigirá.

🚀 Paso 1: Configurando las Aplicaciones Backend Simples

Primero, crearemos dos aplicaciones web muy básicas que Nginx actuará como proxy. Estas serán simples archivos HTML servidos por un servidor web Nginx ligero.

app1/index.html

Crea el directorio app1 y dentro, el archivo index.html con el siguiente contenido:

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Aplicación 1</title>
    <style>
        body { font-family: Arial, sans-serif; background-color: #e0f7fa; color: #004d40; text-align: center; padding-top: 50px; }
        h1 { color: #00796b; }
        p { font-size: 1.2em; }
    </style>
</head>
<body>
    <h1>¡Hola desde la Aplicación 1!</h1>
    <p>Este es el contenido servido por la Aplicación 1.</p>
</body>
</html>

app2/index.html

De manera similar, crea el directorio app2 y dentro, el archivo index.html con este contenido:

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Aplicación 2</title>
    <style>
        body { font-family: Arial, sans-serif; background-color: #fff3e0; color: #e65100; text-align: center; padding-top: 50px; }
        h1 { color: #f57c00; }
        p { font-size: 1.2em; }
    </style>
</head>
<body>
    <h1>¡Saludos desde la Aplicación 2!</h1>
    <p>Aquí está el contenido que te ofrece la Aplicación 2.</p>
</body>
</html>

Estas dos aplicaciones serán la base para probar nuestro proxy inverso.

🚀 Paso 2: Configurando el Contenedor Nginx como Proxy Inverso

Ahora, configuraremos Nginx para que actúe como proxy para app1 y app2.

nginx/default.conf

Este archivo será la configuración principal de Nginx. Crea el directorio nginx y dentro, el archivo default.conf:

worker_processes auto;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;
    error_log /var/log/nginx/error.log warn;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    upstream app1_backend {
        server app1:80;
    }

    upstream app2_backend {
        server app2:80;
    }

    server {
        listen 80;
        server_name localhost;

        location /app1/ {
            proxy_pass http://app1_backend/;
            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;
        }

        location /app2/ {
            proxy_pass http://app2_backend/;
            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;
        }

        location / {
            # Un simple "Hello World" para la ruta raíz o un error 404
            return 200 'Bienvenido al proxy Nginx! Usa /app1/ o /app2/
';
        }
    }
}
📌 Nota: En este archivo, definimos dos `upstream` blocks, `app1_backend` y `app2_backend`, que apuntan a los nombres de los servicios Docker (`app1` y `app2`) y sus puertos internos (80). Luego, en el bloque `server`, usamos `location /app1/` y `location /app2/` para dirigir el tráfico a los respectivos backends.

nginx/Dockerfile

Crea el archivo Dockerfile dentro del directorio nginx:

FROM nginx:alpine
COPY default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Este Dockerfile es muy sencillo: toma la imagen base nginx:alpine, copia nuestra configuración personalizada default.conf y expone el puerto 80.

🚀 Paso 3: Orquestando con Docker Compose

Ahora uniremos todo usando docker-compose.yml.

docker-compose.yml

Crea el archivo docker-compose.yml en el directorio raíz de tu proyecto:

version: '3.8'

services:
  nginx:
    build:
      context: ./nginx
      dockerfile: Dockerfile
    ports:
      - "80:80"
    depends_on:
      - app1
      - app2
    networks:
      - app-network

  app1:
    image: nginx:alpine
    volumes:
      - ./app1:/usr/share/nginx/html
    networks:
      - app-network

  app2:
    image: nginx:alpine
    volumes:
      - ./app2:/usr/share/nginx/html
    networks:
      - app-network

networks:
  app-network:
    driver: bridge
🔥 Importante: Asegúrate de que los nombres de los servicios (`app1`, `app2`) coincidan con los nombres utilizados en los bloques `upstream` de tu configuración Nginx (`app1_backend`, `app2_backend`). Docker Compose se encarga de la resolución de DNS entre servicios en la misma red.

Explicación del docker-compose.yml:

  • nginx service:
    • build: Indica a Docker Compose que construya la imagen Nginx usando el Dockerfile en el directorio ./nginx.
    • ports: "80:80": Mapea el puerto 80 del host al puerto 80 del contenedor Nginx, permitiendo el acceso externo.
    • depends_on: Asegura que app1 y app2 se inicien antes que nginx.
    • networks: Asigna el servicio nginx a la red app-network.
  • app1 y app2 services:
    • image: nginx:alpine: Utiliza la imagen oficial de Nginx para servir nuestras aplicaciones simples.
    • volumes: ./app1:/usr/share/nginx/html: Monta el contenido del directorio local app1 (o app2) en el directorio de servicio web predeterminado de Nginx dentro del contenedor.
    • networks: Asigna estos servicios a la red app-network para que puedan comunicarse con nginx.
  • networks section: Define una red de puente (app-network) que permite la comunicación entre los contenedores de forma aislada.
Red Docker (app-network) Cliente Nginx Puerto 80:80 Contenedor App 1 app1:5000 Contenedor App 2 app2:5000 HTTP Orquestado con Docker Compose

✅ Paso 4: Ejecutando y Probando

Una vez que todos los archivos están en su lugar, navega a la raíz de tu proyecto en la terminal y ejecuta:

docker-compose up --build -d
  • up: Inicia los servicios definidos en docker-compose.yml.
  • --build: Fuerza la reconstrucción de las imágenes (especialmente útil si has modificado el Dockerfile de Nginx).
  • -d: Ejecuta los contenedores en segundo plano (detached mode).

Verifica que todos los contenedores estén corriendo:

docker-compose ps

Deberías ver algo similar a esto:

      Name                     Command               State           Ports         
----------------------------------------------------------------------------------
app1_app1_1           /docker-entrypoint.sh ngin ...   Up      80/tcp              
app1_app2_1           /docker-entrypoint.sh ngin ...   Up      80/tcp              
app1_nginx_1          /docker-entrypoint.sh ngin ...   Up      0.0.0.0:80->80/tcp

Ahora, abre tu navegador web y visita las siguientes URLs:

  • http://localhost/ -> Deberías ver el mensaje de bienvenida del proxy Nginx.
  • http://localhost/app1/ -> Deberías ver el contenido de la Aplicación 1.
  • http://localhost/app2/ -> Deberías ver el contenido de la Aplicación 2.

¡Felicidades! Has configurado con éxito Nginx como un proxy inverso para tus aplicaciones Docker.

🔐 Paso 5: Configuración SSL/TLS con Certificados Auto-Firmados

Para entornos de desarrollo o pruebas, es útil configurar SSL/TLS con certificados auto-firmados. Para producción, usarías Let's Encrypt o un certificado de una CA confiable.

Generar Certificados Auto-Firmados

Primero, crea un directorio para los certificados en nginx/certs y genera los certificados:

mkdir -p nginx/certs
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout nginx/certs/nginx.key -out nginx/certs/nginx.crt -subj "/C=ES/ST=Madrid/L=Madrid/O=TuEmpresa/OU=TuUnidad/CN=localhost"

Esto creará nginx.key (clave privada) y nginx.crt (certificado) dentro de nginx/certs.

Actualizar nginx/default.conf para SSL/TLS

Modifica nginx/default.conf para incluir la configuración SSL/TLS. Añade un nuevo bloque server para HTTPS o modifica el existente:

worker_processes auto;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;
    error_log /var/log/nginx/error.log warn;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    upstream app1_backend {
        server app1:80;
    }

    upstream app2_backend {
        server app2:80;
    }

    server {
        listen 80;
        server_name localhost;
        return 301 https://$host$request_uri; # Redirige HTTP a HTTPS
    }

    server {
        listen 443 ssl;
        server_name localhost;

        ssl_certificate /etc/nginx/certs/nginx.crt;
        ssl_certificate_key /etc/nginx/certs/nginx.key;

        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout 5m;
        ssl_ciphers HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers on;

        location /app1/ {
            proxy_pass http://app1_backend/;
            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;
        }

        location /app2/ {
            proxy_pass http://app2_backend/;
            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;
        }

        location / {
            return 200 'Bienvenido al proxy Nginx (HTTPS)! Usa /app1/ o /app2/
';
        }
    }
}

Cambios clave:

  • Un bloque server para el puerto 80 que simplemente redirige todo el tráfico a HTTPS.
  • Un nuevo bloque server para el puerto 443 con las directivas ssl_certificate y ssl_certificate_key apuntando a los certificados generados.

Actualizar nginx/Dockerfile para montar certificados

FROM nginx:alpine
COPY default.conf /etc/nginx/conf.d/default.conf
COPY certs/nginx.crt /etc/nginx/certs/nginx.crt
COPY certs/nginx.key /etc/nginx/certs/nginx.key
EXPOSE 80
EXPOSE 443
CMD ["nginx", "-g", "daemon off;"]

Hemos añadido las líneas COPY para los certificados y EXPOSE 443.

Actualizar docker-compose.yml para el puerto HTTPS

Modifica el servicio nginx para exponer también el puerto 443:

version: '3.8'

services:
  nginx:
    build:
      context: ./nginx
      dockerfile: Dockerfile
    ports:
      - "80:80"
      - "443:443"
    depends_on:
      - app1
      - app2
    networks:
      - app-network

  app1:
    image: nginx:alpine
    volumes:
      - ./app1:/usr/share/nginx/html
    networks:
      - app-network

  app2:
    image: nginx:alpine
    volumes:
      - ./app2:/usr/share/nginx/html
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

Reiniciar y Probar HTTPS

Detén y reinicia tus contenedores para aplicar los cambios:

docker-compose down
docker-compose up --build -d

Ahora, visita https://localhost/. Tu navegador te advertirá sobre un certificado no válido (porque es auto-firmado), pero podrás proceder. Luego, prueba https://localhost/app1/ y https://localhost/app2/.

⚠️ Advertencia: Nunca uses certificados auto-firmados en producción. Para entornos de producción, obtén certificados de una autoridad de certificación reconocida (como Let's Encrypt para certificados gratuitos).

🔄 Paso 6: Implementando Balanceo de Carga (Round Robin)

El balanceo de carga es crucial para la escalabilidad y alta disponibilidad. Crearemos múltiples instancias de app1 y Nginx las distribuirá.

Modificar docker-compose.yml para múltiples instancias

Modifica el servicio app1 para tener múltiples réplicas usando deploy.replicas (esto requiere Docker Swarm o Kubernetes para funcionar correctamente en producción, pero Docker Compose lo simulará bien para desarrollo si el backend se escala de forma manual en el docker-compose.yml)

En este ejemplo simple con Docker Compose, simularemos múltiples instancias definiendo explícitamente app1a y app1b.

version: '3.8'

services:
  nginx:
    build:
      context: ./nginx
      dockerfile: Dockerfile
    ports:
      - "80:80"
      - "443:443"
    depends_on:
      - app1a
      - app1b
      - app2
    networks:
      - app-network

  app1a:
    image: nginx:alpine
    volumes:
      - ./app1:/usr/share/nginx/html
    networks:
      - app-network
    container_name: app1a

  app1b:
    image: nginx:alpine
    volumes:
      - ./app1:/usr/share/nginx/html
    networks:
      - app-network
    container_name: app1b

  app2:
    image: nginx:alpine
    volumes:
      - ./app2:/usr/share/nginx/html
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

Cambios:

  • app1 ahora se divide en app1a y app1b.
  • nginx depende de ambos.

Modificar nginx/default.conf para balanceo de carga

Actualiza el bloque upstream de app1_backend para incluir ambas instancias:

# ... (otras configuraciones)

http {
    # ... (otras configuraciones)

    upstream app1_backend {
        server app1a:80;
        server app1b:80;
    }

    upstream app2_backend {
        server app2:80;
    }

    # ... (bloques server 80 y 443 con sus location /app1/ y /app2/ como antes)
}

Ahora, app1_backend tiene dos servidores. Nginx utilizará por defecto el algoritmo de balanceo de carga round robin, distribuyendo las solicitudes de forma equitativa entre app1a y app1b.

Probar el Balanceo de Carga

Detén y reinicia los contenedores:

docker-compose down
docker-compose up --build -d

Para ver el balanceo de carga en acción, podrías modificar app1/index.html para que cada instancia muestre un identificador único. Por ejemplo, en app1a/index.html pon "¡Hola desde la Aplicación 1 (Instancia A)!" y en app1b/index.html pon "¡Hola desde la Aplicación 1 (Instancia B)!" (tendrías que crear un app1a y app1b directorios separados con sus propios index.html y montar esos directorios en lugar del único app1 en el docker-compose.yml).

Para simplicidad en este tutorial, si recargas https://localhost/app1/ varias veces, verás el mismo contenido porque app1a y app1b sirven el mismo index.html. Sin embargo, Nginx estará balanceando la carga entre ellos internamente. Puedes verificar esto mirando los logs de los contenedores:

docker logs app1a
docker logs app1b
docker logs nginx

En los logs de nginx, verás peticiones proxy_pass a app1a:80 y app1b:80 alternando.

✨ **Opciones Avanzadas de Balanceo de Carga Nginx** Además de `round robin` (por defecto), Nginx soporta:
  • least_conn: La solicitud se pasa al servidor con el menor número de conexiones activas.
  • ip_hash: Distribuye las solicitudes basándose en la dirección IP del cliente, asegurando que el mismo cliente siempre vaya al mismo servidor. Útil para sesiones pegajosas.
  • hash key [consistent]: Distribuye basándose en una clave definida por el usuario. Requiere el módulo Nginx Plus para algunas funcionalidades avanzadas.

Para usar, por ejemplo, least_conn:

upstream app1_backend {
    least_conn;
    server app1a:80;
    server app1b:80;
}

🌐 Paso 7: Múltiples Dominios con Nginx

Imagina que quieres servir app1 desde app1.local y app2 desde app2.local.

Modificar nginx/default.conf para múltiples dominios

Necesitamos dos bloques server diferentes, cada uno con su server_name y sus propias configuraciones de location.

# ... (configuraciones globales, upstream blocks)

http {
    # ... (configuraciones http generales, upstream blocks)

    server {
        listen 80;
        server_name app1.local;
        return 301 https://$host$request_uri;
    }

    server {
        listen 443 ssl;
        server_name app1.local;

        ssl_certificate /etc/nginx/certs/nginx.crt;
        ssl_certificate_key /etc/nginx/certs/nginx.key;
        # ... (otras configuraciones SSL)

        location / {
            proxy_pass http://app1_backend/;
            proxy_set_header Host $host;
            # ... (otros headers proxy)
        }
    }

    server {
        listen 80;
        server_name app2.local;
        return 301 https://$host$request_uri;
    }

    server {
        listen 443 ssl;
        server_name app2.local;

        ssl_certificate /etc/nginx/certs/nginx.crt;
        ssl_certificate_key /etc/nginx/certs/nginx.key;
        # ... (otras configuraciones SSL)

        location / {
            proxy_pass http://app2_backend/;
            proxy_set_header Host $host;
            # ... (otros headers proxy)
        }
    }
}

En este escenario, cada bloque server está dedicado a un server_name específico. La location / ahora actúa como la ruta raíz para cada dominio.

📌 Nota: Para que `app1.local` y `app2.local` funcionen, necesitarás añadir estas entradas a tu archivo `hosts` local (`/etc/hosts` en Linux/macOS, `C:\Windows\System32\drivers\etc\hosts` en Windows): ```text 127.0.0.1 app1.local 127.0.0.1 app2.local ```

Reiniciar y Probar Dominios

Detén y reinicia los contenedores:

docker-compose down
docker-compose up --build -d

Ahora, en tu navegador:

  • https://app1.local/ -> Deberías ver el contenido de la Aplicación 1.
  • https://app2.local/ -> Deberías ver el contenido de la Aplicación 2.

Este patrón es fundamental para servir múltiples aplicaciones o microservicios desde una única instancia de Nginx, que es una práctica común en arquitecturas modernas.


📉 Monitorización Básica y Logs

Nginx genera logs de acceso y error que son valiosos para la depuración y monitorización. En nuestra configuración default.conf, ya hemos definido rutas para access_log y error_log.

Para ver los logs del contenedor Nginx en tiempo real:

docker-compose logs -f nginx

Esto mostrará las peticiones HTTP que Nginx está procesando, incluyendo la dirección IP del cliente, la URL solicitada, el código de estado HTTP y más.

💡 Consejo: Integra estos logs con herramientas de monitorización y agregación de logs como ELK Stack (Elasticsearch, Logstash, Kibana) o Grafana Loki para una observabilidad completa de tu infraestructura.

🗑️ Limpieza

Para detener y eliminar todos los contenedores, redes y volúmenes creados por docker-compose:

docker-compose down --volumes --rmi all
  • --volumes: Elimina los volúmenes anónimos.
  • --rmi all: Elimina las imágenes creadas por Docker Compose.

Esto deja tu sistema limpio de los recursos del tutorial.

conclusión ✨

Has completado una guía exhaustiva sobre cómo configurar Nginx como un proxy inverso dentro de contenedores Docker. Hemos cubierto desde la configuración básica para redirigir tráfico hasta la implementación de SSL/TLS y balanceo de carga, e incluso cómo gestionar múltiples dominios.

Nginx, combinado con Docker Compose, proporciona una solución potente y flexible para gestionar el acceso a tus aplicaciones contenerizadas. Esta configuración es un pilar para construir arquitecturas robustas, escalables y seguras en el mundo de DevOps. ¡Ahora tienes las herramientas para llevar tus despliegues de Docker al siguiente nivel!

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!