--- title: "Despliegues blue-green sin caídas con Nginx y Docker" description: "Configura despliegues blue-green en Ubuntu 24.04 con Nginx y Docker: dos entornos, comprobación de salud, cambio de tráfico sin cortes y rollback en segundos." category: "Temas Avanzados" language: es source: https://cubepath.com/es/docs/temas-avanzados/despliegues-sin-tiempo-de-inactividad-con-blue-green updated: 2026-09-25 --- # Despliegues blue-green sin caídas con Nginx y Docker En un despliegue **blue-green** mantienes dos copias de tu aplicación: una (por ejemplo, *blue*) atiende el tráfico real mientras la otra (*green*) recibe la versión nueva. Cuando la versión nueva está arrancada y responde bien, cambias el tráfico de golpe hacia ella, y la antigua queda parada en reserva para volver atrás en segundos si algo falla. En este tutorial montarás este esquema en Ubuntu 24.04 con Docker para ejecutar los dos entornos y Nginx como proxy inverso que decide cuál de ellos recibe el tráfico, y automatizarás el cambio con un script de despliegue. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath. - Un usuario no root con privilegios `sudo`. - Docker Engine instalado desde el repositorio oficial. Si aún no lo tienes, sigue la guía de instalación de Docker en Ubuntu 24.04. - El puerto 80 accesible (con UFW activo lo abrirás en el paso 1). Como aplicación de ejemplo se usará la imagen oficial de Nginx en dos versiones (`nginx:1.26-alpine` y `nginx:1.27-alpine`). En tu caso serán dos versiones de la imagen de tu aplicación. ## Cómo funciona el cambio de tráfico El esquema de esta guía tiene tres piezas: | Pieza | Función | |---|---| | Contenedor `app-blue` | Escucha en `127.0.0.1:8081` | | Contenedor `app-green` | Escucha en `127.0.0.1:8082` | | Nginx en el host | Escucha en el puerto 80 y reenvía al `upstream` definido en `active.conf` | `active.conf` es un enlace simbólico que apunta a `blue.conf` o a `green.conf`. Cambiar de entorno consiste en cambiar el enlace y ejecutar `systemctl reload nginx`. En una recarga, Nginx arranca procesos nuevos con la configuración nueva y deja que los antiguos terminen las peticiones en curso antes de salir, así que no se corta ninguna conexión. ## Paso 1: Instalar Nginx y abrir el puerto 80 Instala Nginx desde los repositorios de Ubuntu: ```bash sudo apt update sudo apt install nginx ``` Si usas UFW, permite el tráfico HTTP: ```bash sudo ufw allow 'Nginx HTTP' ``` Desactiva el sitio por defecto, que también escucha en el puerto 80: ```bash sudo rm /etc/nginx/sites-enabled/default ``` ## Paso 2: Arrancar el entorno blue Arranca la versión actual de la aplicación como entorno blue. Publicar el puerto solo en `127.0.0.1` evita que el contenedor sea accesible desde Internet saltándose Nginx (Docker publica puertos por delante de las reglas de UFW): ```bash docker run -d --name app-blue --restart unless-stopped -p 127.0.0.1:8081:80 nginx:1.26-alpine ``` Comprueba que responde: ```bash curl -sI http://127.0.0.1:8081/ | grep -i '^server' ``` ```output Server: nginx/1.26.3 ``` ## Paso 3: Configurar Nginx con blue y green Crea un directorio para las definiciones de cada entorno: ```bash sudo mkdir -p /etc/nginx/bluegreen ``` Crea la definición del entorno blue: ```bash sudo nano /etc/nginx/bluegreen/blue.conf ``` ```nginx upstream app_active { server 127.0.0.1:8081; keepalive 16; } ``` Y la del entorno green: ```bash sudo nano /etc/nginx/bluegreen/green.conf ``` ```nginx upstream app_active { server 127.0.0.1:8082; keepalive 16; } ``` Ambos archivos definen el mismo `upstream` con distinto destino. Crea el enlace `active.conf` apuntando a blue: ```bash sudo ln -sfn /etc/nginx/bluegreen/blue.conf /etc/nginx/bluegreen/active.conf ``` Ahora crea el sitio que usa ese `upstream`: ```bash sudo nano /etc/nginx/sites-available/app ``` ```nginx include /etc/nginx/bluegreen/active.conf; server { listen 80 default_server; listen [::]:80 default_server; server_name _; location / { proxy_pass http://app_active; proxy_http_version 1.1; proxy_set_header Connection ""; 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; add_header X-Upstream $upstream_addr always; } } ``` - `proxy_http_version 1.1` y la cabecera `Connection` vacía permiten reutilizar conexiones con el backend gracias a `keepalive`. - La cabecera `X-Upstream` indica qué backend ha atendido cada petición. Es útil para verificar el cambio; puedes quitarla en producción. Si tienes un dominio, sustituye `server_name _;` por `server_name your_domain;` y añade HTTPS con Certbot como harías en cualquier sitio de Nginx. Activa el sitio, valida la sintaxis y recarga: ```bash sudo ln -s /etc/nginx/sites-available/app /etc/nginx/sites-enabled/app sudo nginx -t sudo systemctl reload nginx ``` ```output nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful ``` Comprueba que el tráfico llega a blue: ```bash curl -sI http://localhost/ | grep -i '^x-upstream' ``` ```output X-Upstream: 127.0.0.1:8081 ``` ## Paso 4: Crear el script de despliegue El despliegue siempre sigue los mismos pasos: detectar el entorno activo, arrancar la versión nueva en el otro, esperar a que responda, cambiar el enlace y recargar Nginx. Es un buen caso para un script, porque evita cambiar el tráfico a un entorno que no ha arrancado bien. ```bash sudo nano /usr/local/bin/bluegreen-deploy ``` ```bash #!/usr/bin/env bash # Despliega una imagen en el entorno inactivo y le cambia el tráfico. set -euo pipefail IMAGE="${1:?Uso: bluegreen-deploy }" APP_PORT="${APP_PORT:-80}" HEALTH_PATH="${HEALTH_PATH:-/}" CONF_DIR=/etc/nginx/bluegreen active="$(basename "$(readlink -f "$CONF_DIR/active.conf")" .conf)" if [[ "$active" == "blue" ]]; then target=green port=8082 else target=blue port=8081 fi echo "Entorno activo: $active. Desplegando $IMAGE en $target (puerto $port)." docker pull "$IMAGE" docker rm -f "app-$target" >/dev/null 2>&1 || true docker run -d --name "app-$target" --restart unless-stopped \ -p "127.0.0.1:$port:$APP_PORT" "$IMAGE" echo "Esperando a que $target responda en $HEALTH_PATH..." for attempt in $(seq 1 30); do if curl -fsS -o /dev/null "http://127.0.0.1:$port$HEALTH_PATH"; then break fi if [[ "$attempt" -eq 30 ]]; then echo "El entorno $target no responde. El tráfico sigue en $active." >&2 exit 1 fi sleep 2 done ln -sfn "$CONF_DIR/$target.conf" "$CONF_DIR/active.conf" if ! nginx -t 2>/dev/null; then ln -sfn "$CONF_DIR/$active.conf" "$CONF_DIR/active.conf" echo "La configuración de Nginx no es válida. Cambio revertido." >&2 exit 1 fi systemctl reload nginx echo "Tráfico cambiado a $target. $active sigue en marcha para un posible rollback." ``` Si tu aplicación escucha en otro puerto dentro del contenedor o tiene un endpoint de salud, pásalos como variables, por ejemplo `APP_PORT=3000 HEALTH_PATH=/health`. El script usa `curl -f`, que falla con cualquier respuesta 4xx o 5xx, así que solo cambia el tráfico si el endpoint devuelve un código correcto. Hazlo ejecutable: ```bash sudo chmod 755 /usr/local/bin/bluegreen-deploy ``` ## Paso 5: Desplegar la versión nueva Para comprobar que no se pierde ninguna petición, abre una segunda sesión SSH y lanza 300 peticiones seguidas durante unos 30 segundos, agrupando los códigos de respuesta: ```bash for i in $(seq 1 300); do curl -s -o /dev/null -w '%{http_code}\n' http://localhost/; sleep 0.1; done | sort | uniq -c ``` Mientras corre, despliega la versión nueva desde la primera sesión: ```bash sudo bluegreen-deploy nginx:1.27-alpine ``` ```output Entorno activo: blue. Desplegando nginx:1.27-alpine en green (puerto 8082). ... Esperando a que green responda en /... Tráfico cambiado a green. blue sigue en marcha para un posible rollback. ``` Cuando termine el bucle de la segunda sesión, todas las respuestas deben ser `200`: ```output 300 200 ``` Comprueba que el tráfico va ahora a green: ```bash curl -sI http://localhost/ | grep -i '^x-upstream' ``` ```output X-Upstream: 127.0.0.1:8082 ``` Y que ambos entornos siguen en marcha: ```bash docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}' ``` ```output NAMES IMAGE PORTS app-green nginx:1.27-alpine 127.0.0.1:8082->80/tcp app-blue nginx:1.26-alpine 127.0.0.1:8081->80/tcp ``` ## Paso 6: Hacer rollback Si la versión nueva da problemas, volver atrás es cambiar el enlace al entorno anterior, que sigue arrancado con la versión que funcionaba: ```bash sudo ln -sfn /etc/nginx/bluegreen/blue.conf /etc/nginx/bluegreen/active.conf sudo nginx -t && sudo systemctl reload nginx ``` ```bash curl -sI http://localhost/ | grep -i '^x-upstream' ``` ```output X-Upstream: 127.0.0.1:8081 ``` Cuando confirmes que la versión nueva es estable, puedes parar el entorno inactivo para liberar memoria. El siguiente despliegue lo volverá a crear: ```bash docker stop app-blue ``` ## Paso 7: Tener en cuenta la base de datos El cambio de tráfico es instantáneo, pero durante unos segundos las dos versiones conviven y, tras un rollback, la versión antigua vuelve a usar la base de datos que ya ha tocado la nueva. Por eso las migraciones deben ser compatibles con ambas versiones. La técnica habitual se llama **expandir y contraer**: 1. **Expandir**: la versión nueva solo añade (columnas nuevas que admiten `NULL` o tienen valor por defecto, tablas nuevas). La versión antigua sigue funcionando porque ignora lo que no conoce. 2. **Desplegar**: la versión nueva escribe en las columnas antiguas y en las nuevas. 3. **Contraer**: en un despliegue posterior, cuando ya no vas a volver a la versión antigua, eliminas las columnas que dejaron de usarse. Nunca renombres ni borres una columna en el mismo despliegue que cambia el código que la usa. Ejecuta las migraciones antes de cambiar el tráfico y solo si son de tipo "expandir". Tampoco guardes sesiones de usuario en la memoria de la aplicación: tras el cambio, los usuarios perderían la sesión. Guárdalas en Redis, en la base de datos o en cookies firmadas. ## El mismo patrón en Kubernetes Si tu aplicación corre en Kubernetes, la idea es idéntica: dos Deployments (por ejemplo con las etiquetas `version: blue` y `version: green`) y un Service cuyo `selector` decide a cuál van los pods. El cambio de tráfico es modificar ese selector: ```bash kubectl patch service app -p '{"spec":{"selector":{"app":"app","version":"green"}}}' ``` Antes de cambiar, comprueba que el Deployment nuevo está listo con `kubectl rollout status deployment/app-green`. ## Solución de problemas **`nginx: [emerg] duplicate upstream "app_active"`**: Nginx está cargando `blue.conf` y `green.conf` a la vez. Asegúrate de que solo se incluye `active.conf` y de que el directorio `bluegreen` no está dentro de `conf.d/` ni de `sites-enabled/`. **El script falla con `El entorno green no responde`**: revisa los logs del contenedor con `docker logs app-green`. Comprueba también que `APP_PORT` coincide con el puerto en el que escucha la aplicación dentro del contenedor y que `HEALTH_PATH` existe. **Aparecen errores 502 durante el cambio**: suele ocurrir si detienes el entorno antiguo inmediatamente después de recargar Nginx. Espera a que terminen las peticiones en curso antes de pararlo; el script lo deja en marcha a propósito. **`X-Upstream` no aparece en la respuesta**: comprueba que el sitio `app` está enlazado en `sites-enabled` y que no queda otro sitio con `default_server` en el puerto 80 (`sudo nginx -T | grep default_server`). ## Conclusión Has montado un despliegue blue-green con dos entornos en Docker y Nginx como conmutador, has automatizado el cambio con una comprobación de salud previa y has comprobado que no se pierde ninguna petición durante el cambio ni durante el rollback. Como siguientes pasos puedes: - Añadir HTTPS al sitio de Nginx con Certbot. - Llamar a `bluegreen-deploy` desde tu pipeline de CI/CD tras construir y publicar la imagen. - Añadir un endpoint `/health` a tu aplicación que compruebe también la conexión con la base de datos y usarlo como `HEALTH_PATH`.