Pular para o conteúdo
Development

O deploy do Cloudflare Workers Builds que falhou por causa de um arquivo de config discordando de si mesmo

Por Victor Da Luz
cloudflareciastrodev-logsite

O GitHub Actions parou de funcionar para este site algumas semanas atrás. A cobrança da conta expirou, e decidi não resolver isso pagando por ela. O repositório é privado, então o Actions nunca voltaria de graça. Isso deixou o único caminho de deploy do site quebrado: todo push para a main compilava bem localmente mas nunca chegava em produção, porque o job de CI que rodava wrangler deploy simplesmente nunca começava.

A correção que todo mundo indica é o Cloudflare Workers Builds. É o CI próprio do Cloudflare, conectado ao Git, para Workers, sem precisar de minutos do GitHub Actions. Conecta o repositório, define um comando de build e um comando de deploy, pronto. Escrevi isso como um plano, apaguei os dois arquivos de workflow mortos, documentei o novo fluxo, mesclei. Depois fiz a pergunta óbvia seguinte: será que isso de fato faz o deploy?

A primeira falha parecia que devia ser culpa minha

Eu tinha testado o comando de deploy localmente antes de escrever as instruções do painel: npx wrangler deploy dist/server/entry.mjs --dry-run rodou limpo. Então configurei o painel para usar npx wrangler deploy dist/server/entry.mjs como comando de deploy e considerei o assunto encerrado.

O build falhou. Tudo o que eu tinha era um screenshot: “Failed: error occurred while running deploy command.” Nenhum detalhe. Pedi as linhas de log acima disso, recebi um screenshot do log rolado para o lugar errado, pedi de novo, e nesse ponto falei a coisa que realmente fez isso andar: ficar copiando e colando logs de um lado para o outro é coisa de troglodita, me dá acesso de verdade.

Ter acesso de verdade significou aprender duas coisas sobre tokens do Cloudflare

A jogada óbvia era criar um token de API novo, com escopo bem restrito. Meu primeiro instinto foi restringi-lo a “este Worker aqui.” Isso estava errado em duas contas. Primeiro, a conta do Cloudflare aqui já abrange quatro sites, não um só, então “a conta” nunca foi um escopo restrito para começar. Segundo, e mais útil: já existia um token de deploy funcionando, guardado no vault do Ansible do homelab, usado exatamente para esse tipo de coisa. Criar um token novo antes de checar o que já existia foi o erro de verdade, não o escopo que escolhi.

Peguei o token existente e testei. Funcionou bem para ler Workers Scripts nos quatro sites. Falhou completamente contra a API do Workers Builds, com um simples “Authentication error.” Acontece que o Cloudflare traça uma linha real aqui: a API do Builds só aceita tokens de usuário, aqueles que um humano cria pela própria página de perfil, não tokens de conta criados para uma service account ou pipeline de CI. Tenta usar o outro tipo e você é rejeitado antes mesmo das permissões entrarem em jogo. Dois tokens diferentes, dois trabalhos diferentes, e nenhum jeito de fazer um cobrir os dois.

Então eu precisava de um segundo token, criado à mão, com escopo de Workers Builds Configuration e acesso de leitura a Workers Scripts. Assim que tive, guardá-lo virou seu próprio pequeno desvio. Eu queria ele no vault, ao lado do token existente, mesmo lugar, um padrão só. Mas descriptografar um vault compartilhado para adicionar uma linha significa que o arquivo inteiro fica em texto plano no disco por um momento, e minha própria rede de segurança (corretamente) não gostou disso acontecer como efeito colateral de depurar um script de deploy. Não briguei com isso. Em vez disso, coloquei o token em seu próprio arquivo chmod 600, no mesmo formato de uma credencial que já uso para outra ferramenta, e segui em frente. A consolidação do vault ainda é uma boa ideia, só não é o problema de hoje.

O que os logs realmente diziam

Com o token certo, a API REST do Workers Builds é direta: lista os builds de um Worker pela sua tag (não pelo nome, um campo diferente), pega o UUID de um build, busca os logs dele. O log do build que falhou terminava com isto:

