Deployment guide

Turborepo Remote Cache

Overview

Turborepo Remote Cache is a fast, self-hosted server that implements Vercel's Turborepo Remote Cache HTTP API. It stores the outputs of completed build tasks on a local filesystem storage provider. Your developers and CI runners download a previous result instead of recomputing it, which speeds up local and CI builds across a monorepo.

The SecureAnyCloud image installs the official turborepo-remote-cache npm package on Node.js 22 LTS. It runs as the turborepo-remote-cache service, bound to 127.0.0.1:3000. An nginx front on port 80 publishes a public health endpoint for load balancers and monitoring, and proxies the token-gated cache API at /v8/artifacts/. The cache lives at /var/lib/turborepo-remote-cache/cache on the OS disk.

Secure by default, with no default login. Bearer token authentication (AUTH_MODE=static) protects the cache API. A turborepo-remote-cache-firstboot.service oneshot generates a unique token on each VM's first boot and writes it to a root-only file. It also resolves the instance URL, writes a root-only info note, and then disables itself. No two VMs share a token, and none is baked into the image. Requests to the artifact API without a valid token are rejected. The public health endpoint and the connectivity status probe expose no cache data.

Fail secure. The cache server carries a bootstrap gate (ConditionPathExists) that clears only after first boot has written the per-VM token. The cache cannot start until a token exists. If first boot is interrupted, the cache does not serve and never comes up open.

No web interface. Turborepo Remote Cache is an API service with no web UI. You use it through Turborepo by setting the cache API URL, team and token, or through its HTTP API with curl. This guide uses curl to demonstrate the cache and shows the Turborepo client configuration.

What you get:

  • Turborepo Remote Cache (verified at 2.11.2) as the official npm package on Node.js 22 LTS
  • turborepo-remote-cache.service bound to 127.0.0.1:3000, fronted by nginx on :80
  • Bearer token authentication enforced on the artifact API (AUTH_MODE=static)
  • A public health endpoint and connectivity status probe that expose no cache data
  • turborepo-remote-cache-firstboot.service for the first-boot token and info note
  • A unique per-VM bearer token generated on first boot, in a root-only 0600 file
  • A fail-secure bootstrap gate so the cache never starts without a token
  • Local filesystem storage on the VM's own disk, with no external cloud dependency
  • Ubuntu 24.04 LTS base, patched at build time
  • 24/7 SecureAnyCloud support

Prerequisites

  • An active Azure subscription
  • An SSH key pair
  • A VNet with a subnet

The cache uses little CPU. Performance depends mainly on disk and network throughput and on cache capacity. Recommended VM size: Standard_B2s (2 vCPU, 4 GB RAM) for evaluation and small teams, or Standard_D4s_v5 or larger with a bigger data disk for busy CI fleets. The cache lives on the OS disk by default, so expand the disk before you cache large build graphs.

1

Deploy from the Azure Portal

Search the Marketplace for Turborepo Remote Cache on Ubuntu 24.04 and choose your VM size. Attach an NSG that allows TCP 22 (SSH) from your management network, and TCP 80 (the cache API and health endpoint) from the developer and CI machines that use the cache.

Bearer token authentication protects the artifact API, so you can expose the cache to your build fleet. Still restrict the source ranges to networks you control.

2

Deploy from the Azure CLI

RG="turborepo-cache-prod"; LOCATION="eastus"; VM_NAME="turbo-cache-01" IMAGE_URN="sac1647283583153:turborepo-remote-cache-ubuntu-24-04:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION" az network vnet create -g "$RG" --name turbo-vnet --address-prefix 10.90.0.0/16 --subnet-name turbo-subnet --subnet-prefix 10.90.1.0/24 az network nsg create -g "$RG" --name turbo-nsg az network nsg rule create -g "$RG" --nsg-name turbo-nsg --name allow-ssh --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp az network nsg rule create -g "$RG" --nsg-name turbo-nsg --name allow-cache --priority 110 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 80 --access Allow --protocol Tcp
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:turborepo-remote-cache-ubuntu-24-04:default:latest
az vm create -g "$RG" --name "$VM_NAME" --image "$IMAGE_URN" \
  --size Standard_B2s --storage-sku StandardSSD_LRS \
  --admin-username azureuser --ssh-key-values "$SSH_KEY" \
  --vnet-name turbo-vnet --subnet turbo-subnet --nsg turbo-nsg --public-ip-sku Standard
3

Connect via SSH

ssh azureuser@<vm-ip>
4

Verify the service, the front and the listeners

The Node app serves the cache on 127.0.0.1:3000, and nginx fronts it on :80. The public health endpoint returns 200 with no authentication. The connectivity status probe reports the cache as enabled. Neither exposes any cache data.

sudo systemctl is-active turborepo-remote-cache nginx ss -tln | grep -E ':80|:3000' echo "public health / -> HTTP $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/)" curl -s http://127.0.0.1/v8/artifacts/status

Both services are active, nginx listens on :80 and the app on 127.0.0.1:3000, the public health endpoint returns HTTP 200, and the status probe returns {"status":"enabled",...}.

5

Read your per VM cache token

The bearer token was generated on this VM's first boot and stored in a root-only file. Read it and keep it secret. Then confirm that a request to the artifact API without a token is rejected, and that the per-VM token authenticates.

sudo cat /root/turborepo-remote-cache-info.txt
T=$(sudo grep '^TURBO_TOKEN=' /root/turborepo-remote-cache-info.txt | cut -d= -f2-)
H=$(head -c 64 /dev/urandom | sha256sum | awk '{print $1}')
echo "no token -> HTTP $(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1/v8/artifacts/$H?teamId=sac")"
echo "wrong token -> HTTP $(curl -s -o /dev/null -w '%{http_code}' -H 'Authorization: Bearer wrongtoken' "http://127.0.0.1/v8/artifacts/$H?teamId=sac")"
echo "per VM token-> HTTP $(curl -s -o /dev/null -w '%{http_code}' -H "Authorization: Bearer $T" "http://127.0.0.1/v8/artifacts/$H?teamId=sac")"

