Turborepo Remote Cache on AWS User Guide
Overview
This guide covers the deployment and operation of a self-hosted Turborepo Remote Cache on AWS using cloudimg AWS Marketplace images. Turborepo Remote Cache is a fast server that implements Vercel's Turborepo Remote Cache API, so your build and CI machines share the results of already-completed tasks instead of recomputing them.
The image installs the official turborepo-remote-cache npm package (pinned at build) globally on Node.js 22 LTS and runs it under systemd as the unprivileged turbo user, so a production-ready cache is serving within minutes of launch.
Secure by design - never an open cache. The cache server itself binds to loopback 127.0.0.1:3000 only and is never exposed to the network. An nginx front end on port 80 exposes a public, unauthenticated health endpoint for load balancers and proxies the token-gated cache API (/v8/...) to the server, which enforces bearer-token authentication. There is no default token inside the image: a unique bearer token is generated on this instance's first boot and written to a root-only file, and never baked into the image. Unauthenticated requests to the cache API are rejected (HTTP 400 for a missing header, HTTP 401 for a bad token), so only the per-instance token can read or write cache data. A fail-secure bootstrap gate means the cache server cannot start at all until that per-instance token exists, so the image can never come up as an open cache.
Local storage. Cache artifacts are stored on a local filesystem storage provider at /var/lib/turborepo-remote-cache/cache on the instance disk, with no external cloud dependency.
What is included:
-
Turborepo Remote Cache 2.11.2 (official npm package, pinned at build), run under systemd as the unprivileged
turbouser (turborepo-remote-cache.service) -
An nginx front end (
nginx.service) on port 80 with a public, unauthenticated health endpoint and a token-gated proxy to the cache API -
A per-instance bearer token generated on first boot, enforced on the cache API, and never baked into the image
-
A fail-secure bootstrap gate so the cache never starts without a per-instance token
-
Unattended security upgrades left enabled so the server keeps receiving patches

Prerequisites
-
Active AWS account, an EC2 key pair, a VPC and subnet in the target region
-
Subscription to the Turborepo Remote Cache listing on AWS Marketplace
-
A security group allowing TCP 22 (admin) and, from the build machines and CI runners that will use the cache, TCP 80 (cache API and health)
Recommended instance type: m5.large (2 vCPU, 8 GB RAM). Light cache use runs comfortably; busy caches shared across large teams or CI fleets can move to a larger instance type and a larger root volume.
Step 1: Launch from AWS Marketplace
Find Turborepo Remote Cache on AWS Marketplace, published by cloudimg, and choose Continue to Subscribe, then Continue to Configuration and Continue to Launch. Select the Ubuntu 24.04 delivery option, your instance type (m5.large), your key pair, and a security group that allows TCP 22 for administration plus TCP 80 from the client networks that will use the cache.
Step 2: Connecting to your instance
SSH in with your EC2 key pair as the login user for the OS variant you launched.
| OS variant | SSH login user | Example |
|---|---|---|
| Ubuntu 24.04 | ubuntu |
ssh -i your-key.pem ubuntu@<public-ip> |
ssh -i your-key.pem ubuntu@<public-ip>
Step 3: First boot
On first boot the image resolves the instance public IP via EC2 instance metadata (IMDSv2), mints a per-instance bearer token, writes the fail-secure bootstrap marker, and then systemd starts the cache server. This completes within a minute.
Confirm both services are active, and that the cache server is bound to loopback only while nginx serves the network:
sudo systemctl start turborepo-remote-cache.service nginx.service 2>/dev/null || true
sleep 2
sudo systemctl is-active turborepo-remote-cache.service nginx.service
sudo ss -tln | grep -E ':(80|3000) '
The cache and nginx services report active, the cache server listens on 127.0.0.1:3000 (loopback only), and nginx listens on port 80.
The unauthenticated health endpoint is handled entirely by nginx (no cache round-trip), which makes it ideal for a load-balancer health check:
curl -s http://127.0.0.1/
curl -s -o /dev/null -w 'health: HTTP %{http_code}\n' http://127.0.0.1/

Step 4: Retrieve your cache token
The per-instance bearer token lives in a root-only file. Read it over SSH:
sudo cat /root/turborepo-remote-cache-info.txt
The file holds the TURBO_TOKEN for this instance, the cache API URL (TURBO_API), and the team id (TURBO_TEAM, cloudimg). These values are unique to this instance and were generated on first boot - they are never baked into the image.
Step 5: Authentication is enforced
The cache is never an open target. The command below reads the token straight from the root-only file, so no secret is printed. An authenticated request to the cache API returns HTTP 200, an anonymous request is rejected with HTTP 400 (missing header), and a wrong-token request is rejected with HTTP 401:
sudo systemctl start turborepo-remote-cache.service nginx.service 2>/dev/null || true
sleep 2
T=$(sudo grep '^TURBO_TOKEN=' /root/turborepo-remote-cache-info.txt | cut -d= -f2-)
PH=$(head -c 64 /dev/urandom | sha256sum | awk '{print $1}')
curl -s -o /dev/null -w 'authenticated status: HTTP %{http_code}\n' -H "Authorization: Bearer $T" http://127.0.0.1/v8/artifacts/status
curl -s -o /dev/null -w 'anonymous artifact GET: HTTP %{http_code}\n' "http://127.0.0.1/v8/artifacts/$PH?teamId=cloudimg"
curl -s -o /dev/null -w 'wrong-token artifact GET: HTTP %{http_code}\n' -H "Authorization: Bearer wrongtoken" "http://127.0.0.1/v8/artifacts/$PH?teamId=cloudimg"
The authenticated request prints 200, the anonymous request prints 400, and the wrong-token request prints 401.

