wandres.dev
WRANGLER AVANZADO · entornos y CI/CD

Workers Builds: el repositorio como fuente de despliegues

Conectar un repositorio Git a un Worker para que Cloudflare compile y publique cada push: comando de build y comando de deploy, previews por rama con `wrangler versions upload`, la diferencia entre variables de build y variables de runtime, y el uso de caché y rutas vigiladas para no pagar builds inútiles.

⏱ 18 min

Desplegar desde tu portátil funciona hasta el día en que dos personas despliegan a la vez, o en que alguien publica desde una rama que nunca se revisó, o en que el artefacto que llegó a producción no se corresponde exactamente con ningún commit. Workers Builds resuelve ese problema por la vía directa: conectas el repositorio, Cloudflare escucha los push y ejecuta la compilación y el despliegue en su propia infraestructura. Lo interesante no es la automatización en sí —eso lo hace cualquier CI— sino que aquí el sistema que construye y el que ejecuta son el mismo, lo que elimina de un plumazo la gestión de credenciales de despliegue. A cambio adquieres un conjunto nuevo de decisiones: qué comando construye, cuál publica, qué hace una rama que no es la principal y cómo evitar que cada cambio en el README dispare una compilación entera.

🎯 Al terminar esta lección sabrás
  • Conectar un repositorio a un Worker y distinguir el comando de build del de deploy.
  • Generar previews por rama publicando versiones sin tráfico en lugar de desplegar.
  • Separar con criterio las variables de build de las variables y secretos de runtime.
  • Usar rutas vigiladas y caché de dependencias para que cada push cueste lo que debe.

Dos comandos, dos responsabilidades

Al conectar un repositorio, Cloudflare pide dos comandos y la diferencia entre ellos es toda la lección. El de build transforma el repositorio en algo desplegable; el de deploy toma ese resultado y lo publica. Mantenerlos separados es lo que permite que la rama principal publique y las demás no.

# Configuracion tipica de un Worker conectado
directorio raiz: .
comando de build: npm run build
comando de deploy: npx wrangler deploy
rama de produccion: main

La primera decisión, sin embargo, es anterior a los comandos: qué rama es la de producción. Ese único campo define la topología entera del flujo, porque todo lo demás se expresa como excepción a él. Elegir la rama principal y protegerla es la opción por defecto y la que mejor envejece; elegir una rama de release separada tiene sentido cuando el ritmo de publicación no coincide con el de integración, a costa de mantener viva una divergencia permanente entre dos ramas.

El comando de deploy es literalmente una invocación de Wrangler dentro del entorno de build, con la autenticación ya resuelta. Eso significa que todo lo que sabes de Wrangler sigue valiendo: puedes escribir npx wrangler deploy --env production para seleccionar un entorno con nombre, o npx wrangler versions upload para producir una versión sin publicarla. Esa segunda opción es la pieza que hace posible el flujo por ramas.

Conviene no confundir esto con la clave build de wrangler.jsonc, que describe una compilación personalizada que el propio Wrangler ejecuta antes de empaquetar. Son capas distintas: la clave build corre en cualquier sitio donde se ejecute Wrangler, incluida tu máquina; el comando de build de Workers Builds es un paso previo que solo existe en la plataforma de compilación.

El entorno de compilación merece una comprobación temprana. Cloudflare detecta el gestor de paquetes a partir del archivo de bloqueo presente y elige una versión de Node por defecto que no tiene por qué coincidir con la tuya, algo que se corrige fijándola con un archivo de versión en el repositorio o con una variable de build. Alinear esa versión con la que usa el equipo en local es de las cosas que más tiempo ahorran, porque elimina de raíz la categoría entera de fallos que solo ocurren en el servidor de compilación.

El directorio raíz es el otro parámetro que decide si la configuración funciona a la primera. En un repositorio con un solo Worker es la raíz y no hay nada que pensar; en un monorepo debe apuntar a la carpeta del Worker concreto, porque es desde ahí donde se ejecutarán los comandos y donde Wrangler buscará su manifiesto. La instalación de dependencias, en cambio, suele necesitar la raíz del workspace, así que el comando de build de un monorepo acaba siendo una invocación que delega en la herramienta de orquestación en lugar de un simple npm run build.