The info note holds TURBO_TOKEN, the cache URL and the team. It is owned by root:root with mode 0600.

A request with no Authorization header returns 400, and a wrong token returns 401. The per-VM token returns 404 for this random unknown hash. That is the authenticated "not cached yet" response. The cache never serves artifacts without the token.

6

Store and fetch an artifact (cache round trip)

Turborepo uploads each task's output to the cache, keyed by a hash, and downloads it on the next build. Store a blob with an authenticated PUT, then fetch it back and confirm that it matches byte for byte. Turborepo does exactly this for every cached task.

T=$(sudo grep '^TURBO_TOKEN=' /root/turborepo-remote-cache-info.txt | cut -d= -f2-) BLOB="hello from turborepo remote cache at $(date -u +%FT%TZ)"
HASH=$(printf '%s' "$BLOB" | sha256sum | awk '{print $1}') printf '%s' "$BLOB" | curl -s -H "Authorization: Bearer $T" -H 'Content-Type: application/octet-stream' \
  -X PUT --data-binary @- "http://127.0.0.1/v8/artifacts/$HASH?teamId=sac" -o /dev/null -w 'PUT /v8/artifacts -> HTTP %{http_code}\n'
GOT=$(curl -s -H "Authorization: Bearer $T" "http://127.0.0.1/v8/artifacts/$HASH?teamId=sac") [ "$GOT" = "$BLOB" ] && echo "GET /v8/artifacts -> byte for byte match" || echo "GET /v8/artifacts -> MISMATCH" echo "unauthenticated GET -> HTTP $(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1/v8/artifacts/$HASH?teamId=sac")"

The PUT returns 200, and the authenticated GET returns the identical blob. An unauthenticated GET of the same key returns 400, so cached content is never served without the token.

7

Point Turborepo at the cache

In your monorepo, point Turborepo at this server with three environment variables, using the token from Step 5. Turborepo reads TURBO_API, TURBO_TOKEN and TURBO_TEAM, and sends every task result to the cache:

# In your monorepo, before running turbo (token from /root/turborepo-remote-cache-info.txt)
export TURBO_API="http://<vm-ip>"
export TURBO_TOKEN="<turbo-token>"
export TURBO_TEAM="sac"
# Now run any build; results are pushed to and pulled from the remote cache turbo build

Alternatively, add the cache to your project's turbo.json remote cache settings, or pass --api, --token and --team on the turbo command line.

Run any build twice. The first build populates the cache, and a clean second build pulls the results from the cache instead of recomputing them. Share the same cache across your CI fleet so that every machine benefits.

8

Sizing and tuning the cache

The cache is stored on the local filesystem under /var/lib/turborepo-remote-cache/cache on the OS disk. Inspect the configuration and disk usage:

cat /etc/turborepo-remote-cache/turbo-cache.env du -sh /var/lib/turborepo-remote-cache/cache 2>/dev/null df -h /var/lib/turborepo-remote-cache

Tunables live in /etc/turborepo-remote-cache/turbo-cache.env. The maximum artifact size is BODY_LIMIT, and storage is the local provider at STORAGE_PATH.

To grow the cache, attach a dedicated data disk and mount it at /var/lib/turborepo-remote-cache/cache, preserving turbo ownership. Then run sudo systemctl restart turborepo-remote-cache. The local provider does not evict automatically, so prune or rotate the cache directory periodically.

9

Optional TLS

Bearer token authentication protects the token over the wire only if the transport is encrypted. Restrict the NSG source ranges to networks you control. For traffic across untrusted networks, terminate TLS at the nginx front. Add a certificate and a listen 443 ssl; server block to nginx, or place the VM behind an Azure Application Gateway or Load Balancer that terminates HTTPS:

# Terminate TLS at the nginx front (see /etc/nginx/sites-available/turborepo-remote-cache)
# listen 443 ssl;
# ssl_certificate /etc/nginx/tls/cert.pem;
# ssl_certificate_key /etc/nginx/tls/key.pem;
# then set TURBO_API=https://<vm-ip> on your clients echo "Terminate HTTPS at nginx or an Azure Application Gateway, then use https:// on clients."

With TLS enabled, set TURBO_API=https://<vm-ip> on your clients so the token is always sent over an encrypted channel.

Persistence, first boot and updates

The cache lives on the OS disk under /var/lib/turborepo-remote-cache/cache. The first-boot service generates the per-VM bearer token, resolves the instance URL and writes the fail-secure bootstrap marker. It then disables itself, so later reboots are unaffected. The OS is patched at build time and ships with unattended security upgrades enabled.

node --version systemctl is-enabled turborepo-remote-cache-firstboot.service turborepo-remote-cache.service nginx.service ls -ld /var/lib/turborepo-remote-cache/cache export DEBIAN_FRONTEND=noninteractive;
sudo apt-get update -y >/dev/null 2>&1 echo "held packages: $(apt-mark showhold | tr '\n' ' ')[none]"

Support

Every SecureAnyCloud deployment includes 24/7 support with a 24 hour response SLA. Contact sales@secureanycloud.com for help with deployment, configuration or scaling.

This is a repackaged open source software product with additional charges for SecureAnyCloud support services. Turborepo Remote Cache is distributed under the MIT license. Turborepo and Vercel are trademarks of their respective owners. All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.

Deploy on Azure

Launch Turborepo Remote Cache on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure