wandres.dev
REMOTE CACHING · compartir la caché

Self-hosted: el protocolo abierto y cuándo alojar tu caché

La caché remota es solo una API HTTP documentada, así que cualquiera puede implementar el servidor: existen implementaciones open source del protocolo que guardan artefactos en tu propio S3, GCS o disco. Turborepo apunta a cualquier endpoint con TURBO_API. Cuándo alojarla tú mismo por residencia de datos, coste o aislamiento, y cuándo no vale la pena asumir su operación.

⏱ 17 min

La caché remota alojada de Vercel es cómoda, pero no es magia propietaria: por debajo es un protocolo HTTP documentado —subir y bajar artefactos indexados por hash, acotados a un equipo—. Y un protocolo abierto se puede reimplementar. Existen servidores open source que hablan ese mismo protocolo y guardan los artefactos en tu propio S3, tu GCS o tu disco, y Turborepo apunta a cualquiera de ellos cambiando TURBO_API. El cliente no se entera: para turbo, tu caché self-hosted es indistinguible de la de Vercel. Alojarla tú es una decisión de soberanía y economía, no de capacidad.

🎯 Al terminar esta lección sabrás
  • Entender que la caché remota es un protocolo abierto y por eso reimplementable.
  • Conocer implementaciones open source y cómo apuntar a ellas con TURBO_API.
  • Decidir con criterio cuándo alojar la caché tú mismo y cuándo no compensa.
  • Enumerar lo que pasas a operar: evicción, autenticación, firma y observabilidad.

El protocolo es abierto

El punto de partida es liberador: no hay nada secreto en cómo funciona la caché remota. El cliente sube un artefacto con un PUT a /v8/artifacts/:hash, lo descarga con un GET, consulta el estado con otras rutas, y autentica con el token y el equipo. Eso es todo. Cualquier servidor que responda a esas rutas es una caché remota válida para Turborepo, porque el cliente solo sabe hablar el protocolo; le da igual qué haya al otro lado.

Esa indiferencia del cliente es la propiedad de diseño más importante de todo el sistema. Significa que el acoplamiento entre tu build y tu caché es mínimo: una URL. No hay SDK propietario, ni formato secreto, ni dependencia de un proveedor; hay un contrato HTTP que cualquiera puede implementar y contra el que cualquiera puede migrar. En un mundo donde el vendor lock-in es la norma de las herramientas de infraestructura, que la caché se reduzca a un endpoint intercambiable es una libertad que conviene valorar.

Apuntar a un servidor propio es cambiar una variable. TURBO_API deja de valer el endpoint por defecto de Vercel y pasa a valer la URL de tu servidor; TURBO_TOKEN y TURBO_TEAM siguen cumpliendo su papel de autenticación y ámbito, ahora contra tu implementación.

export TURBO_API="https://cache.miempresa.dev"
export TURBO_TOKEN="un-token-que-valida-tu-servidor"
export TURBO_TEAM="plataforma"
turbo run build

Del lado del servidor hay varias implementaciones open source del protocolo. La más conocida es un servidor en Node —sobre Fastify— que actúa de adaptador: recibe los artefactos por el protocolo de Turborepo y los persiste en el almacenamiento que le configures, sea S3, Google Cloud Storage, Azure Blob o el disco local. Hay también reimplementaciones en otros lenguajes y despliegues serverless sobre tu propia nube. En el mundo de Nx el modelo es análogo aunque con otras piezas: Nx Cloud es la opción alojada, y para self-hosted existe la caché remota personalizada sobre tu propio bucket. En todos los casos la idea es la misma: el protocolo es el contrato, y el almacenamiento es un detalle que eliges.

# Configuracion tipica de un servidor de cache open source
storage:
  provider: s3
  bucket: turbo-cache-plataforma
  region: eu-west-1
tokens:
  - equipo: plataforma
    valor: un-token-que-valida-tu-servidor

El almacenamiento es intercambiable porque el servidor lo abstrae detrás del mismo protocolo. Puedes empezar con disco local para probar, migrar a un bucket de objetos cuando quieras compartir de verdad entre máquinas, y cambiar de proveedor más tarde sin que ninguna máquina cliente se entere, porque para turbo nada de eso existe: solo ve el endpoint. Esa indiferencia al backend es la contrapartida natural de que la caché sea un protocolo y no un producto; la sustancia está en el contrato HTTP, y todo lo demás es una decisión de operación que puedes cambiar sin tocar una línea del lado cliente.

