PHP

Artigo 48 — Deploy e Ambiente de Produção Já leu

6 min de leitura

Artigo 48 — Deploy e Ambiente de Produção
Saber desenvolver é necessário, mas não suficiente. Um sistema que funciona perfeitamente no seu computador e nunca chega ao servidor de produção não tem

Artigo 48 — Deploy e Ambiente de Produção

Módulo 10 · Semana 48 · Nível: Avançado


Introdução

Saber desenvolver é necessário, mas não suficiente. Um sistema que funciona perfeitamente no seu computador e nunca chega ao servidor de produção não tem valor. Deploy é o ato de levar o código do repositório para o servidor — e fazê-lo de forma segura, repetível e rastreável.


Configuração do servidor — NGINX + PHP-FPM

PHP-FPM (FastCGI Process Manager) é como o PHP roda em produção real: gerencia um pool de processos PHP separado do servidor web. NGINX recebe as requisições e as passa para o PHP-FPM processar:

# /etc/nginx/sites-available/minha-loja
server {
    listen 80;
    server_name minha-loja.com.br www.minha-loja.com.br;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name minha-loja.com.br www.minha-loja.com.br;

    root /var/www/minha-loja/public;
    index index.php;

    # SSL — gerado com Let's Encrypt / Certbot
    ssl_certificate     /etc/letsencrypt/live/minha-loja.com.br/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/minha-loja.com.br/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;

    # Laravel: rota todas as requisições para index.php
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass   unix:/var/run/php/php8.3-fpm.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include        fastcgi_params;
        fastcgi_read_timeout 300;
    }

    # Bloqueia acesso direto a arquivos sensíveis
    location ~ /\.(env|git|htaccess) {
        deny all;
    }

    # Cache de assets estáticos
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Logs
    access_log /var/log/nginx/minha-loja.access.log;
    error_log  /var/log/nginx/minha-loja.error.log;
}

PHP-FPM — configuração do pool

; /etc/php/8.3/fpm/pool.d/minha-loja.conf
[minha-loja]
user  = www-data
group = www-data

listen = /var/run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data

; Gerenciamento de processos
pm                   = dynamic
pm.max_children      = 20
pm.start_servers     = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 8
pm.max_requests      = 500    ; reinicia o processo após 500 requisições (evita memory leaks)

; Timeout
request_terminate_timeout = 60s

; Logs de slowlog
slowlog = /var/log/php-fpm/minha-loja.slow.log
request_slowlog_timeout = 5s

Deploy com zero downtime — script completo

#!/bin/bash
# deploy.sh — executa no servidor de produção

set -euo pipefail  # para em qualquer erro

PROJETO_DIR="/var/www/minha-loja"
BRANCH="${1:-main}"

echo "🚀 Iniciando deploy — branch: $BRANCH"
cd $PROJETO_DIR

# 1. Ativa modo de manutenção (Laravel)
php artisan down --secret="token-secreto-de-bypass"
echo "✅ Modo de manutenção ativado"

# 2. Atualiza o código
git fetch origin
git reset --hard origin/$BRANCH
echo "✅ Código atualizado"

# 3. Instala dependências (sem dev, otimizado)
composer install --no-dev --optimize-autoloader --no-interaction
echo "✅ Dependências instaladas"

# 4. Roda migrations
php artisan migrate --force
echo "✅ Migrations executadas"

# 5. Otimizações do Laravel
php artisan config:cache      # cache de configurações
php artisan route:cache       # cache de rotas
php artisan view:cache        # pre-compila templates Blade
php artisan event:cache       # cache de event listeners
echo "✅ Cache otimizado"

# 6. Reinicia workers de fila (se houver)
php artisan queue:restart
echo "✅ Workers de fila reiniciados"

# 7. Recarrega PHP-FPM (graceful — sem derrubar requisições em andamento)
sudo systemctl reload php8.3-fpm
echo "✅ PHP-FPM recarregado"

# 8. Desativa modo de manutenção
php artisan up
echo "🎉 Deploy concluído com sucesso!"

GitHub Actions — CI/CD completo

# .github/workflows/deploy.yml
name: Testes e Deploy

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  testes:
    name: Testes
    runs-on: ubuntu-latest

    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_DATABASE: teste
          MYSQL_ROOT_PASSWORD: senha
        ports: ['3306:3306']
        options: --health-cmd="mysqladmin ping" --health-interval=10s

    steps:
      - uses: actions/checkout@v4

      - name: Instalar PHP 8.3
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'
          extensions: mbstring, pdo, pdo_mysql, gd, redis
          coverage: xdebug

      - name: Cache do Composer
        uses: actions/cache@v3
        with:
          path: vendor
          key: composer-${{ hashFiles('composer.lock') }}

      - name: Instalar dependências
        run: composer install --no-interaction

      - name: Auditoria de segurança
        run: composer audit

      - name: Preparar ambiente de teste
        run: |
          cp .env.testing .env
          php artisan key:generate
          php artisan migrate --force

      - name: Análise estática
        run: vendor/bin/phpstan analyse --no-progress

      - name: Estilo de código
        run: vendor/bin/php-cs-fixer fix --dry-run --diff

      - name: Testes com cobertura
        run: vendor/bin/pest --coverage --min=75

  deploy:
    name: Deploy em Produção
    needs: testes
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'

    steps:
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVIDOR_HOST }}
          username: ${{ secrets.SERVIDOR_USER }}
          key: ${{ secrets.SERVIDOR_SSH_KEY }}
          script: |
            cd /var/www/minha-loja
            bash deploy.sh main

