danielmorrisey.com/luna
Luna
An alternative to the Bluesky CDN built on Cloudflare Workers and D1 SQLight Database.
Get Started with Luna:
Luna uses the same URL format as the offical Bluesky CDN cdn.bsky.app, meaning it works with any social-app fork.
Not all social-app forks let you set a custon CDN, as of now Witchsky.app is the best option.
Open witchsky.app and go to Settings
Navigate to the Runes section
Click on infrastructure
Select Blueat in the "Image CDN" dropdown
Done! Now all images will load from luna.daneilmorrisey.com
* Blueat is a retired name for the CDN when the BlueAT Network was a active project
Request arrives at the edge
A request comes in for a path like /img/avatar/plain/did:plc:…/cid@jpeg — the same URL format used by cdn.bsky.app. The DID and CID are extracted from the path.
Cloudflare cache check
Before doing any resolution work, the Worker checks Cloudflare's Cache API. If the blob has been fetched before, it's served instantly from the edge with no further processing.
DID resolution
On a cache miss, the Worker resolves the DID to find the user's PDS. For did:plc, it queries the PLC directory. For did:web, it fetches the /.well-known/did.json document directly.
Blob fetch from PDS
The blob is fetched from the PDS using com.atproto.sync.getBlob. If the CID has rotated (e.g. a new profile picture), the current one is looked up first via com.atproto.repo.getRecord.
Cache and serve
The response is streamed back to the client and stored in Cloudflare's Cache API with a 7-day TTL. Future requests for the same blob are served entirely from the edge, bypassing the PDS entirely.
Luna is a Cloudflare Worker that caches Bluesky user avatars and images at the edge. Instead of hitting personal data servers every time a user avatar is needed, Luna resolves DIDs, fetches blobs once, and caches them for 7 days globally.
This dramatically reduces load on PDSes and provides sub-50ms image delivery for most users worldwide.
Why Luna?
- Faster image delivery — Cached images served from the nearest Cloudflare edge location
- Reduces PDS load — Popular avatars are fetched once, cached forever (for 7 days)
- DID agnostic — Works with both did:plc and did:web identifiers
- CID rotation handling — Automatically fetches the current blob if the CID has rotated
- Zero latency — Runs on Cloudflare Workers with no cold starts
How to use Luna
Luna is designed to be a drop-in replacement for Bluesky's native CDN. Any app that uses the standard Bluesky avatar URL format can point to Luna instead.
https://cdn.blueat.net/img/avatar/plain/{did}/{cid}@{format}
Parameters:
{did}— User's Decentralized Identifier (e.g., did:plc:abc123 or did:web:example.com){cid}— Content Identifier for the avatar blob{format}— Image format (jpeg, png, webp, etc.)
https://cdn.blueat.net/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvvrjmk6r22z27h6tvvrjmk6r22z27h6tvvr/bafkreia7ql76q3zpuqw3s7g7r3hpwvfp3f4c4z4xz6z4xz2c4z4xz6z4xz@jpeg
Integration with witchsky
witchsky is a customizable Bluesky client that lets you specify a custom CDN. To use Luna:
- Open witchsky.app and go to Settings
- Navigate to the Runes section
- Find the Custom CDN field and enter
https://cdn.blueat.net - Save — images will now load directly from users' PDSes via Luna
Build with Luna
You can integrate Luna into your Bluesky app or bot with a simple URL replacement. Luna is compatible with any app that expects the standard Bluesky avatar URL format.
const lunaUrl = (did, cid, format = 'jpeg') =>
`https://cdn.blueat.net/img/avatar/plain/${did}/${cid}@${format}`;
// Usage
const avatarUrl = lunaUrl(
'did:plc:z72i7hdynmk6r22z27h6tvvr',
'bafkreia7ql76q3zpuqw3s7g7r3hpwvfp',
'jpeg'
);
// Instead of:
const bskyUrl = `https://cdn.bsky.app/img/avatar/plain/${did}/${cid}@jpeg`;
// Use Luna:
const lunaUrl = `https://cdn.blueat.net/img/avatar/plain/${did}/${cid}@jpeg`;
Cache Headers
Luna returns standard HTTP cache headers so your application can benefit from edge caching:
Cache-Control: public, max-age=604800— 7-day cache TTLETag— For conditional requestsContent-Type— Detected from the blob
Error Handling
404— DID not found or blob doesn't exist500— PDS unreachable or DID resolution failed503— Service temporarily unavailable
When Luna encounters an error, it returns the appropriate HTTP status code. Applications should fall back to the user's default avatar on 404 and retry with exponential backoff on 5xx errors.