CI/CD para WordPress: automatizar o deployment em 2026

CI/CD para WordPress: automatizar o deployment em 2026

Última verificação: 15 de outubro de 2026
14 min de leitura
Guia
Desenvolvedor full-stack

Arrastar temas por FTP em 2026 costuma acabar igual: falta um ficheiro de inc/, o composer.lock veio de outro portátil, e a produção cai porque alguém sobrescreveu o wp-config.php. Continuous Integration e Continuous Deployment não são teatro de enterprise - são a forma de cada alteração no Git passar pelo mesmo build, pelos mesmos testes e pelo mesmo caminho até ao servidor.

Este guia é para WordPress com tema ou plugin no repositório (Composer, npm, eventualmente Docker), não para sites que só vivem no painel. Se o código custom ainda só existe na base de dados e nos media, leve temas e código próprio para o Git primeiro - sem isso, o CI/CD não tem o que construir.

Documentação dos runners: GitHub Actions. Em paralelo: Hardening WordPress. Para comandos pós-deploy: WP-CLI.

#Porque não fazer deploy do WordPress por FTP

Três problemas voltam em lojas WooCommerce portuguesas e em entregas de agência em Lisboa ou Porto:

  1. Deriva de ambiente - localmente PHP 8.2 e Composer 2.7; no VPS PHP 8.1 sem intl. O composer install local «funciona»; a produção morre com class not found.
  2. Sem rasto de auditoria - depois de um incidente não se sabe qual commit estava live. O painel do hosting mostra a data do ficheiro, não o SHA do Git.
  3. Segredos no disco - passwords SFTP no FileZilla, ou um .env commitado «só um momento», continuam a ser o caminho clássico de fuga - e caro sob o RGPD.

O CI/CD resolve isto mecanicamente: build numa imagem conhecida (shivammathur/setup-php ou Docker próprio), artefacto versionado por SHA, deploy por SSH com chave em Secrets.

#Pipeline CI/CD WordPress com GitHub Actions

Fluxo típico para tema custom mais plugins via Composer:

  1. Trigger - push para main ou merge de pull request revisto. O develop costuma ir só para staging.
  2. Build - composer install --no-dev --optimize-autoloader, npm ci, npm run build. Fixe PHP e Node no workflow (php-version: '8.3', node-version: '24').
  3. Test - PHPUnit para plugins, eventualmente Playwright no checkout. Fail = paragem, zero deploy.
  4. Artefacto - empacote a release sem .git, sem node_modules, com vendor/ e assets/dist/ compilado.
  5. Deploy - rsync/SCP para releases/<run_id>/, depois troca atómica do symlink.

Fixe versões que ainda recebem correções. O PHP 8.2 recebe correções de segurança só até 31 de dezembro de 2026, o PHP 8.3 até 31 de dezembro de 2027 (versões suportadas do PHP). O Node.js 20 chegou ao fim de vida a 30 de abril de 2026; o Node.js 22 é suportado até 30 de abril de 2027 e o Node.js 24 até 30 de abril de 2028 (calendário de versões do Node.js). A versão minor do PHP acompanha a produção, não a versão mais recente.

Esboço curto em GitHub Actions, com tags de versão principal para facilitar a leitura; a secção sobre reforço do workflow, mais abaixo, explica porque os workflows de produção fixam SHAs de commit:

name: deploy-production
on:
  push:
    branches: [main]
permissions:
  contents: read
concurrency:
  group: deploy-production
  cancel-in-progress: false
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v7
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'
      - run: composer install --no-dev --optimize-autoloader
      - uses: actions/setup-node@v7
        with:
          node-version: '24'
          cache: npm
      - run: npm ci && npm run build
      - name: rsync release
        env:
          SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
        run: |
          # gravar chave, rsync para releases/$GITHUB_SHA, ln -sfn

