Saltar al contenido
Development

El despliegue de Cloudflare Workers Builds que falló por un archivo de configuración que discrepaba consigo mismo

Por Victor Da Luz
cloudflareciastrodev-logsite

GitHub Actions dejó de funcionar para este sitio hace algunas semanas. La facturación de la cuenta venció, y decidí no resolverlo pagando. El repositorio es privado, así que Actions nunca iba a volver gratis. Eso dejó roto el único camino de despliegue del sitio: cada push a main compilaba bien localmente pero nunca llegaba a producción, porque el job de CI que corría wrangler deploy simplemente nunca arrancaba.

La solución que todos recomiendan es Cloudflare Workers Builds. Es el propio CI de Cloudflare conectado a Git para Workers, sin necesidad de minutos de GitHub Actions. Conectar el repositorio, definir un comando de build y uno de despliegue, listo. Lo redacté como un plan, borré los dos archivos de workflow muertos, documenté el nuevo flujo, hice merge. Después me hice la pregunta obvia siguiente: ¿realmente despliega?

El primer fallo parecía que debía ser culpa mía

Había probado el comando de despliegue localmente antes de escribir las instrucciones del panel: npx wrangler deploy dist/server/entry.mjs --dry-run corrió limpio. Así que le indiqué al panel que usara npx wrangler deploy dist/server/entry.mjs como comando de despliegue y lo di por terminado.

El build falló. Lo único que tenía era una captura de pantalla: “Failed: error occurred while running deploy command.” Sin ningún detalle. Pedí las líneas de log anteriores a esa, recibí una captura del log desplazado al lugar equivocado, volví a pedirlo, y en ese punto dije lo que realmente hizo avanzar esto: andar copiando y pegando logs de un lado a otro es una payasada de las cavernas, necesito acceso real.

Conseguir acceso real significó aprender dos cosas sobre los tokens de Cloudflare

El movimiento obvio era crear un token de API nuevo y de alcance reducido. El primer instinto fue limitarlo a “este único Worker.” Eso estaba mal por dos razones. Primero, la cuenta de Cloudflare en cuestión ya abarca cuatro sitios, no uno solo, así que “la cuenta” nunca fue un alcance reducido de entrada. Segundo, y más útil: ya había un token de despliegue funcional guardado en el vault de Ansible del homelab, usado exactamente para este tipo de cosa. El verdadero error fue crear un token nuevo antes de revisar qué ya existía, no el alcance elegido.

Recuperé el token existente y lo probé. Funcionaba bien para leer Workers Scripts en los cuatro sitios. Falló por completo contra la API de Workers Builds con un escueto “Authentication error.” Resultó que Cloudflare traza una línea real aquí: la API de Builds solo acepta tokens de usuario, los que un humano crea desde su propia página de perfil, no tokens propiedad de la cuenta creados para una cuenta de servicio o un pipeline de CI. Usar el otro tipo provoca un rechazo antes de que los permisos siquiera entren en juego. Dos tokens distintos, dos trabajos distintos, sin forma de que uno cumpla ambos.

Así que hacía falta un segundo token, creado a mano, con alcance a Workers Builds Configuration y acceso de lectura a Workers Scripts. Una vez que lo tenía, guardarlo se convirtió en su propio pequeño desvío. La idea era tenerlo en el vault junto al token existente, mismo lugar, un solo patrón. Pero desencriptar un vault compartido para agregar una línea significa que todo el archivo queda en texto plano en disco por un momento, y la propia red de seguridad (con razón) no toleró que eso ocurriera como efecto secundario de depurar un script de despliegue. No opuse resistencia. En cambio, dejé el token en su propio archivo con chmod 600, con la misma forma que una credencial ya usada para otra herramienta, y seguí adelante. La consolidación del vault sigue siendo una buena idea, simplemente no es el problema de hoy.

Lo que los logs realmente decían

Con el token correcto, la API REST de Workers Builds es directa: listar los builds de un Worker por su tag (no su nombre, un campo distinto), tomar el UUID de un build, obtener sus logs. El log del build fallido terminaba con esto:

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

@astrojs/cloudflare escribe su propio wrangler.json en la salida del build del servidor. Es un archivo real, generado de nuevo en cada build, que describe el punto de entrada y los bindings. Workers Builds, por su parte, prepara su propia configuración de despliegue en una ubicación distinta. Al apuntar wrangler al archivo de entrada sin una bandera --config explícita, encuentra dos configuraciones candidatas en dos rutas diferentes y se niega a adivinar entre ellas. Esto nunca apareció en el dry-run local, porque un checkout simple nunca tiene ese segundo archivo de configuración preparado. Solo existe dentro del propio entorno de Workers Builds.

La solución fue una sola bandera: npx wrangler deploy --config wrangler.toml dist/server/entry.mjs. El workflow de CI viejo y muerto siempre había tenido esa bandera. La omití al escribir las nuevas instrucciones del panel de memoria en vez de copiar el comando que funcionaba.

Verificarlo de verdad, no solo creerlo

Corregir la configuración del panel no bastaba por sí solo, quería ver un build ponerse realmente en verde antes de darlo por terminado. La API de Builds tiene un endpoint para disparar un build nuevo directamente, así que lancé uno manualmente contra el trigger corregido y lo fui consultando hasta que se detuvo. Estado: success. Un despliegue nuevo apareció en wrangler deployments list, el sitio seguía devolviendo 200.

Eso habría bastado para la mayoría de las correcciones, pero quería probar también el camino real, no solo el manual. Así que hice push de un segundo commit genuino (una corrección de documentación que registraba la solución) y observé el log del build correspondiente específicamente a ese push. Sus metadatos de trigger decían build_trigger_source: push_event, ligados a ese hash de commit exacto. Eso es lo que el issue realmente pedía: un git push normal a main, sin trigger manual, terminando en un despliegue en vivo, sin GitHub Actions en ningún punto de la cadena.

También encontré un segundo error, más pequeño, de regalo mientras hurgaba en todo esto: el trigger “deploy non-production branches” que había activado como un extra estaba fallando en cada rama de PR de Dependabot, al intentar aprovisionar automáticamente un namespace de KV que ya existía para producción. Causa raíz distinta, mismo tema: la configuración no fijaba algo explícito que las herramientas de Cloudflare necesitaban fijado. Esa es una corrección real (vincular un ID de namespace explícito), solo que no es una que bloquee nada hoy, así que la dejé deshabilitada y la registré como seguimiento en vez de perseguirla mientras el issue real seguía abierto.

Qué haría diferente

Copiar el comando que funciona en vez de reconstruirlo a partir de una prueba dry-run que pasó por razones ajenas al caso. Un dry-run demuestra que la configuración resuelve, no demuestra que el paso de despliegue corre en el mismo entorno en el que realmente va a correr. Y antes de crear cualquier credencial nueva, revisar qué ya hay guardado en el lugar donde se supone que viven las credenciales. El instinto correcto apareció, solo que no en el primer intento.

Lecturas relacionadas