+ + +
+

danielmorrisey.com/luna

+

Luna

+

+ A fast, caching CDN for Bluesky avatars and images. Resolve DIDs, fetch blobs from personal data servers, cache at the edge, and serve instantly. +
+ Try it now:
cdn.blueat.net +

+
+ + +
+ +
+
+
+

Global Edge Caching

+

Images cached for 7 days at 310+ Cloudflare edge locations worldwide.

+
+
+
+

DID Resolution

+

Resolves did:plc and did:web identifiers to find users' personal data servers.

+
+
+
+

Blob Fetching

+

Fetches blobs directly from PDSes using com.atproto.sync.getBlob.

+
+
+
+

Zero Cold Starts

+

Cloudflare Workers handle requests instantly at the edge with no latency.

+
+
+
+ + +
+
+ + + + +
+ + +
+
+
+
1
+
+

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.

+
+
+
+
2
+
+

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.

+
+
+
+
3
+
+

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.

+
+
+
+
4
+
+

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.

+
+
+
+
5
+
+

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.

+
+ +
+
Avatar URL Format
+
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.)
  • +
+
+ +
+
Example Request
+
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:

+
    +
  1. Open witchsky.app and go to Settings
  2. +
  3. Navigate to the Runes section
  4. +
  5. Find the Custom CDN field and enter https://cdn.blueat.net
  6. +
  7. Save — images will now load directly from users' PDSes via Luna
  8. +
+
+
+ + +
+
+

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.

+
+ +
+
JavaScript / TypeScript
+
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'
+);
+
+ +
+
Replace Bluesky CDN
+
// 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 TTL
  • +
  • ETag — For conditional requests
  • +
  • Content-Type — Detected from the blob
  • +
+
+ +
+

Error Handling

+
    +
  • 404 — DID not found or blob doesn't exist
  • +
  • 500 — PDS unreachable or DID resolution failed
  • +
  • 503 — 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.

+
+
+ +
+