Step 6: Store and retrieve a cache artifact
The Turborepo Remote Cache API is a content-addressed artifact store: a task output is stored with a PUT under its hash and retrieved with a GET. The block below performs a full authenticated round-trip - store an artifact, retrieve it byte-for-byte, and confirm an anonymous request for the same artifact is rejected. The token is read from the root-only file, so no secret is printed:
sudo systemctl start turborepo-remote-cache.service nginx.service 2>/dev/null || true
sleep 2
T=$(sudo grep '^TURBO_TOKEN=' /root/turborepo-remote-cache-info.txt | cut -d= -f2-)
BLOB=$(head -c 512 /dev/urandom | base64 -w0)
H=$(printf '%s' "$BLOB" | sha256sum | awk '{print $1}')
PUT=$(printf '%s' "$BLOB" | curl -s -o /dev/null -w '%{http_code}' -H "Authorization: Bearer $T" -H 'Content-Type: application/octet-stream' -X PUT --data-binary @- "http://127.0.0.1/v8/artifacts/$H?teamId=cloudimg")
echo "authenticated PUT artifact ${H:0:12} HTTP $PUT"
GOT=$(curl -s -H "Authorization: Bearer $T" "http://127.0.0.1/v8/artifacts/$H?teamId=cloudimg")
[ "$GOT" = "$BLOB" ] && echo "authenticated GET artifact ${H:0:12} byte-for-byte match OK" || echo "MISMATCH"
UNG=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1/v8/artifacts/$H?teamId=cloudimg")
echo "anonymous GET artifact ${H:0:12} HTTP $UNG (rejected)"
The PUT returns 200, the authenticated GET returns the artifact byte-for-byte, and the anonymous GET is rejected - a full content-addressed round-trip through the token-gated cache API.

Step 7: Point Turborepo at your cache
From a client or CI runner, point Turborepo at your cache by setting the API URL, team and token. Replace <public-ip> with your instance's public IP and <token> with the TURBO_TOKEN value from Step 4:
export TURBO_API=http://<public-ip>
export TURBO_TOKEN=<token>
export TURBO_TEAM=cloudimg
turbo build --remote-cache-timeout 60
On the first build Turborepo uploads task outputs to the cache; on subsequent builds - locally or in CI, on any machine that shares this configuration - Turborepo downloads the cached result instead of recomputing it. You can verify connectivity from a client with a single authenticated request:
curl -H "Authorization: Bearer <token>" http://<public-ip>/v8/artifacts/status
Step 8: Storage and configuration notes
-
Cache storage. Cache artifacts are stored on the local filesystem at
/var/lib/turborepo-remote-cache/cache. Confirm free space withdf -h /. -
Non-secret configuration lives at
/etc/turborepo-remote-cache/turbo-cache.env(the systemdEnvironmentFile):AUTH_MODE=static,STORAGE_PROVIDER=local, theSTORAGE_PATH, the loopbackHOST/PORT, and the body-size limit. The per-instance secretTURBO_TOKENlives separately in/etc/turborepo-remote-cache/token.env(0600 root:root), read by systemd at start. -
nginx front end is at
/etc/nginx/sites-available/turborepo-remote-cache. The public/and/healthendpoints are unauthenticated liveness probes;/v8/...is proxied to the cache server, which enforces the bearer token. -
Per-instance secrets. The token copy in
/root/turborepo-remote-cache-info.txtis0600 root:root. The token is generated on first boot and never baked into the image; a fail-secure gate stops the cache starting until it exists. -
Production TLS. For production, front the cache with your own domain and a CA-signed TLS certificate (for example an nginx
:443server block, or an AWS load balancer terminating TLS) so the bearer token is never sent in clear text over the network.
Step 9: Managing the services
sudo systemctl --no-pager --lines=3 status turborepo-remote-cache.service nginx.service | cat
Restart the cache with sudo systemctl restart turborepo-remote-cache, and reload nginx after a configuration change with sudo systemctl reload nginx. Both services are enabled at boot.
Support
This image is published and supported by cloudimg. For deployment help, configuration questions, or issues, contact support@cloudimg.co.uk. Every cloudimg image ships with 24/7 support and fully patched operating systems with unattended security upgrades enabled.
This is a repackaged open source software product with additional charges for cloudimg support services. Turborepo Remote Cache is distributed under the MIT license. cloudimg is not affiliated with or endorsed by Vercel, Inc. 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.