This guide will help you self host a multi-region write-through cache proxy for serverless SQLite with Turso.
Turso CDN provides similar guarantees to Turso's (deprecated) Edge Replicas.
- Regional Routing: Fly.io automatically routes requests to the nearest proxy instance using anycast IPs.
- Local Replicas: Each region maintains a local SQLite file that syncs with the primary database.
- Sync Interval: The local replicas sync every 60 seconds (configurable) with the primary database.
Client (Asia) Client (US) Client (EU)
| | |
v v v
[Fly Anycast IP - Global Load Balancing]
| | |
v v v
[sin proxy] [bos proxy] [lhr proxy] # Each has local SQLite replica
| | |
\ | /
\ | /
\ | /
\ v /
`----> [Turso/LibSQL] <´ # Primary database
Sync every 60s
- Clone the repository
git clone https://github.com/notrab/turso-cdn
cd turso-cdn
- Create a Fly app:
fly launch
When prompted:
- Choose a unique app name
- Select "No" for Postgres/Redis
- Select "No" for immediate deployment
- Create volumes in your desired regions:
fly volumes create libsql_data --size 10 --region lhr
fly volumes create libsql_data --size 10 --region sin
fly volumes create libsql_data --size 10 --region bos
- Set your Turso database credentials:
fly secrets set TURSO_DATABASE_URL=libsql://your-database.turso.io
fly secrets set TURSO_AUTH_TOKEN=your-auth-token
fly secrets set PROXY_AUTH_TOKEN=a-random-string
# Optional: Set the sync interval (default is 60 seconds)
# fly secrets set TURSO_SYNC_INTERVAL=30
- Deploy to multiple regions:
fly deploy
fly scale count 3 --region lhr,sin,bos
- Check that each region is running:
fly status
Update your client applications to use the proxy:
Note
Make sure to use https://
for the proxy URL and not libsql://
.
Note
The authToken
here is the PROXY_AUTH_TOKEN
you set in the Fly secrets, this is created by you and not Turso.
import { createClient } from "@libsql/client/web";
const client = createClient({
url: "https://your-cdn.fly.dev",
authToken: process.env.PROXY_AUTH_TOKEN,
});
// The client will automatically connect to the nearest region
const result = await client.execute("SELECT 1");
To expand to new regions:
- Create a volume in the new region:
fly volumes create libsql_data --size 10 --region new-region
- Scale the application:
fly scale count 4 --region lhr,sin,bos,new-region
- Scale down the regions:
# Scale down to just one instance in the primary region
fly scale count 1
# Or keep multiple regions but reduce count
fly scale count 1 --region lhr,sin
# Remove a specific region entirely while keeping others
fly scale count 0 --region sin
- Remove the volumes not longer used:
# List volumes first to see what exists
fly volumes list
# Remove volume in a specific region
fly volumes destroy libsql_data --region sin
SOme of the commands and steps below help you customise the proxy to your individual needs.
View the logs from a specific region:
fly logs --region sin
SSH into an instance to check the local database:
fly ssh console
ls -l /app/data/local.db
Test latency from different regions by passing the fly-prefer-region
header:
curl -s -X POST https://your-cdn.fly.dev/v2/pipeline \
-w "\nTotal time: %{time_total}s\n" \
-H "fly-prefer-region: sin" \
-H "Authorization: Bearer your-proxy-auth-secret" \
-H "Content-Type: application/json" \
-d '{"requests":[{"type":"execute","stmt":{"sql":"SELECT 1","want_rows":true}},{"type":"close"}]}'
You can change the sync interval by setting the TURSO_SYNC_INTERVAL
secret:
fly secrets set TURSO_SYNC_INTERVAL=30 # 30 seconds
If not set, the value defaults to 60
seconds.