flowchart LR
dev[Maquinas del equipo] -->|protocolo HTTP| srv[Servidor de cache open source]
ci[Jobs de CI] -->|TURBO_API apunta aqui| srv
srv --> store[Tu almacenamiento S3 o GCS]
style srv fill:#89b4fa,color:#11111b
style store fill:#f9e2af,color:#11111b

Poner en marcha uno de estos servidores es sorprendentemente poco ceremonioso. La implementación en Node, por ejemplo, se levanta como cualquier otro servicio, con unas pocas variables de entorno que le dicen qué backend de almacenamiento usar y qué tokens aceptar.

# Un servidor de cache open source apuntando a un bucket S3
docker run -p 3000:3000 \
  -e STORAGE_PROVIDER=s3 \
  -e STORAGE_PATH=turbo-cache-plataforma \
  -e TURBO_TOKEN=un-token-que-valida-tu-servidor \
  ghcr.io/ejemplo/turbo-remote-cache

A partir de ahí, apuntar TURBO_API a ese host es todo lo que separa a tu equipo de una caché compartida bajo tu control. La barrera de entrada, técnicamente, es baja: la sustancia difícil no está en encender el servidor, sino en mantenerlo sano, y de eso trata el resto de la lección.

Conviene situar esto en perspectiva histórica: la caché remota de artefactos no la inventó Turborepo. Sistemas de build como Bazel o Gradle llevan años ofreciendo cachés remotas e incluso ejecución remota, con la misma premisa —si el trabajo ya se hizo con estas entradas, no lo repitas— sobre protocolos propios. Lo que Turborepo y Nx aportan es llevar esa idea, antes reservada a la infraestructura de las grandes tecnológicas, al monorepo de JavaScript con dos comandos. Que ahora sea trivial no debe hacer olvidar que es una técnica madura y probada, no una moda pasajera.

Cuándo alojarla tú mismo, y cuándo no

Que puedas alojarla no significa que debas. La decisión se toma sopesando razones concretas a favor y un coste fijo en contra. A favor pesan cuatro motivos, y suele bastar uno serio. La residencia de datos: si tus artefactos y logs de build no pueden salir de tu nube o de una región concreta por cumplimiento normativo, la caché tiene que vivir donde tú controlas. El coste a gran escala: con tráfico de caché muy alto, guardar en tu propio S3 puede salir más barato que un servicio por asiento. El aislamiento: entornos on-premise o sin salida a internet no pueden depender de un SaaS externo. Y la proximidad: una caché en la misma red que tus runners reduce latencia y egreso frente a una remota.

En contra pesa una sola cosa, pero grande: alojar la caché significa que pasas a operar una pieza de infraestructura de build. Ya no es problema de otro que esté disponible, que no se llene, que sea segura, que se purgue. Para equipos pequeños, ese coste operativo casi nunca compensa frente a una caché alojada que es gratis en personal y barata en equipo y que no operas. La regla práctica: aloja cuando una razón de soberanía, coste o aislamiento lo exija, no porque suene a control; el control que ganas viene con una guardia que ahora es tuya.

Aloja tú mismo si Usa la alojada si
los datos no pueden salir de tu nube o región no tienes requisitos de residencia
la escala hace más barato tu propio almacenamiento el equipo es pequeño o mediano
el entorno es on-premise o sin salida a internet quieres cero operaciones
quieres la caché en la misma red que tus runners la latencia contra el SaaS es aceptable

El coste operativo es fácil de subestimar porque no se paga por adelantado, sino en forma de guardias futuras. El día que la caché se llene, que un token caduque o que la latencia se dispare, alguien de tu equipo tendrá que diagnosticarlo, y ese alguien probablemente preferiría estar construyendo producto. Alojar la caché es contratar, implícitamente, una pequeña responsabilidad de operaciones perpetua. Vale la pena cuando la soberanía o el coste lo justifican; no vale la pena por el placer abstracto de controlarlo todo.

⚠️
La caché debe degradar, nunca tumbar el build

Un principio no negociable al operar tu propia caché: si el servidor de caché está caído o lento, el build tiene que degradar a compilar en local, no fallar. La caché es una optimización, no una dependencia dura del camino crítico. Una caché self-hosted mal diseñada que bloquea las builds cuando se cae es peor que no tener caché, porque convierte una mejora de velocidad en un punto único de fallo para toda la organización. Verifica explícitamente que un servidor inalcanzable produce compilaciones normales, solo más lentas.

