Tutorial: implementar deploy canário com GitHub Actions e Nginx para aplicações PHP monolito

Deploy & DevOps · Por · Publicado em · 11 min de leitura

Pessoa trabalhando em mesa com monitor grande e notebook, escritório claro ao fundo
Ambiente profissional com monitor e notebook, adequado para tutoriais técnicos e operações de deploy. Foto: RDNE Stock project / Pexels

Este tutorial: implementar deploy canário com GitHub Actions e Nginx para aplicações PHP monolito (smoke tests e rollback automático) é um passo a passo prático. Você vai dividir tráfego entre versões, forçar canário por header/cookie, promover gradualmente e reverter com segurança.

Pré-requisitos e visão da arquitetura canário

A estratégia canário reduz risco. Uma pequena parcela do tráfego vai para a nova versão. Se tudo ficar estável, você promove. Caso contrário, reverte.

Usaremos Nginx para roteamento ponderado e GitHub Actions para build, deploy, smoke tests, promoção e rollback. Dois pools PHP‑FPM isolam execuções.

Componentes: Nginx na frente, dois pools PHP‑FPM (stable e canary), releases versionados e workflow de CI acionando SSH no servidor.

Você precisa de acesso root ou sudo, chaves SSH e um endpoint de saúde, como /health. Tenha logs e métricas mínimas para observar erros.

Para evitar problemas com estáticos, use fingerprinting de arquivos. Exemplo: app.abc123.css. Isso evita servir CSS/JS da versão errada.

Monte um plano de rollback simples e testado. Um checklist ajuda a não esquecer etapas críticas. Veja o checklist de pré‑deploy e rollback.

  • Nginx como reverse proxy e roteador canário
  • Dois pools PHP‑FPM: stable e canary
  • Releases em /var/www/app/releases/<sha> + symlinks stable/canary
  • Workflow no GitHub Actions com SSH e scripts idempotentes
  • Endpoint /health para smoke tests e monitoramento básico
  • Assets com fingerprint para evitar inconsistências
  • Plano de rollback claro e testado

Configurar dois pools PHP‑FPM (stable e canary)

Crie dois pools PHP‑FPM para isolar opcache e parâmetros entre stable e canary. Eles usam sockets e diretórios distintos. Um reload do PHP‑FPM limpa o opcache e aplica mudanças com interrupção mínima.

Ajuste pm.max_children conforme a capacidade. O canário recebe menos tráfego. Garanta permissões corretas no diretório da aplicação e nos sockets.

; /etc/php/8.2/fpm/pool.d/app.conf
[app_stable]
user = www-data
group = www-data
listen = /run/php-fpm-stable.sock
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
chdir = /var/www/app/releases/stable
php_admin_value[opcache.enable] = 1
php_admin_value[opcache.validate_timestamps] = 1

[app_canary]
user = www-data
group = www-data
listen = /run/php-fpm-canary.sock
pm = dynamic
pm.max_children = 8
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
chdir = /var/www/app/releases/canary
php_admin_value[opcache.enable] = 1
php_admin_value[opcache.validate_timestamps] = 1

Configure o Nginx para separar tráfego entre stable e canary por porcentagem com split_clients. Permita forçar canário com header X‑Canary: 1 ou cookie canary=1. A seleção também define o docroot para o PHP.

Sobre assets: sirva estáticos do stable por simplicidade. Use fingerprinting para evitar inconsistências visuais. Alternativas: CDN ou regras duplicadas com caminhos versionados.

Se preferir outra troca de tráfego, considere um fluxo blue‑green em containers. Há um guia prático no final desta página.