Docker para desenvolvimento padronizado

# Dockerfile
FROM php:8.3-fpm-alpine

# Extensões necessárias
RUN docker-php-ext-install pdo pdo_mysql opcache
RUN apk add --no-cache libpng-dev && docker-php-ext-install gd

# OPcache — melhora performance significativamente em produção
COPY opcache.ini /usr/local/etc/php/conf.d/

WORKDIR /var/www/html
COPY . .
RUN composer install --no-dev --optimize-autoloader

RUN chown -R www-data:www-data storage bootstrap/cache
# docker-compose.yml (desenvolvimento)
services:
  app:
    build: .
    volumes:
      - .:/var/www/html  # monta código local
    depends_on: [db, redis]

  nginx:
    image: nginx:alpine
    ports: ['8000:80']
    volumes:
      - .:/var/www/html
      - ./nginx.conf:/etc/nginx/conf.d/default.conf

  db:
    image: mysql:8.0
    environment:
      MYSQL_DATABASE: loja
      MYSQL_ROOT_PASSWORD: senha
    volumes:
      - db_data:/var/lib/mysql

  redis:
    image: redis:7-alpine

volumes:
  db_data:

Monitoramento e logs

<?php
// config/logging.php — Laravel

'channels' => [
    'stack' => [
        'driver'   => 'stack',
        'channels' => ['daily', 'slack'],
    ],

    'daily' => [
        'driver' => 'daily',
        'path'   => storage_path('logs/laravel.log'),
        'level'  => env('LOG_LEVEL', 'debug'),
        'days'   => 14,
    ],

    'slack' => [
        'driver'   => 'slack',
        'url'      => env('LOG_SLACK_WEBHOOK_URL'),
        'username' => 'Laravel Log',
        'level'    => 'critical',  // Apenas erros críticos vão para o Slack
    ],
],

// Uso — o mesmo que vimos na PSR-3:
Log::info('Pedido criado', ['id' => $pedido->id, 'total' => $pedido->total]);
Log::error('Falha no pagamento', ['pedido_id' => $id, 'erro' => $e->getMessage()]);
Log::critical('Banco de dados inacessível', ['exception' => $e]);

Resumo

Etapa Ferramenta
Servidor web NGINX + PHP-FPM
HTTPS Let's Encrypt + Certbot
Deploy Script bash + GitHub Actions
Containerização Docker + Docker Compose
CI/CD GitHub Actions (testes → deploy)
Logs Monolog + Laravel Log (diário + Slack crítico)

Exercício da semana

  1. Configure NGINX + PHP-FPM localmente com Docker. Crie um docker-compose.yml com app, nginx, mysql e redis. Garanta que o Laravel funciona completamente nesse ambiente.

  2. Escreva o script deploy.sh completo. Teste localmente simulando as etapas. Adicione tratamento de erro: se php artisan migrate falhar, o script deve reverter o git reset e reativar a aplicação.

  3. Configure o GitHub Actions com as etapas: composer audit, phpstan, php-cs-fixer, pest. Faça um PR intencional com erro e confirme que o CI falha.

  4. Desafio: configure um pipeline de deploy com zero downtime real usando o padrão de diretórios releases/: cada deploy cria um novo diretório com timestamp, um symlink current aponta para o release ativo, e os últimos 5 releases são mantidos para rollback instantâneo.


Referências

  • NGINX Docs: https://nginx.org/en/docs/
  • PHP-FPM Configuration: https://www.php.net/manual/pt_BR/install.fpm.configuration.php
  • GitHub Actions: https://docs.github.com/en/actions
  • Docker for PHP: https://hub.docker.com/_/php
  • Let's Encrypt: https://letsencrypt.org/

Comentários

Mais em PHP

Formulários HTML e PHP
Formulários HTML e PHP

Formulários são a porta de entrada dos dados do usuário para o servidor. Tudo...

Interfaces Avançadas
Interfaces Avançadas

Interfaces s&atilde;o o principal mecanismo de abstra&ccedil;&atilde;o do PHP...

Observer, Decorator e Strategy
Observer, Decorator e Strategy

No artigo anterior estudamos três padrões criacionais — como construir objeto...