Operar una caché compartida: evicción, autenticación y envenenamiento

Si decides alojarla, heredas una lista de responsabilidades que el SaaS te ocultaba. La primera es la evicción: los artefactos se acumulan sin fin, y cada cambio de código genera hashes nuevos; sin una política de purga —por tiempo de vida o por menos usado recientemente— tu almacenamiento crece sin techo. La segunda es la autenticación: emitir, validar y rotar los tokens que dan acceso, con el alcance mínimo para cada consumidor. La tercera es la observabilidad: sin métricas no sabes si la caché sirve, y la métrica que manda es la tasa de aciertos; una caché con hit rate bajo consume recursos sin devolver tiempo.

De esas responsabilidades, la evicción es la que más ingenieros subestiman. Un monorepo activo genera hashes nuevos con cada commit, y cada hash nuevo es un artefacto nuevo; sin purga, el almacenamiento crece de forma monótona hasta que el coste de guardar supera al de compilar, que es el colmo de la ironía. Las políticas habituales son por tiempo de vida —expulsar lo que no se ha tocado en semanas— o por capacidad, echando lo menos usado recientemente al llegar a un límite. Lo esencial es que la evicción sea segura por construcción: expulsar un artefacto solo puede provocar un miss, nunca un error, porque un miss se resuelve recompilando. La caché puede olvidar sin peligro; lo que no puede es mentir.

La autenticación, la segunda responsabilidad, tiene un matiz propio de un almacén compartido: no basta con un único token para todos. Lo maduro es emitir credenciales distintas para consumidores distintos —una para el CI de la rama principal con escritura, otra de solo lectura para los PRs, quizá una personal por desarrollador— de modo que revocar una no derribe a las demás y que un token filtrado tenga el menor alcance posible. Un solo token compartido por todo el mundo es cómodo el primer día y una pesadilla el día que hay que rotarlo.

Y la cuarta, la más delicada, es la seguridad frente al envenenamiento. Una caché compartida y escribible es una superficie de ataque: quien pueda escribir un artefacto bajo el hash de una tarea legítima puede repartir su salida manipulada a todo el que obtenga ese acierto. Las defensas son las mismas que en la caché alojada, ahora bajo tu responsabilidad de configurarlas: firma de artefactos con clave compartida para verificar integridad y procedencia, solo lectura para los escritores no confiables como los PRs de forks, y aislamiento correcto de las entradas para que un hash no colisione entre contextos que no deberían compartir salida. El envenenamiento de caché de build documentado en 2025 dejó la lección clara: en un almacén compartido, el peor fallo no es la caída, es la incorrección silenciosa, un artefacto malo que todos restauran creyendo que es bueno.

La observabilidad merece un apunte concreto porque es la que convierte todo lo anterior en algo gobernable. Sin un panel que muestre la tasa de aciertos, el tamaño medio de los artefactos y la latencia de las peticiones, operas a ciegas: no sabrías distinguir una caché sana de una que la gente ha dejado de usar en silencio porque se volvió más lenta que compilar. Esos tres números —cuánto aciertas, cuánto pesa cada acierto y cuánto tarda— son el cuadro de mandos mínimo de una caché self-hosted, y medirlos desde el primer día es mucho más barato que descubrir su ausencia el día que algo va mal.

🗑️

Evicción

Los artefactos se acumulan. Sin política de purga por antigüedad o por uso, el almacenamiento crece sin límite.

📊

Observabilidad

La métrica que manda es la tasa de aciertos. Sin medirla no sabes si la caché devuelve tiempo o solo consume recursos.

☠️

Envenenamiento

Un almacén escribible es superficie de ataque. Firma, solo lectura para no confiables y aislamiento de entradas son la defensa.

Anatomía de una petición a la caché

Para desmitificar del todo el protocolo, vale la pena seguir una petición concreta. Cuando Turborepo quiere saber si existe el artefacto de un hash, hace un GET a /v8/artifacts/:hash con el token en la cabecera de autorización y el equipo como parámetro. Si el servidor responde con el cuerpo del artefacto, es un acierto; si responde que no existe, es un miss. Tras ejecutar en un miss, sube el resultado con un PUT a la misma ruta, con el tarball como cuerpo.

