The Problem

Self-hosting a Nostr relay normally means exposing a port to the public internet, dealing with DNS, TLS certificates, and an attack surface you don't want. selfhost-buzz solves this by running the relay inside a Tailscale network, so it's reachable only by devices on your private tailnet. No port forwarding, no public DNS, no certificate management.

What This Does

This repo deploys Buzz, a Nostr relay with AI agent support, onto a machine you own. The entire ingress story is tailscale serve terminating TLS and proxying to the relay container. Nothing binds a host port—the Tailscale container is the only way in.

The stack is defined in docker-compose.yml: a buzz relay container, a buzz-pair sidecar for device pairing, plus buzz-postgres, buzz-redis, and buzz-minio for persistence and media. There's also docker-compose.ollama.yml for pointing agents at a local LLM instead of a hosted API. The scripts/ directory holds shell scripts for setup, backup, and maintenance.

How It Is Wired

Execution starts with scripts/setup.sh, which generates a .env file with random secrets. You then fill in three values: TS_TAILNET, TS_AUTHKEY, and BUZZ_RELAY_OWNER_PUBKEY. Everything public is derived from TS_TAILNET—the relay URL becomes wss://buzz.<tailnet>.ts.net, media is served at https://buzz.<tailnet>.ts.net/media, and pairing at /pair.

The wiring for this repository has not been fully mapped—there's no internal call graph or import analysis available. What's clear from the file structure:

  • scripts/setup.sh—writes .env with generated secrets
  • scripts/backup.sh—backup routine for the stateful services
  • scripts/check.sh—health check against the running stack
  • scripts/members.sh—member management
  • scripts/reset-data.sh—destructive reset of all data
  • docker-compose.yml—the full stack definition, the only entry point that matters
  • docs/tailscale.md and docs/local-llm.md—setup walkthroughs

The blast radius is concentrated in scripts/setup.sh, which has deep nesting (max indentation depth 7) and is the first thing anyone runs. docker-compose.yml is the deployment artifact—if it's wrong, nothing comes up.

How To Use It

Setup:

git clone https://github.com/ciram-co/selfhost-buzz
cd selfhost-buzz
scripts/setup.sh

Configuration: After setup, edit .env and fill in TS_TAILNET (your tailnet name), TS_AUTHKEY (a reusable auth key from the Tailscale admin console), and BUZZ_RELAY_OWNER_PUBKEY (your Nostr pubkey in hex, not npub).

Running it: docker compose up -d from the repo root. The Tailscale container joins your tailnet and tailscale serve proxies to the relay. Full walkthroughs are in docs/tailscale.md and docs/local-llm.md.

Real-World Use

A home server or spare NUC running this stack gives you a private Nostr relay for you and your family. Install Tailscale on every device, share the relay with specific people via your tailnet, and your messages and agent traffic never touch a public server. The docs/local-llm.md path lets you point agents at Ollama on the same machine, keeping everything local.

Code Health & Issues

Static analysis of this repo found 1 issue, 0 high and 1 medium:

  • Medium - deep nesting in scripts/setup.sh (max indentation depth 7). The control flow is hard to follow; flatten with early returns or extract inner blocks.

The analysis also found no test files and no CI/CD pipeline. For a deployment repo, the lack of a build gate is notable: docker-compose.yml is a deployment artifact with no automated validation. A workflow that at least builds the image and validates the manifests would catch problems before they reach a server. No committed secrets were found, and a license is present.

The Bottom Line

This is a clean, well-documented way to run a private Nostr relay with minimal attack surface. The Tailscale-only ingress is a genuinely good design choice. The main risk is the unvalidated deployment path—no CI, no tests, and setup.sh is complex enough to hide bugs. Suitable for someone comfortable with Docker who wants a private relay without the public-internet hassle.