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.
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_nameylocationbloques.
🛠️ 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/
';
}
}
}
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
Explicación del docker-compose.yml:
nginxservice:build:Indica a Docker Compose que construya la imagen Nginx usando elDockerfileen 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 queapp1yapp2se inicien antes quenginx.networks:Asigna el servicionginxa la redapp-network.
app1yapp2services: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 localapp1(oapp2) en el directorio de servicio web predeterminado de Nginx dentro del contenedor.networks:Asigna estos servicios a la redapp-networkpara que puedan comunicarse connginx.
networkssection: Define una red de puente (app-network) que permite la comunicación entre los contenedores de forma aislada.
✅ 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 endocker-compose.yml.--build: Fuerza la reconstrucción de las imágenes (especialmente útil si has modificado elDockerfilede 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
serverpara el puerto 80 que simplemente redirige todo el tráfico a HTTPS. - Un nuevo bloque
serverpara el puerto 443 con las directivasssl_certificateyssl_certificate_keyapuntando 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/.
🔄 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:
app1ahora se divide enapp1ayapp1b.nginxdepende 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.
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.
🗑️ 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
- Migrando Aplicaciones a Contenedores Docker: Del Monolito a la Contenerización Paso a Pasointermediate18 min
- Automatización CI/CD con Docker y Jenkins: Construyendo un Pipeline Robustointermediate20 min
- Asegurando Contenedores Docker: Mejores Prácticas y Herramientas Esencialesintermediate15 min
- Orquestación de Contenedores Docker con Docker Swarm: Escalabilidad y Alta Disponibilidadintermediate25 min
- Desarrollo Local Ágil: Usando Docker para Entornos de Desarrollo Coherentes y Aisladosintermediate18 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!