O script completo do symlink fica no repo como bin/release.sh - o workflow só o chama. O mesmo script funciona a partir do portátil se o Actions estiver em baixo.

#Impedir dois deploys WordPress em simultâneo no GitHub Actions

Dois merges rápidos para main arrancam duas execuções do workflow. Sem bloqueio, ambas fazem rsync para releases/ e trocam current numa ordem que ninguém escolheu. Um grupo concurrency no GitHub Actions permite no máximo um workflow em execução por grupo; uma execução recém-colocada em fila espera como pending (documentação GitHub: controlar a concorrência).

Para deploys, contam dois detalhes. Por omissão, um grupo mantém uma execução ativa e uma pendente, e uma execução mais recente cancela a pendente mais antiga: ganha o último commit e o do meio é ignorado. cancel-in-progress: true cancelaria também o job em curso, o que pode parar o rsync a meio de um diretório de release. Mantenha false em produção e use true em workflows de teste em pull requests, onde só conta o commit mais recente.

concurrency:
  group: deploy-production
  cancel-in-progress: false

Como o symlink só troca depois de um rsync completo, uma execução pendente cancelada nunca toca em current.

#Proteger a produção com ambientes do GitHub Actions

Acrescente environment: production ao job de deploy e configure o ambiente nas definições do repositório. Passam a valer três proteções (documentação GitHub: deployments e ambientes):

  • Revisores obrigatórios: até seis utilizadores ou equipas, e uma aprovação liberta o job. Com o bloqueio de autoaprovação ativo, quem iniciou o deploy não o pode aprovar.
  • Branches e tags de deploy: só main ou tags de release podem fazer deploy para production, por isso um workflow num feature branch não chega ao servidor de produção.
  • Segredos de ambiente: disponíveis só para jobs que referenciam o ambiente e, com revisores obrigatórios, só depois da aprovação. O DEPLOY_SSH_KEY de produção fica aqui, não nos segredos do repositório.

Confirme o plano antes de contar com isto. No GitHub Free, Pro e Team, os revisores obrigatórios e os tempos de espera só funcionam em repositórios públicos. Branches de deploy e segredos de ambiente funcionam em repositórios privados no Pro e no Team, mas não no Free. Os repositórios WordPress de agências são quase sempre privados, por isso nesses planos não há porta de aprovação e são as regras de branch que garantem a proteção.

#Reforçar o workflow de deploy no GitHub Actions

O job de deploy guarda uma chave SSH para a produção, por isso o ficheiro do workflow merece a mesma revisão que o código PHP. A referência do GitHub sobre uso seguro enumera as medidas que aqui importam:

  • Fixe actions de terceiros no SHA completo do commit. Uma tag como @v2 pode ser movida ou apagada. Redirecionar um SHA exigiria uma colisão SHA-1. Tire o SHA do repositório da própria action, não de um fork. O GitHub aponta agora um lockfile de dependências do workflow como a opção mais forte para os repositórios que o adotem.
  • Dê ao GITHUB_TOKEN o mínimo. Defina permissions: contents: read no topo e aumente por job só onde um passo precisa de mais. Um deploy por rsync e SSH só precisa de ler o repositório.
  • Mantenha input não confiável fora de run:. Um título de pull request ou nome de branch dentro de ${{ }} num script shell abre caminho a injeção de código. Passe o valor por uma variável de ambiente intermédia.
  • Um segredo por valor. Dados estruturados, como um bloco JSON, podem quebrar a ocultação nos logs, por isso guarde host, utilizador, caminho e chave como segredos separados.

A mesma página nota que a ocultação só abrange segredos usados no job e acessíveis ao runner, por isso nunca faça echo de valores derivados, como uma chave descodificada.

Sobrescrever ficheiros «no sítio» em public_html significa que durante alguns segundos metade dos pedidos vê um functions.php antigo com um template novo. No WooCommerce, numa campanha de Natal ou Black Friday, isso pode corromper sessões de carrinho.

Layout atómico:

  • base: /var/www/site/
  • releases: /var/www/site/releases/20260920-142211/
  • shared: uploads, wp-config.php, object cache - fora da release
  • current: symlink para a release ativa

Após rsync bem sucedido:

ln -sfn /var/www/site/releases/20260920-142211 /var/www/site/current

O root do Nginx aponta para .../current/public (ou equivalente). A troca é imediata. Apague releases antigas após N dias, ou guarde as três últimas para rollback.

Os uploads do WordPress têm de viver fora da release - caso contrário cada deploy apaga media ou copia gigabytes. Padrão clássico: shared/uploads ligado a wp-content/uploads em cada release.

#Staging WordPress e estratégias de deploy comparadas

Em 2026, o caminho de um feature branch por pull request e staging até main e produção não é opcional para lojas e páginas de captura. O staging deve ter:

  • a mesma versão de PHP e as mesmas extensões que a produção
  • uma cópia da base (anonimizada quando há dados pessoais sob o RGPD)
  • segredos de pagamento só em modo sandbox

URLs de preview por PR ajudam em redesign e CSS; migrações de esquema continuam a precisar de staging com dump real.

EstratégiaRiscoCusto de manutençãoQuando faz sentido
FTP manualMuito altoArranque barato, falhas carasSó sandbox / aprendizagem
Git hook no servidorMédioBaixoPortfólio pequeno, um programador
CI + rsync atómicoBaixoMédioNegócio, WooCommerce, agências
Blue/green ou dois VPSMínimoAltoMuito tráfego, SLA, pagamentos

Blue/green (dois ambientes completos, troca no load balancer) raramente compensa abaixo de centenas de sessões em paralelo. A maioria das lojas PT-PT fica bem com symlink atómico e rollback de diretório - em VPS (Hetzner, DigitalOcean) ou hosting gerido com SSH.

#Testes automáticos num pipeline CI/CD de WordPress

Um pipeline sem testes só envia código partido mais depressa. Quatro camadas servem um repositório WordPress, da mais rápida à mais lenta:

  • Testes unitários com PHPUnit verificam funções isoladas de um plugin ou tema sem base de dados. Correm a cada push.
  • Testes de integração carregam o WordPress contra uma base de dados de teste temporária. wp scaffold plugin-tests gera a estrutura: phpunit.xml.dist, bin/install-wp-tests.sh para a suite de testes e a base de dados, tests/bootstrap.php, um teste de exemplo e um conjunto de regras .phpcs.xml.dist. O ficheiro de CI por omissão é para CircleCI; --ci=github escreve antes uma configuração para GitHub Actions (WP-CLI: wp scaffold plugin-tests).
  • Análise estática com PHPStan e PHP_CodeSniffer apanha erros de tipos e violações da norma de código sem executar o código.
  • Testes end-to-end com Playwright percorrem fluxos reais: formulário de contacto, carrinho e checkout WooCommerce. São a camada mais lenta, por isso corra-os em pull requests para main e contra o staging, não a cada commit.

Um job a vermelho em qualquer camada para o workflow antes do passo do artefacto, e nada chega ao servidor.

#Segredos e migrações de base de dados no deploy WordPress

Nunca faça commit de:

  • chaves SSH
  • passwords da base de dados
  • AUTH_KEY / SECURE_AUTH_KEY do wp-config.php
  • tokens de API de gateways (MB Way, Stripe, PayPal)

No GitHub ficam em Settings, depois Secrets and variables, depois Actions. Injete-os como env no passo de deploy. No servidor, o wp-config.php vive em shared/ e não faz parte do artefacto de CI.

WP-CLI depois da troca do symlink:

  • wp cache flush se houver object cache
  • wp rewrite flush após alterações de CPT
  • wp plugin list como linha de smoke no log

#Correr o WP-CLI no servidor de produção a partir do runner