También conviene saber leer dónde falló algo. Un build que revienta durante el comando de compilación es un problema de tu código o de tus dependencias; uno que revienta durante el comando de despliegue es casi siempre un problema de configuración —una fecha de compatibilidad inválida, un binding que apunta a un recurso inexistente, un entorno mal escrito—. Distinguirlo en el log antes de empezar a depurar evita buscar en el sitio equivocado.

Previews por rama sin tráfico real

El patrón que da valor real al sistema es asimétrico: la rama de producción despliega, las demás solo suben versiones. Cada rama secundaria produce entonces una versión con su URL de vista previa propia, alcanzable para revisar el cambio, y ningún usuario real la ejecuta jamás.

{
  "name": "tienda-web",
  "main": "src/index.ts",
  "compatibility_date": "2026-01-15",
  "preview_urls": true,
  "observability": { "enabled": true }
}

La habilitación de las URL de vista previa es explícita porque tiene consecuencias: una vez activas, cada versión que subas queda accesible desde internet, aunque sea en una dirección impredecible. Para un producto público eso rara vez importa; para uno que aún no se ha anunciado, o para un servicio interno, conviene decidirlo a propósito y no descubrirlo. La alternativa cuando el contenido no debe salir es dejarlas desactivadas y verificar los cambios en un Worker de un entorno de pruebas con acceso restringido.

Con preview_urls habilitado, cada versión recibe una dirección derivada de su identificador. La configuración por rama se expresa asignando comandos distintos: npx wrangler deploy para la rama de producción y npx wrangler versions upload para las de vista previa. El resultado es que un pull request obtiene una URL viva de su propio código sin que exista ningún camino por el que ese código atienda tráfico de clientes.

flowchart TD
P[push a rama de trabajo] --> B1[build en cloudflare]
B1 --> V[versions upload]
V --> U[url de vista previa]
M[merge a main] --> B2[build en cloudflare]
B2 --> D[wrangler deploy]
D --> T[trafico real]
style U fill:#89b4fa,color:#11111b
style T fill:#f38ba8,color:#11111b

La consecuencia de gobierno es más importante que la técnica: si la única forma de que algo reciba tráfico es un merge a la rama principal, entonces las reglas de protección de esa rama —revisión obligatoria, checks en verde— se convierten en las reglas de despliegue de tu producto. La política deja de vivir en la cabeza del equipo y pasa a estar aplicada por el sistema.

Hay además un efecto secundario que conviene aprovechar. Cada build queda asociado al commit que lo originó y publica su estado de vuelta en el repositorio, de modo que en la propia lista de commits se ve qué se construyó, qué falló y qué versión salió de cada uno. Esa trazabilidad hacia atrás —de un incidente en producción al commit exacto que lo introdujo— es difícil de conseguir cuando los despliegues salen de portátiles, y aquí viene puesta.

Una advertencia sobre la vida útil de los previews. Cada push a una rama produce una versión nueva con su propia URL, así que un pull request activo acumula tantas direcciones como iteraciones haya tenido. Compartir una de ellas en un canal de revisión y volver a ella tres días después es una fuente clásica de confusión, porque esa URL sigue sirviendo el código de entonces y no el actual. La costumbre sana es citar siempre la versión más reciente y decir explícitamente a qué commit corresponde.

⚠️
Un preview no es inofensivo por ser preview

Una versión de vista previa no recibe tráfico de usuarios, pero se ejecuta con los bindings y los secretos del Worker al que pertenece. Si ese Worker es el de producción, el preview de una rama cualquiera puede escribir en la base de datos real. La separación correcta es que las ramas de trabajo construyan hacia un Worker de un entorno de pruebas, con sus propios recursos, y que solo la rama principal toque el Worker de producción. Que la URL sea difícil de adivinar no es un control de acceso.

Build y runtime son dos mundos

Es el malentendido más frecuente del sistema y produce fallos desconcertantes. Existen dos conjuntos de variables completamente distintos y no se ven entre sí.

