Turborepo Remote Cache on Ubuntu 24.04 on Azure User Guide
Overview
Turborepo Remote Cache is a fast, self hosted server that implements Vercel's Turborepo Remote Cache HTTP API. It stores the outputs of already completed build tasks on a local filesystem storage provider, so your developers and CI runners can download a previous result instead of recomputing it, which dramatically speeds up local and CI builds across a monorepo.
The cloudimg image installs the official turborepo-remote-cache npm package on Node.js 22 LTS, running 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, no default login: the cache API is protected by bearer token authentication (AUTH_MODE=static). A turborepo-remote-cache-firstboot.service oneshot generates a unique token on each VM's first boot, writes it to a root only file, resolves the instance URL and writes a root only info note, 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, while the public health endpoint and the connectivity status probe expose no cache data.
Fail secure: the cache server carries a bootstrap gate (ConditionPathExists) that only clears after first boot has written the per VM token, so the cache cannot start at all until a token exists. If first boot were interrupted the cache simply would not serve, rather than coming up open.
Note on the interface: Turborepo Remote Cache is an API service with no web UI. You drive 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 is included:
- Turborepo Remote Cache (verified at 2.11.2) as the official npm package on Node.js 22 LTS
turborepo-remote-cache.servicebound to127.0.0.1:3000, fronted bynginxon: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.servicefor the first boot token and info note- A unique per VM bearer token generated on first boot, in a root only
0600file - A fail secure bootstrap gate so the cache never starts without a token
- Local filesystem storage on the VM's own disk, no external cloud dependency
- Ubuntu 24.04 LTS base, fully patched
- 24/7 cloudimg support, 24h response SLA
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet with a subnet. The cache is CPU light; performance is dominated by disk and network throughput and cache capacity. Recommended VM size: Standard_B2s (2 vCPU, 4 GB RAM) for evaluation and small teams, or a 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 caching large build graphs.
Step 1: Deploy from the Azure Portal
Search the Marketplace for Turborepo Remote Cache on Ubuntu 24.04, choose your VM size, and 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 will use the cache. Because bearer token auth is enforced on the artifact API, the cache is safe to expose to your build fleet, but you should still restrict the source ranges to networks you control.
Step 2: Deploy from the Azure CLI
RG="turborepo-cache-prod"; LOCATION="eastus"; VM_NAME="turbo-cache-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/turborepo-remote-cache-ubuntu-24-04/versions/<version>"
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
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
--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
Step 3: Connect via SSH
ssh azureuser@<vm-ip>
Step 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, and the connectivity status probe reports the cache enabled, while 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
You should see both services active, nginx listening on :80 and the app on 127.0.0.1:3000, HTTP 200 from the public health endpoint, and the status probe returning {"status":"enabled",...}.

Step 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, keep it secret, and confirm that a request to the artifact API without a token is rejected while 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=cloudimg")"
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=cloudimg")"
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=cloudimg")"
The info note holds TURBO_TOKEN, the cache URL and the team, is owned root:root with mode 0600. A request with no Authorization header returns 400, a wrong token returns 401, and the per VM token returns 404 for this random unknown hash, which is the authenticated "not cached yet" response, so the cache never serves artifacts without the token.

Step 6: Store and fetch an artifact (cache round trip)
Turborepo uploads each task's output to the cache keyed by a hash, then downloads it back on the next build. Store a blob with an authenticated PUT, then fetch it straight back and confirm it matches byte for byte. This is exactly what Turborepo does under the hood 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=cloudimg" -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=cloudimg")
[ "$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=cloudimg")"
The PUT returns 200, the authenticated GET returns the identical blob, and an unauthenticated GET of the same key returns 400, so cached content is never served without the token.

Step 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="cloudimg"
# 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 populates the cache, and a clean second build pulls the results back from the cache instead of recomputing them. Share the same cache across your CI fleet so every machine benefits.
Step 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, storage is the local provider at STORAGE_PATH). To grow the cache, attach a dedicated data disk, mount it at /var/lib/turborepo-remote-cache/cache preserving turbo ownership, then sudo systemctl restart turborepo-remote-cache. Prune the cache directory periodically, or rotate it, since the local provider does not evict automatically.
Step 9: Optional TLS
Bearer token auth protects the token over the wire only if the transport is encrypted, so restrict the NSG source ranges to networks you control and, 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, writes the fail secure bootstrap marker, then disables itself so subsequent reboots are unaffected. The OS ships fully patched 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 cloudimg deployment includes 24/7 support with a 24 hour response SLA. Contact support@cloudimg.co.uk for help with deployment, configuration or scaling.
This is a repackaged open source software product with additional charges for cloudimg 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.