Sinal Como definir Quando usar
Porcentagem split_clients 5% canary Gradual padrão sem intervenção do usuário
Header X-Canary: 1 Forçar canário em smoke tests e QA
Cookie canary=1 Fixar usuário/grupo no canário temporariamente
# Contexto http (ex.: nginx.conf)
map $http_x_canary $force_hdr_canary {
    default 0;
    "1" 1;
}
map $cookie_canary $force_ck_canary {
    default 0;
    "1" 1;
}
# Percentual no split_clients (editado via CI)
split_clients "$request_id" $bucket {
    5% canary;
    *  stable;
}
# Prioriza header/cookie; senão usa bucket
map "$force_hdr_canary$force_ck_canary$bucket" $pool {
    ~1            canary;
    ~.*canary     canary;
    default       stable;
}
map $pool $fpm_socket {
    stable  unix:/run/php-fpm-stable.sock;
    canary  unix:/run/php-fpm-canary.sock;
}
map $pool $docroot {
    stable  /var/www/app/releases/stable/public;
    canary  /var/www/app/releases/canary/public;
}

server {
    listen 80;
    server_name example.com;

    # Fallback de estáticos aponta para stable
    root /var/www/app/releases/stable/public;

    location /health {
        add_header Content-Type text/plain;
        return 200 "ok";
    }

    location / {
        try_files $uri /index.php?$query_string;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $docroot$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT   $docroot;
        fastcgi_pass $fpm_socket;
    }

    # Estáticos: use fingerprinting (ex.: app.abc123.css)
    location ~* \.(?:css|js|png|jpg|jpeg|gif|svg|ico|woff2?)$ {
        expires 7d;
        try_files $uri @php_fallback;
    }
    location @php_fallback { rewrite ^ /index.php last; }
}

Pipeline CI: build e empacotamento no GitHub Actions

O workflow compila dependências, empacota a aplicação e prepara variáveis para o deploy. Use Composer com flags de produção e artefato único tar.gz.

A etapa de build é separada do deploy. É possível acionar por workflow_dispatch e informar o percentual inicial do canário.

Para organizar o pipeline com boas práticas, confira o guia de pipeline de deploy automático.

  • Composer em modo produção
  • Artefato único versionado por SHA
  • Inputs do workflow para parametrizar rollout
  • Separação clara de build e deploy
name: canary-deploy-php
on:
  workflow_dispatch:
    inputs:
      percent_start:
        description: 'Percentual inicial do canário'
        default: '5'
      host:
        description: 'Host de produção (SSH)'
        required: true
      app_url:
        description: 'URL pública para smoke tests'
        required: true
jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      rel: ${{ steps.pkg.outputs.rel }}
    steps:
      - uses: actions/checkout@v4
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
      - name: Install Composer deps
        run: |
          composer install --no-dev --prefer-dist --no-interaction --optimize-autoloader
      - name: Build assets (opcional)
        run: |
          echo 'adicione seu build de front aqui, se houver'
      - name: Package artifact
        id: pkg
        run: |
          REL=${GITHUB_SHA}
          tar -czf release-$REL.tar.gz --exclude-vcs --exclude=.github .
          echo rel=$REL >> $GITHUB_OUTPUT
      - uses: actions/upload-artifact@v4
        with:
          name: php-release-${{ steps.pkg.outputs.rel }}
          path: release-${{ steps.pkg.outputs.rel }}.tar.gz

Deploy do canário via SSH e reloads sem downtime

Nesta etapa, enviamos o artefato, extraímos em releases/<sha> e atualizamos o symlink canary. Definimos o percentual inicial no split_clients. Teste a configuração (nginx -t) e faça reloads.

