A self-hosted music streaming server running entirely on Cloudflare's edge infrastructure (Workers + R2 + D1), implementing the Subsonic/OpenSubsonic API for compatibility with dozens of existing mobile, desktop, and TV clients.
┌──────────────────────────────────────────────────────────────┐
│ Cloudflare Edge │
│ │
│ ┌─────────────────┐ ┌──────────┐ ┌─────────────────┐ │
│ │ Worker │───>│ D1 │ │ R2 Bucket │ │
│ │ (Subsonic API) │ │ (SQLite) │ │ (audio files) │ │
│ │ │────────────────────>│ │ │
│ └────────^─────────┘ └──────────┘ └─────────────────┘ │
│ │ ^ │
└───────────┼───────────────────────────────────────┼──────────┘
│ │
┌───────┴─────────┐ ┌─────────┴─────────┐
│ Subsonic │ │ CLI Scanner │
│ Clients │ │ (local, bun) │
│ DSub, play:Sub │ │ reads ID3 tags │
│ Substreamer │ │ uploads to R2 │
│ Symphonium │ │ writes to D1 │
└─────────────────┘ └───────────────────┘
- Own your music library — no device limits, region locks, or quality caps
- Zero egress fees (R2's killer feature for audio streaming)
- No server to manage — fully serverless, globally distributed
- Instant access to 20+ existing Subsonic client apps
- Costs ~$0.23/month for a 25GB library (or $0 if under 10GB)
| Client | Platform | Status |
|---|---|---|
| Sublime Music | Linux/Desktop | Tested |
| Ultrasonic | Android | Tested |
| Feishin | Desktop (Electron) | Tested |
| DSub | Android | Should work |
| play:Sub | iOS | Should work |
| Substreamer | Android TV / Fire TV | Should work |
| Symphonium | Android | Should work |
- Cloudflare account with Workers, R2, and D1 enabled
- Bun runtime (for the CLI scanner)
- R2 S3 API credentials (create here)
- Cloudflare API token with D1 read/write permissions
git clone https://github.com/ptqa/cf-music.git
cd cf-music
# Worker
cd worker
bun install
# CLI
cd ../cli
bun installcd worker
# Create D1 database
npx wrangler d1 create music-metadata
# Note the database_id from the output
# Create R2 bucket
npx wrangler r2 bucket create music# Copy the example and fill in your D1 database ID
cp wrangler.toml.example wrangler.tomlEdit worker/wrangler.toml and replace YOUR_D1_DATABASE_ID with the ID from step 2.
cd worker
# Apply schema to remote D1
npx wrangler d1 execute music-metadata --remote --file=src/db/schema.sql
# Deploy the Worker
npx wrangler deployYour server is now live at https://music-server.<your-account>.workers.dev.
# From the repo root
cp music-cli.toml.example music-cli.tomlEdit music-cli.toml with your Cloudflare credentials:
[cloudflare]
account_id = "your-account-id"
api_token = "your-api-token"
[r2]
bucket_name = "music"
endpoint = "https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com"
access_key_id = "your-r2-access-key"
secret_access_key = "your-r2-secret-key"
[d1]
database_id = "your-d1-database-id"cd cli
# Create a user (password stored as plaintext — required by Subsonic auth protocol)
bun run cli user --username tony --password 'your-secure-password' --admin
# Scan your music library
bun run cli scan /path/to/your/music/
# Check library stats
bun run cli statsPoint any Subsonic-compatible client at your Worker URL:
- Server:
https://music-server.<your-account>.workers.dev - Username / Password: the credentials you created in step 6
# Scan a directory — reads ID3 tags, uploads audio + cover art to R2, populates D1
bun run cli scan /path/to/music/
# Add a single file
bun run cli add /path/to/song.mp3
# Show library statistics
bun run cli stats
# Create a user
bun run cli user --username <name> --password <pass> [--admin]
# Delete ALL metadata from D1 (does not delete R2 files)
bun run cli nuke --confirm- No transcoding — Workers don't have FFmpeg. Files are served as-is from R2. Store your music as mp3 (320kbps/V0) for best compatibility.
- Streaming via R2 — Audio is streamed directly from R2 as a
ReadableStreamwith fullRangerequest support (HTTP 206). Zero buffering in Worker memory. - Subsonic JSON quirk — Single-element arrays collapse to bare objects, empty arrays are omitted. A custom serializer handles this.
- Plaintext passwords — The Subsonic auth protocol requires
md5(password + salt)verification, which means the server must know the plaintext password. This is a known protocol limitation, acceptable for personal use over HTTPS. - Deterministic IDs — Song, album, and artist IDs are SHA-256 hashes of their metadata, making re-scans idempotent.
Assuming ~5,000 songs (~25GB), single user, ~2 hours listening/day:
| Service | Usage | Monthly Cost |
|---|---|---|
| Workers | ~10K req/day | Free tier |
| R2 Storage | ~25GB | ~$0.23 |
| R2 Operations | ~3K reads/day | Free tier |
| R2 Egress | ~2GB/day | $0 (always free) |
| D1 Storage | ~50MB | Free tier |
| D1 Reads | ~50K rows/day | Free tier |
Estimated total: ~$0.23/month
MIT