Estes comandos têm de correr no servidor, contra a nova release. O WP-CLI faz isso a partir do runner com o parâmetro global --ssh ([<scheme>:][<user>@]<host>[:<port>][<path>]) ou com aliases no wp-cli.yml do projeto (manual do WP-CLI: correr comandos remotamente):

@staging:
  ssh: [email protected]/var/www/site/current/public
@prod:
  ssh: [email protected]/var/www/site/current/public

Com esse ficheiro no repositório, wp @prod cache flush corre em produção. O WP-CLI tem na mesma de estar instalado no servidor e acessível como wp: o runner apenas passa o comando por SSH.

Outros comandos que vale a pena pôr no passo a seguir à troca, todos na referência de comandos do WP-CLI:

  • wp core is-installed não escreve nada e responde pelo código de saída, por isso uma verificação falhada faz falhar o passo.
  • wp core update-db --dry-run compara as versões da base de dados antes do wp core update-db real após uma atualização do core.
  • wp core verify-checksums compara os ficheiros do core com os checksums do WordPress.org e apanha ficheiros alterados fora do Git.
  • wp maintenance-mode activate e deactivate cobrem a release rara que tem de alterar o esquema enquanto nada escreve nele.

Planeie migrações de base de dados à parte do deploy de ficheiros. O rollback do symlink não desfaz ALTER TABLE. Em migrações destrutivas: backup primeiro (wp db export em staging e produção), depois migração, depois tráfego para o código que precisa do esquema - ou feature flag em PHP.

Dependências PHP: Composer install - em CI sempre --no-dev para produção e composer.lock bloqueado.

#Erros comuns num pipeline DevOps WordPress

Da prática em VPS (Hetzner, DigitalOcean, OVH) e hosting gerido com SSH:

  • .gitignore come o vendor/ - a produção recebe o tema sem autoloader. Ou constrói o vendor no runner e mete-o no artefacto, ou corre Composer no servidor após o rsync (aí o servidor precisa de Composer e acesso ao Packagist).
  • Caminhos errados - o script assume /var/www/html e o hosting usa /home/user/domains/.... Fixe caminhos em Secrets por ambiente.
  • Opcache mantém código antigo - após ln -sfn, faça reload do PHP-FPM ou chame opcache_reset via WP-CLI/mu-plugin só para o utilizador de deploy.
  • Cron e filas - o Action Scheduler do WooCommerce pode correr jobs com código antigo durante alguns minutos; após releases grandes, observe failed jobs um pouco.

Smoke test pós-deploy (curl a partir do runner ou job separado):

  • homepage HTTP 200
  • /wp-login.php 200
  • um URL crítico de produto ou formulário
  • HEAD opcional ao CDN se os assets tiverem hash no nome

Se o smoke falhar, o rollback automático do symlink custa menos do que o telefonema do cliente às 23:00.

#Temas de blocos e must-use plugins no Git

Nem todo o WordPress cabe no mesmo pipeline. Conteúdo na base (artigos, grupos ACF sem export JSON, CSS do Elementor) continua fora da release. O CI/CD protege código; não substitui backups da base.

Divisões práticas:

  • Tema clássico ou híbrido com Vite - build completo em CI, artefacto com style.css + dist/.
  • Tema de blocos (FSE) - mantenha theme.json e templates HTML no Git; alterações do Site Editor voltam por PR, senão produção e Git divergem após o primeiro «Guardar».
  • Must-use plugins - vão com a release; não os atualize pelo painel. Bom sítio para healthchecks de deploy e para desativar o editor de ficheiros.
  • Plugins de wordpress.org - fixe versões via Composer (wpackagist) ou atualize-os num job revisto. Misturar «Update no painel» com «CI sobrescreveu plugins/» produz uma árvore aleatória.

