Skip to content

Blossom server

Blossom is where the pictures go. A server stores blobs and serves them back by their SHA-256 hash, with Nostr keys as the only identity — the cheapest useful thing to run on this list, and the one shortest of operators.

Content, addressed by its own hash. GET /<sha256> returns the bytes; PUT /upload takes them, authorized by a signed kind-24242 event. Because the address is the hash, the same file uploaded twice is one blob, and a blob mirrored to a second server keeps the same URL path — which is why Armada uploads to several and treats none of them as the home.

What a server holding Armada media can see is less than you would think. In a Concord community, attachments are encrypted client-side before upload and arrive as opaque application/octet-stream — community icons and banners included. The server stores ciphertext and a hash.

blossom-server is the reference implementation: Deno 2, Hono, SQLite or libSQL, with local-filesystem or S3-compatible storage.

Terminal window
git clone https://github.com/hzrd149/blossom-server
cd blossom-server
cp config.example.yml config.yml
docker compose up --build

It listens on 3000. Set publicDomain to the hostname it is reachable at — a bare hostname, no scheme — because that is what goes into the url of every blob descriptor it hands back, and what BUD-11 checks a server tag against. TLS goes on your proxy, as usual.

Key Why you touch it
publicDomain The hostname in returned blob URLs. Wrong here means clients fetch from the wrong origin.
storage.backend local or s3. S3 can redirect GET to a publicURL instead of proxying bytes.
storage.rules Retention and an upload allowlist, matched by MIME glob.
upload.maxSize 2 GB by default, which is more generosity than most operators mean.
upload.requireAuth On by default. Leave it on unless you enjoy hosting other people’s warez.
list.enabled GET /list/:pubkey, off by default.
media.enabled BUD-05 resize and transcode. Needs ffmpeg.

Settings → Media servers takes a list. A user’s own servers are published as a kind-10063 list, so other Nostr clients pick them up too, and a toggle decides whether uploads also go to the app’s defaults.

To ship a default with a client you host, build with VITE_APP_BLOSSOM_SERVERS — see the web app’s configuration. Trailing slashes are fine; several servers are better than one, because Armada mirrors uploads across them and a blob that exists in two places survives one of them disappearing.

A relay is a commitment. A Blossom server is a disk, a proxy and a retention rule — and it serves every Nostr app, not just Armada.

$ hold some bytes