Deploy blue-green com Docker Compose para aplicações PHP monolito (Nginx, DB e healthcheck)
Deploy & DevOps · Por Kevin Marcondes · Publicado em · 9 min de leitura
Este tutorial ensina deploy blue-green com Docker Compose para aplicações PHP monolito. Você terá Nginx na frente, dois containers PHP-FPM (Blue e Green), um banco de dados compartilhado, healthchecks HTTP e um script para alternar o tráfego sem interrupção.
Pré-requisitos e arquitetura do blue-green com Compose
Blue-green mantém duas versões do app rodando: Blue (ativa) e Green (preparada). Trocamos o tráfego quando a Green estiver saudável. Se algo falhar, voltamos rapidamente para a Blue.
Neste deploy blue-green com Docker Compose para aplicações PHP monolito, você terá os componentes abaixo. Instale Docker e Compose antes de começar. Se precisar, siga: instalar Docker e Docker Compose.
A estratégia exige migrações compatíveis. Planeje mudanças no banco em duas etapas para permitir rollback. Nunca aplique migrações destrutivas sem plano de retorno.
- Um Nginx único, expondo a porta 80.
- Dois containers PHP-FPM: app_blue e app_green.
- Um banco de dados compartilhado (ex.: MariaDB).
- Um volume compartilhado para uploads/arquivos do app.
- Healthchecks HTTP: /health, /health-blue e /health-green.
Estrutura de pastas do projeto
Organize os arquivos assim. Vamos versionar a infra junto do app. Ajuste nomes conforme seu projeto.
Criaremos um script para alternar o tráfego copiando a configuração de upstream ativa.
project/
├── .env
├── docker-compose.yml
├── app/
│ ├── Dockerfile
│ ├── src/
│ │ ├── public/
│ │ │ └── health.php
│ │ └── index.php # se usar front controller
├── nginx/
│ └── conf.d/
│ ├── nginx.conf
│ ├── upstream_blue.conf
│ └── upstream_green.conf
└── deploy/
└── switch_traffic.sh
Dockerfile do monolito PHP (PHP-FPM)
Usaremos um Dockerfile enxuto para buildar dependências com Composer e executar PHP-FPM. Adapte extensões e comandos à sua aplicação. Para reduzir tamanho e camadas, veja também imagens Docker leves com multistage.
# app/Dockerfile
FROM php:8.2-fpm-alpine AS runtime
# Extensões comuns — ajuste conforme seu banco (pdo_mysql ou pdo_pgsql)
RUN apk add --no-cache git oniguruma-dev libzip-dev \
&& docker-php-ext-install pdo_mysql opcache
# Composer para instalar dependências quando houver composer.json
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
COPY src/ .
# Instale dependências do PHP se existir composer.json
RUN if [ -f composer.json ]; then \
composer install --no-interaction --no-dev --prefer-dist --optimize-autoloader; \
fi \
&& chown -R www-data:www-data /var/www/html
EXPOSE 9000
CMD ["php-fpm", "-F"]
Configuração do Nginx com upstreams Blue/Green e endpoints de health
Nginx encaminha requisições ao upstream definido em upstream_active.conf. Criamos também rotas fixas para checar Blue e Green separadamente. A troca de tráfego alterna o arquivo de upstream ativo e recarrega o Nginx.
# nginx/conf.d/nginx.conf
# Upstreams fixos para health dos dois lados
upstream php_blue { server app_blue:9000; }
upstream php_green { server app_green:9000; }
# Upstream ativo — definido por upstream_active.conf (criado pelo switch)
# Este arquivo deve declarar: upstream php_upstream { server app_blue:9000; } ou app_green
include /etc/nginx/conf.d/upstream_active.conf;
server {
listen 80;
server_name _;
# Health ativo (aponta para php_upstream definido via include)
location = /health {
fastcgi_pass php_upstream;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/html/public/health.php;
}
# Healths dedicados
location = /health-blue {
fastcgi_pass php_blue;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/html/public/health.php;
}
location = /health-green {
fastcgi_pass php_green;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/html/public/health.php;
}
# Roteie tudo para seu front controller
location / {
try_files $uri /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass php_upstream; # definido no include
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;
}
}
# nginx/conf.d/upstream_blue.conf
upstream php_upstream { server app_blue:9000; }
# nginx/conf.d/upstream_green.conf
upstream php_upstream { server app_green:9000; }
docker-compose.yml: Blue/Green, DB, volumes e healthcheck
O Compose inicia Nginx, os dois apps e o banco. Usamos um volume compartilhado para uploads. O healthcheck do Nginx verifica os endpoints locais. Para publicar imagens em um registry privado, veja: registry Docker privado.
# docker-compose.yml
version: "3.9"
services:
nginx:
image: nginx:1.25-alpine
depends_on:
- app_blue
- app_green
ports:
- "80:80"
volumes:
- ./nginx/conf.d/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./nginx/conf.d/upstream_active.conf:/etc/nginx/conf.d/upstream_active.conf:ro
# Diretório de uploads/arquivos compartilhados
- app_shared:/var/www/html/storage
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost/health >/dev/null && wget -qO- http://localhost/health-blue >/dev/null && wget -qO- http://localhost/health-green >/dev/null"]
interval: 10s
timeout: 3s
retries: 5
networks: [web]
app_blue:
build:
context: ./app
container_name: app_blue
environment:
APP_COLOR: blue
APP_VERSION: v1
DB_HOST: db
DB_NAME: app
DB_USER: app
DB_PASS: secret
volumes:
- app_shared:/var/www/html/storage
networks: [web]
app_green:
build:
context: ./app
container_name: app_green
environment:
APP_COLOR: green
APP_VERSION: v2
DB_HOST: db
DB_NAME: app
DB_USER: app
DB_PASS: secret
volumes:
- app_shared:/var/www/html/storage
networks: [web]
db:
image: mariadb:10.11
environment:
MARIADB_DATABASE: app
MARIADB_USER: app
MARIADB_PASSWORD: secret
MARIADB_ROOT_PASSWORD: rootsecret
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 10
networks: [web]
volumes:
db_data:
app_shared:
networks:
web:
Endpoint de health check no PHP (com verificação do banco)
O health.php retorna 200 quando o app e o banco respondem. Também exibe versão e cor, úteis para validar a troca. Ajuste driver e DSN se usar outro banco (ex.: PostgreSQL).
<?php
// app/src/public/health.php
header('Content-Type: application/json');
$color = getenv('APP_COLOR') ?: 'unknown';
$version = getenv('APP_VERSION') ?: 'unknown';
$dbHost = getenv('DB_HOST') ?: 'db';
$dbName = getenv('DB_NAME') ?: 'app';
$dbUser = getenv('DB_USER') ?: 'app';
$dbPass = getenv('DB_PASS') ?: 'secret';
$status = [
'ok' => true,
'color' => $color,
'version' => $version,
'db' => 'unknown'
];
try {
$pdo = new PDO("mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4", $dbUser, $dbPass, [
PDO::ATTR_TIMEOUT => 2,
]);
$pdo->query('SELECT 1');
$status['db'] = 'up';
} catch (Throwable $e) {
http_response_code(503);
$status['ok'] = false;
$status['db'] = 'down';
$status['error'] = 'db';
}
echo json_encode($status);
Subir ambiente, fazer o primeiro deploy (Blue) e preparar Green
Hora de subir os serviços, apontar o upstream ativo para Blue e validar. Depois, publicamos a Green, validamos e deixamos pronta para troca. Cuidado: se houver migrações, aplique-as de forma compatível com as duas versões.
# 1) Gerar upstream ativo inicial (Blue)
cp -f nginx/conf.d/upstream_blue.conf nginx/conf.d/upstream_active.conf
# 2) Subir stack
docker compose up -d --build db app_blue app_green nginx
# 3) Verificar saúde
curl -s http://localhost/health | jq .
curl -s http://localhost/health-blue | jq .
curl -s http://localhost/health-green | jq .
# 4) Popular DB, seeds e migrações (exemplo genérico)
# docker compose exec app_blue php artisan migrate --force # se usar Laravel
# docker compose exec app_blue php artisan db:seed --force
# 5) Preparar versão Green (atualize código e re-build)
# Atualize app/src/ com sua nova versão
docker compose build app_green
# 6) Validar saúde da Green
curl -s http://localhost/health-green | jq .
Trocar tráfego e rollback seguro
Usaremos um script simples para alternar o upstream e recarregar o Nginx. No deploy blue-green com Docker Compose para aplicações PHP monolito, a troca é rápida e reversível. Para monitorar depois do deploy, veja Prometheus e Grafana com Docker Compose.
- Script aceita blue ou green.
- A troca é atômica do ponto de vista de Nginx: reload é rápido.
- Rollback é apenas repetir a troca para a outra cor.
- Mantenha migrações compatíveis com as duas versões.
# deploy/switch_traffic.sh
#!/usr/bin/env bash
set -euo pipefail
COLOR="${1:-}"
if [[ "$COLOR" != "blue" && "$COLOR" != "green" ]]; then
echo "Uso: $0 blue|green"; exit 1
fi
cp -f "nginx/conf.d/upstream_${COLOR}.conf" "nginx/conf.d/upstream_active.conf"
docker compose exec -T nginx nginx -s reload
echo "Tráfego direcionado para: $COLOR"
# Exemplos de uso:
# bash deploy/switch_traffic.sh green # troca para Green
# bash deploy/switch_traffic.sh blue # rollback para Blue
Como confirmar que funcionou e solução de problemas
Confirme a troca inspecionando a saída do health. Ela deve mostrar a cor e a versão esperadas. Veja também logs e latência após o deploy. Alguns problemas comuns e correções:
Para automatizar tudo em CI/CD, integre o build, validação de health e switch no pipeline. Um exemplo prático: pipeline de deploy automático.
- Verificação: curl http://SEU_HOST/health deve exibir {"color":"green","version":"..."} após a troca.
- Uploads sumiram: garanta que ambos containers montam o mesmo volume de uploads (app_shared).
- Sessões de usuário: use sessões compartilhadas (ex.: Redis) para evitar logout no switch.
- Migrações: use etapas compatíveis com as duas versões; adicione colunas novas antes de usá-las no código.
- Static assets: sirva por CDN/objeto ou monte um volume compartilhado para arquivos públicos gerados.
- TLS: termine TLS no host ou adicione um proxy TLS na frente; este exemplo usa HTTP para simplificar.
Perguntas frequentes
Blue-green funciona sem Kubernetes?
Sim. Este tutorial usa apenas Docker Compose, Nginx e um script para alternar o upstream. Você mantém duas versões rodando e troca o tráfego com reload do Nginx.
Como faço rollback rápido?
Execute o script apontando para a cor anterior. Como ambas versões estão ativas, a reversão é imediata. Ainda assim, monitore erros e latência após o rollback.
E as migrações do banco no blue-green?
Aplique migrações compatíveis com as duas versões. Evite mudanças destrutivas até desligar a versão antiga. Teste o schema novo com a versão Green antes de trocar o tráfego.
Como lidar com sessões e cache?
Guarde sessões e cache em um backend compartilhado, como Redis. Isso evita que usuários percam a sessão na troca entre Blue e Green.
Posso usar PostgreSQL em vez de MariaDB?
Pode. Ajuste a imagem do banco, as variáveis de ambiente e o DSN no health.php (pdo_pgsql). O restante do fluxo blue-green permanece igual.
Como servir arquivos estáticos eficientemente?
Sirva assets estáticos por Nginx a partir de um volume compartilhado ou CDN. Gere os assets durante o build e disponibilize-os para ambas as versões antes da troca.
Quer adaptar o blue-green ao seu monolito PHP?
Posso ajudar a revisar seu Compose, planejar migrações seguras e automatizar o switch de tráfego.
Falar sobre deploy blue-greenVeja também
- tutorial: implementar Canary releases com Traefik em Docker Compose usando headers e weight routing para deploys graduais de aplicações PHP
- Tutorial: configurar HAProxy com Let's Encrypt e healthchecks para balanceamento de tráfego entre containers Docker em VPS (passo a passo)
- Tutorial prático: implementar mTLS entre Nginx e microserviços usando smallstep CA em Docker Compose
- Tutorial: configurar cgroup v2 e systemd ResourceControl para limitar CPU, memória e I/O de containers Docker no Debian