Dimensión Variables de build vars y secretos del Worker
Dónde viven Configuración de Workers Builds wrangler.jsonc y el almacén cifrado del Worker
Cuándo existen Durante la compilación, en el contenedor de build Durante cada petición, en el objeto env
Cómo se leen process.env del proceso de build env.NOMBRE dentro del handler
Para qué sirven Tokens de registries privados, flags del bundler Credenciales de servicios, configuración de la aplicación

Un token de npm privado es una variable de build y no tiene nada que hacer en el Worker. Una clave de una pasarela de pago es un secreto de runtime y no debe existir en el contenedor de compilación. Meter un valor en el lado equivocado no siempre falla de forma ruidosa: a veces simplemente llega como indefinido, y a veces —peor— acaba inlineado en el bundle por el bundler y viaja a producción incrustado en el código, donde cualquiera que descargue el artefacto puede leerlo.

Dentro de las variables de build hay a su vez dos clases: las que se ven en el log y las que se enmascaran por declararse como secretos de build. La distinción no es cosmética. Cualquier valor no marcado como secreto puede acabar impreso por un comando verboso, por un mensaje de error de una dependencia o por un simple volcado del entorno, y los logs de build suelen ser visibles para más gente que los secretos del Worker. Un token de registry privado se declara siempre como secreto de build, aunque parezca inofensivo.

El caso límite que confunde a todo el mundo es el frontend. Un framework que compila su cliente necesita, en tiempo de build, valores que acabarán visibles en el navegador: la URL pública de la API, una clave publicable de analítica. Esos sí son variables de build, y precisamente por eso ninguno de ellos puede ser sensible. La prueba mental es infalible: si te incomodaría que ese valor apareciera al inspeccionar el código descargado por un usuario, entonces no es una variable de build, es un secreto de runtime y debe leerse desde env dentro del Worker.

El coste de cada push

Un repositorio activo genera decenas de push al día y no todos merecen una compilación. La cuenta es sencilla y sale mal enseguida: veinte push diarios por cinco Workers en un monorepo sin filtros son cien builds al día, casi todos reconstruyendo código que nadie tocó. Dos mecanismos acotan ese gasto y ambos conviene configurarlos desde el principio, no cuando duele.

👀

Rutas vigiladas

Patrones de inclusión y exclusión que deciden si un push dispara build. Un cambio solo en documentación no debería reconstruir nada.

📦

Caché de build

Reutiliza el almacén del gestor de paquetes entre compilaciones. Convierte la instalación de dependencias de minutos en segundos.

📁

Directorio raíz

En un monorepo, apunta cada Worker a su subcarpeta para que los comandos corran donde deben y no en la raíz.

🧾

Registro de builds

Cada build guarda su log y el commit que lo originó. Es la trazabilidad que responde a qué artefacto está en producción.

El registro de builds, por su parte, es lo que convierte todo esto en auditable. Ante la pregunta de qué está corriendo en producción ahora mismo, la respuesta no debería ser una conjetura basada en el historial de Git sino la lista de builds, con su commit, su resultado y la versión que produjo cada uno.

Las rutas vigiladas son especialmente valiosas en repositorios con varios Workers: sin ellas, tocar cualquier archivo reconstruye y redespliega todos, lo que además de costar tiempo genera versiones nuevas de servicios que no cambiaron y ensucia el historial. Su punto ciego, eso sí, es que solo entienden de carpetas: un cambio en un paquete compartido no dispara los Workers que dependen de él salvo que incluyas esa ruta en cada patrón, y omitirlo produce despliegues parciales silenciosos.

Hay una tercera palanca que se olvida y que suele ser la más eficaz de todas: reducir lo que hay que construir. Un paso de compilación que no genera nada útil, una dependencia pesada que solo se usa en desarrollo, un paquete de iconos entero importado por tres símbolos. Ninguna caché arregla un build que hace trabajo innecesario; solo lo hace más rápido de repetir.