Executing user deploy command: npx wrangler deploy dist/server/entry.mjs

✘ [ERROR] Found both a user configuration file at "dist/server/wrangler.json"
  and a deploy configuration file at ".wrangler/deploy/config.json".
  But these do not share the same base path so it is not clear which should be used.

Failed: error occurred while running deploy command

O @astrojs/cloudflare escreve seu próprio wrangler.json na saída do build do servidor. É um arquivo real, gerado do zero a cada build, descrevendo o entry point e os bindings. O Workers Builds, separadamente, monta sua própria config de deploy em outro lugar. Aponte o wrangler para o arquivo de entrada sem uma flag --config explícita, e ele encontra dois arquivos de config candidatos em dois caminhos diferentes e se recusa a adivinhar entre eles. Isso nunca apareceu no meu dry-run local, porque um checkout comum nunca tem esse segundo arquivo de config montado por perto. Ele só existe dentro do próprio ambiente do Workers Builds.

A correção foi uma flag: npx wrangler deploy --config wrangler.toml dist/server/entry.mjs. O workflow de CI antigo e morto tinha essa flag desde sempre. Eu a deixei de fora quando escrevi as novas instruções do painel de memória, em vez de copiar o comando que já funcionava.

Verificando de verdade, não só acreditando

Corrigir a configuração do painel não bastava sozinho; eu queria ver um build de fato ficar verde antes de considerar isso resolvido. A API do Builds tem um endpoint para disparar um build novo diretamente, então disparei um manualmente contra o trigger corrigido e fiquei checando até ele parar. Status: sucesso. O novo deploy apareceu no wrangler deployments list, o site continuou retornando 200.

Isso teria bastado para a maioria das correções, mas eu queria provar o caminho de verdade também, não só o manual. Então fiz um segundo push, genuíno (uma correção de documentação registrando a correção) e observei o log do build daquele push especificamente. Os metadados do trigger diziam build_trigger_source: push_event, amarrados exatamente àquele hash de commit. Isso é a coisa que a issue de fato pedia: um git push normal para a main, sem trigger manual, terminando num deploy ao vivo, sem nenhum GitHub Actions em nenhum ponto da cadeia.

Também encontrei de graça um segundo bug, menor, enquanto mexia em tudo isso: o trigger de “deploy non-production branches” que eu tinha ligado como um extra estava falhando em toda branch de PR do Dependabot, tentando auto-provisionar um namespace do KV que já existia para produção. Causa raiz diferente, mesmo tema: a config não fixava algo explícito que as ferramentas do Cloudflare precisavam que fosse fixado. Essa é uma correção real (vincular um ID de namespace explícito), só que não é uma que bloqueia nada hoje, então deixei desativado e registrei como um follow-up em vez de correr atrás dela enquanto a issue de verdade ainda estava aberta.

O que eu faria diferente

Copiar o comando que já funciona em vez de reconstruí-lo a partir de um teste de dry-run que passou por motivos sem relação nenhuma. Um dry-run prova que a config resolve; não prova que o passo de deploy roda no mesmo ambiente em que vai de fato rodar. E antes de criar qualquer credencial nova, checar o que já está guardado no lugar onde as credenciais deveriam estar. Eu tive o instinto certo no fim das contas, só não na primeira tentativa.

Leitura relacionada

Development

O CSP que só quebrou em produção

Quatro falhas atrás de um único header: middleware morto, regras de _headers que se combinam em vez de sobrescrever, um worker de blob URL sob script-src, e um script que só o edge injeta.

Ler

Você também pode achar útil

AdGuard

AdGuard para iOS

Bloqueio de anúncios e rastreadores em todo o sistema no iOS, sem necessidade de um servidor DNS separado.

Como afiliado da AdGuard, ganho com compras qualificadas.

Saiba mais
RackNerd

RackNerd VPS

Hospedagem VPS econômica para serviços leves que funcionam continuamente.

Como afiliado da RackNerd, ganho com compras qualificadas.

Saiba mais
Proton

Proton VPN

VPN comercial com filtragem NetShield e interruptor de desligamento automático.

Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).

Saiba mais