Grave o release estável atual para rollback rápido. O reload do PHP‑FPM limpa o opcache. Isso evita servir bytecode antigo.

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: php-release-${{ needs.build.outputs.rel }}
      - name: Prep SSH
        env:
          SSH_KEY: ${{ secrets.SSH_KEY }}
        run: |
          mkdir -p ~/.ssh
          echo "$SSH_KEY" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
      - name: Ship and activate canary
        env:
          SSH_USER: ${{ secrets.SSH_USER }}
          SSH_HOST: ${{ inputs.host }}
          REL: ${{ needs.build.outputs.rel }}
          PSTART: ${{ inputs.percent_start }}
        run: |
          scp -o StrictHostKeyChecking=no release-$REL.tar.gz $SSH_USER@$SSH_HOST:/tmp/
          ssh -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST bash -s <<EOF
          set -euo pipefail
          REL=${REL}
          APP=/var/www/app
          sudo mkdir -p $APP/releases/$REL
          sudo tar -xzf /tmp/release-$REL.tar.gz -C $APP/releases/$REL
          # Registra release estável atual
          PREV=$(readlink -f $APP/releases/stable || true)
          echo "$PREV" | sudo tee $APP/prev_stable.txt >/dev/null
          # Ativa canary
          sudo ln -sfn $APP/releases/$REL $APP/releases/canary
          sudo systemctl reload php8.2-fpm
          # Ajusta percentual no split_clients (arquivo snippet)
          # Ex.: /etc/nginx/snippets/app_split.conf contém a linha "5% canary;"
          sudo sed -i "s/^[[:space:]]*[0-9]\+% canary;/ ${PSTART}% canary;/" /etc/nginx/snippets/app_split.conf || true
          sudo nginx -t && sudo systemctl reload nginx
          EOF

Smoke tests e promoção gradual automática

Com o canário ativo, execute smoke tests forçando o header X‑Canary: 1. Valide endpoints críticos. Se passarem, aumente o percentual em etapas, como 5→25→50→100.

Entre etapas, aguarde e monitore erros. Valide HTTP 200 em sequência e aborte no primeiro erro. Integre alertas para agir rápido. Veja também como implementar health checks e readiness.

  • Forçar canário com header em todas as chamadas de teste
  • Testar rotas de login, checkout e APIs críticas
  • Aguardar e monitorar 5xx entre promoções
  • Abortar e acionar rollback no primeiro erro
#!/usr/bin/env bash
# run on GitHub Actions or jump host
set -euo pipefail
APP_URL="$1"              # ex.: https://example.com
PERCENTS=(5 25 50 100)
SMOKE_ENDPOINTS=("/health" "/" "/api/status")

smoke() {
  local url="$1"; local fail=0
  for i in {1..10}; do
    for ep in "${SMOKE_ENDPOINTS[@]}"; do
      code=$(curl -fsS -o /dev/null -w "%{http_code}" -H "X-Canary: 1" "$url$ep" || true)
      if [[ "$code" != "200" ]]; then
        echo "Smoke FAIL $ep -> $code"; fail=1
      else
        echo "Smoke OK  $ep -> $code"
      fi
    done
    sleep 1
  done
  return $fail
}

for p in "${PERCENTS[@]}"; do
  echo "Promovendo canário para ${p}%..."
  ssh $SSH_OPTS $SSH_USER@$SSH_HOST "sudo sed -i 's/^[[:space:]]*[0-9]\+% canary;/ ${p}% canary;/' /etc/nginx/snippets/app_split.conf && sudo nginx -t && sudo systemctl reload nginx"
  echo "Aguardando estabilizar..."; sleep 60
  if ! smoke "$APP_URL"; then
    echo "Falha nos smoke tests em ${p}%. Iniciando rollback..."; exit 42
  fi
  echo "OK em ${p}%"
done

echo "100% canário aprovado. Promovendo a estável..."
ssh $SSH_OPTS $SSH_USER@$SSH_HOST 'APP=/var/www/app; sudo ln -sfn "$APP/releases/canary" "$APP/releases/stable" && sudo systemctl reload php8.2-fpm && sudo sed -i "s/^[[:space:]]*[0-9]\+% canary;/ 0% canary;/" /etc/nginx/snippets/app_split.conf && sudo nginx -t && sudo systemctl reload nginx'

Rollback automático e limpeza segura