Stacks de agência em PT misturam muitas vezes tema custom, WooCommerce via wpackagist e dois zips premium instalados por path repository no Composer. O pipeline tem de conhecer as três fontes, senão staging e produção diferem por um ZIP de há seis meses.

#Checklist para deploy contínuo do WordPress

Antes de ligar o main à produção, passe esta lista uma vez - poupa um fim de semana:

  1. Repositório privado, ou confirme que o histórico não tem segredos (git log -p não mostra .env antigos).
  2. Proteção de branch: PR obrigatório, workflow test verde obrigatório.
  3. Secrets separados: DEPLOY_HOST_STAGING, DEPLOY_HOST_PROD, chaves SSH distintas.
  4. Staging com a mesma versão de PHP que a produção (não «nós 8.3, o cliente 8.1»).
  5. Backup da base antes da primeira troca atómica - mesmo com rollback de código pronto.
  6. Monitorização de uptime (UptimeRobot, Better Stack ou equivalente) com alerta SMS/Slack no URL de produção.
  7. Runbook de uma página: como fazer ln -sfn manual para a release anterior, quem tem acesso ao servidor.

Só depois ative auto-deploy a partir de main. Até lá, «build + test» em PRs e workflow_dispatch manual para staging chega.

#Resumo

CI/CD para WordPress não é «YAML mágico». É um contrato repetível: o mesmo PHP, o mesmo lockfile, o mesmo artefacto, uma troca sem janela de ficheiros a meio. Começa com Git e staging, acrescenta Actions para builds Composer/npm, termina com current atómico e smoke test. O FTP fica ferramenta de emergência, não o processo.

Se precisa de um programador WordPress que mantenha esse pipeline junto do código do tema para loja ou página de captura, a WPPoland trabalha no stack real - não em slides.

Próximo passo

Transforme o artigo numa implementação real

Este bloco reforça a ligação interna e conduz o leitor para o passo seguinte mais útil dentro da arquitetura do site.

Quer implementar isto no seu site?

Se quer transformar o artigo em melhorias concretas, redesign ou num plano de implementação, posso fechar o escopo e executar.

Cluster relacionado

Explorar outros serviços WordPress e base de conhecimento

Reforce o seu negócio com suporte técnico profissional em áreas-chave do ecossistema WordPress.

Perguntas frequentes

Respostas práticas para aplicar o tema na execução real.

O CI/CD faz sentido para um freelancer a solo?#
Sim. Quem trabalha sozinho perde mais com um ficheiro PHP esquecido ou um composer.lock gerado noutra versão de PHP. O pipeline força o mesmo build em cada commit e deixa um registo auditável de qual SHA ficou em produção.
O que é deployment zero-downtime no WordPress?#
O código novo chega a releases/TIMESTAMP. Só depois de um rsync bem sucedido e de um eventual wp cache flush é que se muda o symlink current. PHP-FPM ou Nginx servem via current, a troca demora milissegundos e não precisa de modo de manutenção.
Preciso de um VPS para CI/CD?#
VPS ou contentores dão controlo total (SSH, symlinks, WP-CLI). Em hosting gerido fica muitas vezes uma API de deploy ou SFTP com chave em Secrets - ainda assim melhor do que arrastar ficheiros no FileZilla, mesmo sem blue/green verdadeiro.
O que fazer se o deploy falhar?#
Guarde N diretórios de release anteriores. Se o smoke test devolver 500 ou faltar vendor/, volte a apontar o symlink para a release anterior. Planeie migrações de base de dados à parte - o rollback de código não desfaz ALTER TABLE.
Onde construir temas com Vite ou webpack?#
No runner de CI: checkout, setup Node, npm ci, npm run build, depois empacote dist/ no artefacto com o PHP. Não envie node_modules nem fontes TypeScript para produção - só CSS/JS compilados.

Precisa de FAQ adaptado ao setor e mercado? Criamos uma versão alinhada com os seus objetivos de negócio.

Fale connosco

Artigos Relacionados