La caché ataca el otro extremo del problema, el de la latencia entre el merge y el despliegue, que es lo que determina si el equipo integra a menudo o acumula cambios porque publicar es lento. La regla empírica es que por encima de unos pocos minutos la gente deja de integrar en pequeño, y el tamaño de los cambios crece hasta que cada despliegue vuelve a dar miedo. Optimizar el build no es una obsesión de eficiencia: es lo que mantiene viable la entrega continua.

💡
Mide el tiempo del build antes de optimizarlo

Antes de tocar nada, mira el log de una compilación reciente y anota cuánto tarda cada fase: clonado, instalación de dependencias, compilación y despliegue. Casi siempre una sola fase domina el total, y casi siempre es la instalación, que es justo la que resuelve la caché. Optimizar la fase equivocada es el error clásico: se invierten horas en afinar el bundler para recortar diez segundos mientras la instalación se lleva tres minutos.

Queda una decisión de fondo: cuándo Workers Builds deja de bastar. Mientras el pipeline sea construir y publicar, es difícil justificar montar otra cosa. En cuanto necesitas ejecutar pruebas que decidan si se despliega, orquestar varios repositorios, exigir aprobaciones manuales con matices o correr pasos que no son de Node, el CI propio recupera el sitio. Ambos pueden convivir: nada impide que Workers Builds gobierne los previews y que un pipeline externo publique producción.

Esa convivencia tiene una condición para no volverse un lío: que haya un único camino hacia producción y esté escrito. Dos sistemas capaces de desplegar el mismo Worker producen, tarde o temprano, la situación en la que uno pisa lo que el otro acababa de publicar y nadie entiende por qué volvió el bug de la semana pasada. Reparte por función, no por conveniencia, y deja constancia de qué sistema es el dueño de cada entorno.

Quien tiene la llave decide qué es producción

El detalle que más cambia la naturaleza de Workers Builds no es que compile por ti, es que no te obliga a crear un token de API de despliegue. Piensa en lo que significa. En un CI convencional generas una credencial capaz de publicar en producción, la guardas en un sistema de terceros, la compartes con cada workflow que la necesite y aceptas que cualquiera con permiso de escritura sobre esos workflows puede, en la práctica, ejecutar código con esa credencial. La superficie de ataque de tu despliegue deja de ser tu cuenta de Cloudflare y pasa a ser la unión de tu cuenta, tu proveedor de CI, tus acciones de terceros y el árbol entero de dependencias que se instala durante el build. Cuando el sistema que construye es el mismo que ejecuta, esa credencial no existe: la autorización es la conexión entre el repositorio y la cuenta, revocable de una vez, sin nada que rotar ni que se pueda filtrar en un log. A cambio cedes control —el entorno de build lo define Cloudflare, y lo que puedes hacer antes de publicar está acotado a lo que ese entorno permite—, y esa cesión es exactamente el trade-off que debes evaluar. La pregunta correcta no es cuál de los dos sistemas es más potente, porque el CI propio siempre lo será. La pregunta es dónde prefieres que viva la autoridad para publicar en producción: en una relación entre dos sistemas que controlas, o en una cadena de secretos que atraviesa una infraestructura que no controlas del todo. Muchos equipos maduros acaban en la respuesta mixta, y no por indecisión: previews desde Workers Builds, donde la comodidad manda y el daño es acotado; producción desde un pipeline explícito, donde la ceremonia es una virtud.

⚔️ Convierte tu rama principal en la única puerta a producción
  1. Conecta un repositorio a un Worker y configura el comando de build y el de deploy por separado. Confirma en el log cuál corre primero.
  2. Configura las ramas que no son la principal para que ejecuten wrangler versions upload y abre la URL de vista previa de un pull request.
  3. Añade una variable de build y una var de runtime con el mismo nombre y valores distintos, y demuestra desde el Worker cuál de las dos ve.
  4. Define rutas vigiladas que excluyan la documentación y comprueba con un commit que ese push no dispara ninguna compilación.
  5. Escribe qué reglas de protección tiene tu rama principal y comprueba si describen con exactitud tu política de despliegue. Si no coinciden, una de las dos está mal.