Se algo falhar, reverta de imediato. Zere o canário, restaure o symlink stable para o release anterior e recarregue Nginx e PHP‑FPM. Não exclua o release falho até todo o tráfego voltar ao estável.

Armazene o caminho do release estável antes do deploy. Use esse valor no rollback. Depois, investigue com calma, fora do horário de pico.

#!/usr/bin/env bash
# rollback.sh (executar no runner)
set -euo pipefail
ssh $SSH_OPTS $SSH_USER@$SSH_HOST '
  set -euo pipefail
  APP=/var/www/app
  PREV=$(cat $APP/prev_stable.txt || true)
  echo "Restaurando stable -> $PREV"
  # 0% tráfego no canário
  sudo sed -i "s/^[[:space:]]*[0-9]\+% canary;/ 0% canary;/" /etc/nginx/snippets/app_split.conf || true
  sudo nginx -t && sudo systemctl reload nginx
  # Reaponta o estável
  if [ -n "$PREV" ] && [ -d "$PREV" ]; then
    sudo ln -sfn "$PREV" "$APP/releases/stable"
  fi
  sudo systemctl reload php8.2-fpm
'

Como confirmar que funcionou e próximos passos

Confirme que usuários anônimos e autenticados acessam a versão correta conforme o percentual. Force o canário com header e valide o fluxo completo: login, carrinho, checkout e APIs.

Monitore erros 5xx, tempos de resposta e logs de aplicação. Integre alertas do time. Ao final, limpe releases antigos e documente percentuais, tempos e métricas.

Quer alternativa em containers com headers e pesos? Veja o tutorial de canário com Traefik. Para trocas totais, use deploy blue‑green PHP. Você concluiu o tutorial: implementar deploy canário com GitHub Actions e Nginx para aplicações PHP monolito.

  • Conferir percentuais com amostras controladas
  • Acompanhar logs e métricas por 24–48 horas
  • Remover releases antigos após validação
  • Documentar rollout e aprendizados

Perguntas frequentes

Posso usar um único pool PHP‑FPM para stable e canary?

Pode, mas pools separados isolam opcache e parâmetros, reduzindo riscos de interferência entre versões. Com um único pool, invalide o opcache a cada release e confira se o limite de memória comporta o cache de ambas as versões.

Como lidar com assets (CSS/JS) diferentes entre versões?

Use fingerprinting nos nomes de arquivos para que navegador e Nginx sirvam o asset correto mesmo quando só o tráfego dinâmico entra no canário. Alternativamente, publique estáticos em uma CDN por versão e aponte URLs absolutas versionadas.

Este fluxo garante zero downtime?

Reloads do Nginx e do PHP‑FPM são graciosos e costumam manter conexões. A indisponibilidade tende a ser mínima. Mesmo assim, teste configuração com nginx -t e promova em passos curtos para reduzir riscos e evitar interrupções percebidas.

E as migrations de banco durante o canário?

Planeje migrations compatíveis com a versão antiga. Primeiro aplique mudanças que não quebrem o estável. Só depois promova o canário. Se a nova versão exigir schema novo, use feature flags e migre em etapas para permitir rollback sem perda.

Como monitorar erros durante a promoção?

Colete taxas de 5xx, latência p95/p99 e erros de aplicação. Integre alertas no seu canal de time. Se não há stack de observabilidade, comece com um endpoint /health e contadores simples. Evolua para métricas e alertas conforme a maturidade.

Consigo forçar um usuário específico no canário?

Sim. Defina um cookie canary=1 ao autenticar usuários que você deseja fixar no canário. O Nginx prioriza header e cookie sobre a amostragem por porcentagem, permitindo testes direcionados e suporte a QA sem depender do hash aleatório.

Precisa colocar canários em produção com segurança?

Implemente rollout gradual, smoke tests e rollback automático no seu monólito PHP com apoio especializado.

Falar sobre deploy canário

Veja também