pular para o conteúdo

Deploy de WordPress com rsync e GitHub Actions sem sustos

Capa: Deploy de WordPress com rsync sem sustos

Publicar WordPress na mão funciona até o dia em que não funciona. Automatizei o deploy de um WordPress numa VPS — branch, pull request, merge publica sozinho — e as primeiras semanas do pipeline me ensinaram quatro lições que só aparecem quando os arquivos do servidor pertencem a outro dono. Nenhum tutorial de rsync com GitHub Actions avisa disso, porque todos partem do pressuposto de que o usuário do deploy já é dono de tudo. Na prática não é: quem publica o WordPress é o usuário do servidor web, e o usuário de deploy só participa do grupo dele. É exatamente aí que mora cada um dos sustos abaixo.

Lição 1 — Backup antes de sobrescrever, no lugar certo

A pipeline compacta tema e plugin no servidor antes de rodar o rsync, e guarda os 10 backups mais recentes — se o deploy sair errado, dá para voltar em segundos. No primeiro deploy real, antes de qualquer rsync, o próprio backup já falhou:

				
					mkdir: cannot create directory '/var/www/deploy-backups': Permission denied
				
			

/var/www pertence ao usuário do servidor web, não ao usuário de deploy. Daria para resolver liberando escrita ao deploy dentro de /var/www, mas isso amplia o alcance da chave do CI sem necessidade — se o token do pipeline vazar, o atacante ganha escrita onde nunca precisou escrever. O backup foi para a pasta pessoal do usuário de deploy, que ele já é dono:

				
					mkdir -p /home/deploy/deploy-backups
tar czf /home/deploy/deploy-backups/tema-$(date +%Y%m%d%H%M%S).tar.gz -C /var/www/site/wp-content/themes tema
ls -t /home/deploy/deploy-backups/tema-*.tar.gz | tail -n +11 | xargs -r rm --
				
			
Rollback nesse esquema é extrair o tar de volta — não “restaurar backup” clicando em botão de painel.

Lição 2 — O -a do rsync quebra quando os arquivos são de outro dono

Arquivos de produção pertencem a www-data; o usuário deploy só participa do grupo. Ele escreve no diretório, mas não pode alterar dono, grupo ou permissão de um arquivo que não é dele. E foi exatamente isso que o -a do rsync tentou fazer em cada arquivo:

				
					rsync error: some files/attrs were not transferred (code 23)
				
			

-a é atalho para -rlptgoD: recursivo, links, permissões, tempos (mtime), grupo, dono e arquivos de dispositivo. Os quatro últimos — t, g, o e parte do p — exigem que o rsync seja dono do arquivo remoto, e ele não é. A correção é parar de pedir o que não é preciso:

				
					rsync -rlvz --checksum --delete ./tema/ deploy@vps:/var/www/site/wp-content/themes/tema/
				
			

Sem p/t/g/o, o rsync só precisa de escrita no diretório — que o grupo já tem. --checksum compara pelo conteúdo do arquivo em vez do mtime (perdido junto com o -t): sem ele, cada deploy retransmitiria tudo de novo, porque o mtime remoto nunca bate com o local. Dono e permissão corretos entram depois, num passo à parte com sudo pontual — chown/chmod liberados no sudoers só para esse caminho.

Lição 3 — O chmod do deploy anterior não pode quebrar o próximo

Deploy N passou limpo. Deploy N+1 falhou assim:

				
					mkstemp "/var/www/site/wp-content/themes/tema/.style.css.a1b2c3" failed: Permission denied (13)
				
			

O rsync escreve num arquivo temporário e troca o nome no final (mkstemp + rename) — por isso só precisa de escrita no diretório, não no arquivo. O problema era o passo final do deploy anterior: um chmod 755 nos diretórios, que tira a escrita do grupo. Cada deploy bem-sucedido quebrava o seguinte. A correção fica antes do rsync e se repete no final, para o estado nunca regredir:

				
					find /var/www/site/wp-content/themes/tema -type d -exec chmod 2775 {} \;
find /var/www/site/wp-content/themes/tema -type f -exec chmod 644 {} \;
				
			

2775 é escrita de grupo mais o bit setgid — todo arquivo novo criado ali nasce do mesmo grupo do diretório, sem depender do umask de quem criou. Arquivo continua 644: quem precisa de escrita é o diretório, não o arquivo em si.

Lição 4 — O pipeline também vigia o que entra no repo

As três lições acima são sobre o servidor. A quarta é sobre o repositório: todo PR que toca tema ou plugin passa por checagem antes do merge, não só depois.

				
					find tema plugin -name '*.php' -print0 | xargs -0 -n1 php -l
git diff --name-only origin/main...HEAD | grep -E '(^|/)(wp-config\.php|.*\.pem|.*\.sql|.*\.log|\.htaccess)$' && exit 1
				
			

php -l roda em todo arquivo PHP do tema e do plugin — erro de sintaxe bloqueia o merge, não só o deploy. O PHPCS roda também, mas informativo: comenta no PR e não trava nada. Se o diff tentar subir wp-config.php, uma .pem, .sql, .log ou .htaccess, o pipeline recusa o merge — arquivo desses no repositório não é código, é vazamento esperando data. O deploy só dispara quando o PR mergeado tocou tema ou plugin; mudança em documentação não publica nada.

O fluxo completo

Junto, as quatro lições viram este fluxo, do merge ao ar:

  1. Compacta tema e plugin, salva em /home/deploy/deploy-backups e rotaciona os últimos 10
  2. chmod 2775 nos diretórios publicados
  3. rsync -rlvz --checksum --delete (sem p/t/g/o)
  4. Normaliza dono e permissão com sudo pontual (chown/chmod liberados no sudoers)
  5. Recarrega o que precisar (cache de página, opcache)
  6. Smoke test: home, uma página interna e /wp-admin/
				
					curl -fsS -o /dev/null -w '%{http_code}\n' https://example.com/
curl -fsS -o /dev/null -w '%{http_code}\n' https://example.com/um-post-qualquer/
curl -fsS -o /dev/null -w '%{http_code}\n' https://example.com/wp-admin/
				
			

Utilidades

  • rsync --dry-run --delete antes do primeiro deploy real — mostra exatamente o que seria apagado, sem apagar nada.
  • Guarde pelo menos 10 backups rotacionados. Confiança não substitui arquivo em disco.
  • Nunca dê ao usuário do CI mais do que o diretório do site. Se a chave vazar, o estrago tem teto.
  • Se o erro code 23 voltar, é sempre a mesma causa: alguém trocou -rlvz de volta por -a num script ou alias esquecido.

Do mesmo assunto

Tutoriais, WordPress

25 ago 2026

Webhook de pedido no WordPress: não perder e não duplicar

Webhook de loja chega duas vezes, chega fora de ordem
Capa: Revisor automático de PR com IA

Tutoriais

30 jul 2026

Revisor automático de PR com IA, sem depender de máquina ligada

Como tirei um revisor de PR com IA da minha
Capa: Git no WordPress, versione só o que é seu

Tutoriais, WordPress

16 jul 2026

Git no WordPress: versione só o que é seu

Um WordPress que atendo tinha 225 arquivos untracked e um

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *