Civil & Structural Engineering
Deploying Multi-container Web Applications with Docker Compose and Nginx
Table of Contents
Introducción
Las aplicaciones web modernas raramente funcionan como un único proceso monolítico. En lugar de ello, están compuestas de múltiples servicios de colaboración: un frontend, una API, una base de datos, una caché, una cola de mensajes y tal vez un corredor de trabajo. Gestionando manualmente el ciclo de vida de cada contenedor, su red y sus dependencias rápidamente se convierte en un programa de errores y tiempo de consumo.
Prerrequisitos
Antes de bucear, asegúrese de tener instalado en su sistema los siguientes:
- Docker Engine (versión 20.10 o más reciente) – Install Docker
- Docker Compose (V2 ahora está integrado en Docker CLI; verifique con ) – Install Docker Compose
- familiaridad básica] con comandos terminales y sintaxis YAML.
Opcionalmente tenga un nombre de dominio apuntando a su servidor si planea seguir la sección de configuración SSL.
Comprender el Docker Componer en la profundidad
Docker Compose no es sólo un simple envoltorio “docker run”. Es una herramienta declarativa que le permite definir servicios, redes, volúmenes, variables ambientales, políticas de reiniciamiento, y controles de salud en un solo archivo llamado . La especificación de la compostura (ahora parte de la Componer Especation) estandariza cómo se definen diferentes aplicaciones de la nube
Servicios
Cada imagen de contenedor, sus puertos, volúmenes, ambiente y dependencias se definen bajo la llave . Los servicios pueden comunicarse entre sí a través de una red de puentes dedicada que Compose crea automáticamente. Esto significa que nunca necesita abrir puertos de host para la comunicación entre servicios — sólo el proxy inverso necesita exponer puertos al mundo exterior.
Redes
Por defecto, Compose crea una única red para todos los servicios, dándoles un descubrimiento de servicio basado en DNS usando el nombre de servicio como el nombre de host. Por ejemplo, un servicio llamado puede ser alcanzado por otros servicios simplemente como . También puede definir redes personalizadas para el aislamiento, por ejemplo, poniendo sólo el proxy inverso en una red pública y guardando contenedores de bases de datos en una red interna.
networks:
frontend:
backend:
services:
nginx:
networks:
- frontend
app:
networks:
- frontend
- backend
db:
networks:
- backend
Volumen
Los volúmenes son el mecanismo preferido para la persistencia de datos generados por contenedores Docker. En Compose, puede declarar volúmenes nombrados en el nivel superior y montarlos en servicios. Esto es esencial para bases de datos, cargas de archivos y cualquier servicio estatal.
volumes:
db_data:
services:
postgres:
image: postgres:16
volumes:
- db_data:/var/lib/postgresql/data
Medio ambiente
Examinar la configuración utilizando variables ambientales. Puede codificarlas en el archivo Compose para el desarrollo, pero para la producción debe utilizar archivos o secretos Docker. Componga automáticamente un archivo ubicado en el mismo directorio si no especifica .
services:
api:
image: my-api:latest
env_file:
- ./api.env
environment:
- NODE_ENV=production
Configurar Nginx como un Proxy Inverso – Avanzado
Nginx brilla como un proxy inverso debido a su alto rendimiento, baja huella de memoria y conjunto de características ricas. En una arquitectura multicontenedor, Nginx se sienta al borde, acepta las solicitudes de clientes, y las envía al servicio adecuado basado en la solicitud URI, encabezados, o incluso el contenido del cuerpo. También puede manejar la terminación SSL, caching, limiting y equilibrio de carga.
Proxy inverso básico con múltiples corrientes
Aquí hay una configuración más completa que demuestra cómo dividir el tráfico entre dos aplicaciones diferentes, incluyendo el manejo de activos estáticos y WebSockets:
upstream app1_upstream {
server app1:8000;
}
upstream app2_upstream {
server app2:8001;
}
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# App 1 – main web frontend
location / {
proxy_pass http://app1_upstream;
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;
}
# App 2 – admin dashboard
location /admin/ {
proxy_pass http://app2_upstream/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# WebSocket support (e.g., for live updates)
location /ws/ {
proxy_pass http://app1_upstream;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Static assets – serve directly for better performance
location /static/ {
alias /var/www/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
}
Observe el uso de bloques . Permiten definir un grupo de servidores para el balance de carga. Incluso con un solo servidor, el uso de un grupo de corriente permite añadir más réplicas más tarde sin tocar el bloque del servidor.
SSL/TLS con Let's Encrypt
Para la producción, servir HTTPS no es negociable. Puede automatizar la renovación de certificados utilizando Certbot o acme.sh] dentro de un contenedor sidecar. Un patrón común es montar los certificados como volúmenes en el contenedor Nginx. Por ejemplo:
services:
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./certs:/etc/nginx/certs:ro
- ./static:/var/www/static:ro
ports:
- "80:80"
- "443:443"
depends_on:
- app1
- app2
Para la renovación automática, puede añadir un contenedor de acompañantes como o que ejecuta un trabajo de cron y recarga Nginx cuando se refrescan los certificados.
Equilibración de carga y cheques de salud
Cuando escalas un servicio a múltiples réplicas, Nginx puede distribuir solicitudes utilizando la robina redonda, las conexiones mínimas o la hachilla IP. Combina esto con la opción de Docker Compose :
upstream api_servers {
least_conn;
server api:80 max_fails=3 fail_timeout=30s;
}
Para detectar contenedores poco saludables, Nginx puede configurarse con (requiere NGINX Plus o utilizar de la versión de código abierto con una pequeña solución). Una alternativa más simple es confiar en los controles de salud de Docker y sólo registrar los contenedores que pasan.
Construyendo el archivo de composición de Docker – Un ejemplo en el mundo real
Combinar todo en un archivo práctico de compostura para una aplicación web que consiste en una API Node.js, un frontend React servido por Nginx, una base de datos PostgreSQL y un caché Redis. El contenedor Nginx servirá archivos estáticos y solicitudes proxy API.
version: "3.9"
services:
nginx:
image: nginx:alpine
container_name: reverse-proxy
restart: unless-stopped
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./frontend/build:/var/www/frontend:ro
- ./certs:/etc/nginx/certs:ro
ports:
- "80:80"
- "443:443"
depends_on:
- api
- frontend
networks:
- public
frontend:
image: my-frontend:latest
# In production, frontend is built into static files and served by Nginx
# This container may run a dev server, but Nginx will bypass it for static files
container_name: frontend-dev
ports:
- "3000:3000"
networks:
- public
# healthcheck: ...
api:
image: my-api:latest
container_name: api-server
restart: unless-stopped
env_file:
- ./api/.env.production
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
networks:
- public
- internal
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4000/health"]
interval: 30s
timeout: 10s
retries: 3
postgres:
image: postgres:16-alpine
container_name: database
restart: unless-stopped
environment:
POSTGRES_USER: app_user
POSTGRES_DB: app_db
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
volumes:
- pgdata:/var/lib/postgresql/data
secrets:
- db_password
networks:
- internal
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app_user"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: cache
restart: unless-stopped
volumes:
- redisdata:/data
networks:
- internal
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
volumes:
pgdata:
redisdata:
networks:
public:
internal:
internal: true
secrets:
db_password:
file: ./secrets/db_password.txt
Puntos clave en este archivo:
- Aislamiento de red: La red se define como , lo que significa que sólo los servicios conectados a ella pueden comunicarse. La base de datos y el caché no son accesibles desde fuera del sobrecarga de Docker.
- Secrets: Los datos sensibles como contraseñas se almacenan como secretos de Docker, montados como archivos dentro del contenedor, lo que evita filtrarlos en variables ambientales que pueden ser expuestos a través de .
- ] Controles de salud: Los servicios declaran cheques de salud para que pueda esperar la condición . Nginx comenzará sólo después de que la API esté sana.
- Activos estadísticos: La salida de la construcción de frontend se monta directamente en Nginx, permitiendo que Nginx sirva archivos estáticos sin golpear el servidor dev de frontend. Esto mejora el rendimiento y separa las preocupaciones.
Implementación de la Aplicación – Paso a Paso
Con el archivo Compose y la configuración Nginx listo, el despliegue implica unos comandos simples. Primero, asegurar que todos los archivos de configuración estén en su lugar:
project/
├── docker-compose.yml
├── nginx/
│ └── nginx.conf
├── api/
│ └── .env.production
├── frontend/
│ └── build/
├── certs/
│ └── (fullchain.pem, privkey.pem)
└── secrets/
└── db_password.txt
Entonces corre:
docker compose pull # pull latest images
docker compose up -d # start all services in background
docker compose ps # verify services are running
docker compose logs -f # tail logs for all services
Una vez que todo está en marcha, prueba la aplicación visitando tu dominio. Revisa los registros Nginx para confirmar las solicitudes están siendo proxied correctamente. Si necesitas actualizar las variables de configuración o entorno, edita los archivos pertinentes y ejecuta:
docker compose up -d --no-deps --build nginx # rebuild only nginx if config changed
Para los despliegues de tiempo cero, considere utilizar verde azul] o actualizaciones de la inscripción con la característica de Compose y controles de salud Nginx.
Escalada y Tuning de Rendimiento
Docker Compose hace que sea trivial para escalar servicios apátridas como la API:
docker compose up -d --scale api=3
Ahora se están ejecutando tres réplicas del contenedor API. Nginx, configurado con el bloque de corriente superior, distribuirá automáticamente las solicitudes entre ellos. Para mejorar aún más el rendimiento:
- Permitir compresión Gzip en Nginx para respuestas basadas en texto.
- Conecte los encabezados adecuados de caché] para los activos estáticos (como se muestra en el ejemplo).
- Use un CDN] para la entrega global de archivos estáticos.
- Tune Nginx worker processes to match CPU cores:
- : :
Para bases de datos, asegúrese de tener conexión a la piscina en su API y considere utilizar PgBouncer como un contenedor de coches laterales.
Problemas comunes
Incluso con una configuración bien estructurada, las cosas pueden ir mal. Aquí hay frecuentes trampas y cómo arreglarlas:
- El contenedor no puede alcanzar otro servicio por nombre: Comprueba que ambos servicios están en la misma red Docker. Usa para probar.
- Nginx devuelve 502 Bad Gateway: El contenedor de corriente puede no estar funcionando o no está escuchando en el puerto esperado. Verificar con . También asegurar que la URL proxy pass incluye el protocolo y el puerto correctos.
- Errores de permiso con volúmenes: Los archivos montados en contenedores heredan la propiedad del host. Utilice los espacios de nombres de usuario o establezca la directiva en Nginx y el contenedor de servicio.
- Certificado SSL no renovando: Si se utiliza certbot standalone, se asegura que los puertos 80 y 443 no estén bloqueados por Nginx durante la renovación. Utilice un método webroot o certbot con docker para evitar conflictos.
- variables de entorno no cargadas: Verificar el archivo está en la ubicación correcta o utilizar la directiva correctamente. Puede imprimir el env dentro del contenedor con .
Prácticas óptimas de seguridad
La ejecución de aplicaciones multicontainer en la producción requiere vigilancia:
- Nunca se ejecutan contenedores como root a menos que sea necesario. Utilice la directiva o especifique en el fichero Docker.
- Mantén las imágenes hasta la fecha con un análisis de vulnerabilidad regular. Usa imágenes de base oficiales y versiones de pin.
- Exposición de red restringida: Sólo exponga los puertos proxy inversos a Internet. Todos los demás servicios deben estar en redes internas.
- Use secrets management] para contraseñas, claves de API y fichas. Los secretos de Docker son un buen comienzo; para configuraciones más grandes, considere HashiCorp Vault.
- Permitir Docker Content Trust] verificar las firmas de imágenes.
- Regularly log and audit] container activity using o una solución de registro centralizada como el pila ELK.
- Aplicar límites de recursos en su archivo Compose para evitar que un solo contenedor muera de hambre a otros:
services:
api:
deploy:
resources:
limits:
cpus: '0.50'
memory: 256M
reservations:
cpus: '0.25'
memory: 128M
Estos límites se respetan cuando se utiliza el enjambre de Docker; para la composición simple, se aplican con (aunque es mejor utilizar enjambre o K8s para orquestación de producción).
Conclusión
[LTF] Explorar la documentación de dos modos, y crear una plataforma de gran alcance, escalable y sostenible para el despliegue de aplicaciones web multicontenedores. Al definir su pila en un solo archivo YAML, usted consigue la paridad del medio ambiente del desarrollo a la producción. Nginx, como un proxy inverso, proporciona un punto central para la terminación SSL, el funcionamiento de tráfico, el equilibrio de carga y el caché.