# Consulta (hit o miss) y subida, conceptualmente
GET  /v8/artifacts/3f8a1c2b?teamId=plataforma   -> 200 hay artefacto, o 404 no
PUT  /v8/artifacts/3f8a1c2b?teamId=plataforma   -> sube el tarball

Eso es, literalmente, casi todo el protocolo: consultar por hash, descargar, subir, y un endpoint auxiliar para registrar eventos de uso que alimentan las métricas. Su simplicidad es la razón de que existan tantas implementaciones: no hay nada complejo que replicar, solo un almacén indexado por clave detrás de tres o cuatro rutas HTTP. Cualquier cosa capaz de guardar y devolver bytes por una clave puede ser el fondo de esta API.

Entender el protocolo a este nivel tiene un valor práctico más allá de la curiosidad: te permite depurar con las herramientas de red de siempre. Un acierto que no llega, una subida que falla, una latencia sospechosa, todo se diagnostica mirando los códigos de estado de estas peticiones, igual que depurarías cualquier otra API HTTP.

📝
Si sabes depurar una API, sabes depurar la caché

La consecuencia liberadora de que la caché sea HTTP es que no necesitas herramientas nuevas ni conocimiento esotérico para entenderla. Los mismos códigos de estado, las mismas cabeceras de autorización y los mismos registros de acceso que usas para cualquier servicio te dicen qué ocurre: un 404 es un miss, un 200 con cuerpo es un hit, un 401 es un token mal configurado, una latencia alta es un problema de red o de almacenamiento. Reducir la caché a peticiones HTTP es, en sí mismo, media capacidad de operarla.

La caché es un protocolo, no un producto: por eso comprar o construir es una decisión limpia

Hay una elegancia arquitectónica en que la caché remota sea un protocolo abierto y no una caja negra, y esa elegancia tiene consecuencias prácticas profundas. Como el cliente —turbo— habla un protocolo estable y el servidor solo tiene que responderlo, el cliente es invariante: no cambia una línea de tu configuración de tareas cuando pasas de la caché de Vercel a una tuya en S3; lo único que se mueve es a dónde apunta TURBO_API. Eso vuelve la vieja disyuntiva de “comprar o construir” inusualmente nítida, porque comprar y construir producen exactamente la misma capacidad: no estás eligiendo entre una caché buena y una peor, estás eligiendo quién opera la misma caché. Y cuando la capacidad es idéntica en ambos lados, la decisión deja de ser técnica y se vuelve de soberanía y economía: alojas si necesitas que los datos no salgan de tu nube, si la escala hace más barato tu propio almacenamiento, si un entorno aislado te lo impone; no alojas si nada de eso aprieta, porque entonces solo estarías cambiando una guardia que no hacías por una que sí. Pero la simetría se rompe en un punto, y es el punto que de verdad importa: al alojar la caché no heredas su capacidad —esa la tenías igual—, heredas sus modos de fallo. Y el peor modo de fallo de un almacén compartido y mutable no es que se caiga, que es ruidoso y se degrada a compilar en local; es que sirva en silencio un artefacto incorrecto a todo el mundo a la vez, que es la incorrección más cara que existe porque nadie la ve hasta que está en producción. Por eso el protocolo lleva firma dentro: la confianza en el estado compartido no se asume, se construye con criptografía y con mínimo privilegio. La lección que trasciende la caché es que en los sistemas distribuidos las capacidades se compran fácil y los modos de fallo se heredan enteros, y que la madurez de ingeniería consiste precisamente en decidir cuáles de esos fallos quieres que sean tuyos.

⚔️ Decide y, si toca, opera
  1. Levanta un servidor de caché open source apuntando a un bucket o al disco, y redirige TURBO_API hacia él; confirma que turbo obtiene aciertos sin cambiar tu turbo.json.
  2. Simula una caída del servidor y verifica que el build degrada a compilar en local en lugar de fallar.
  3. Escribe la decisión de tu proyecto en un párrafo: ¿alojada o self-hosted? Justifícala con una razón concreta de residencia, coste o aislamiento, no con una intuición de control.
  4. Si eliges self-hosted, define una política de evicción y un plan de rotación de tokens antes de ponerla en producción.
  5. Explica por qué en un almacén compartido la incorrección silenciosa es un fallo más caro que la indisponibilidad, y qué defensa concreta la mitiga.