Seed nodes · Self-host

Run one box.
Make it permanent.

A seed node turns an ephemeral workspace into a durable community — full text history forever, media cached on your policy, and invites answered while every member is offline. Anything Raspberry-Pi-class works; the DHT handles NAT, so no port forwarding. It holds ciphertext only — durable, but blind.

Set up on Linux Containers & systemd
What a seed does

Durability without a company's cloud.

Full history, forever
Text history is always kept in full. The seed is an Autobase indexer, so the community log keeps advancing even when everyone is offline.
Media on your policy
Cache every blob (all), none, or a rolling window of days. Pruning is local-only — it never deletes anyone else's copy.
Answers invites offline
People can join while every human member is away. Add a second seed with another invite — failover is structural, no leader to elect.
Step by step

Set up a seed on a Linux box.

You need Node.js 20 or newer and an internet connection. Everything below runs as an unprivileged user; only the systemd step touches root.

Recorded walkthrough · placeholder
A GIF of the terminal walk-through goes here. Maintainers: record deploy/walkthrough.sh with asciinema rec, render it to website/assets/seed-walkthrough.gif with agg, and swap this block for the <img> in the HTML comment above. The exact same steps are annotated below.
1
Check Node.js and clone the repo
Install production dependencies only — the seed needs nothing else.
1-clone.sh
# need Node.js >= 20
node --version

# clone and install production deps only
git clone <repo> bearhole && cd bearhole
npm ci --omit=dev
2
Create an invite in the app, join once
In the desktop or TUI app, make an invite (/invite), then use it once here. --media-cache takes all, none, or a number of days. The seed is admitted like any member.
2-join.sh
node bin/seed.js --join <inviteCode> \
  --storage /var/lib/bearhole/seed \
  --media-cache 90 --label main-seed
# → up. community key: 8f3c…a91d
# → waiting for a workspace writer to admit this seed…
✓ seed admitted · indexing · durable
3
Make it permanent with systemd
Restarts only need the storage dir — no invite. The unit ships in deploy/systemd/ and restarts on crash and on boot.
3-service.sh
sudo cp deploy/systemd/bearhole-seed.service /etc/systemd/system/
sudo systemctl enable --now bearhole-seed

# status line every ~30s
journalctl -u bearhole-seed -f
# → community=Acme Den peers=4 members=12 messages=845 files=30/30 cached
Containers & the rest of the host stack

Podman, Docker, and the whole operator toolbox.

Ready-to-use deploy/ examples: a Node Dockerfile (npm ci --omit=dev), a docker-compose.yml and a podman-compose.yml (seed + stats + update-seed + website with volumes), a Caddyfile/nginx.conf load-balancing the site, and systemd units. Secrets are always passed at runtime — never baked into an image.

podman
# build the seed image
podman build -t bearhole-seed \
  -f deploy/Dockerfile .

# first run — join once
podman run -d --name seed \
  --restart=always \
  -v bearhole-seed:/data \
  bearhole-seed --join <code>
compose
# seed + stats + update-seed
# + 3 website replicas + Caddy
docker compose \
  -f deploy/docker-compose.yml \
  up -d

# scale the web tier out
# stateless: just add replicas
redundancy
# add a 2nd seed for HA
node bin/seed.js --join <code2> \
  --storage /var/lib/seed-2

# failover is structural —
# both are equal indexers,
# no leader to elect
Secrets never go in images
Invite codes, Discord tokens, OAuth client secrets and the update-feed secret key are mounted or passed at runtime — never COPY-d into a container or committed. The full deploy story, backups (which dirs hold what), and horizontal scaling are in docs/ops-guide.md; every command and flag is in docs/wiki.md.
Telemetry dashboard

Online peers per country.

Coarse, anonymous, opt-in aggregates from bin/stats.js — the same byCountry counts, now with a flag next to each ISO code. No new data: the 2-letter country was already in each report; we only draw its flag.

Flags are Unicode regional-indicator emoji. They render as flags on macOS, Linux, iOS and Android; some platforms (notably Windows) show the 2-letter code instead — which is why the code is always printed alongside the flag.

More host tooling

Everything an operator can run.

Telemetry collector
bin/stats.js — anonymous global aggregates only. Run several for redundancy.
Signed update feed
bin/update-seed.js — a key-signed release Hypercore. A relay can't forge or roll back.
Discord bridge
bin/discord-bridge.js — mirror a Discord channel one-way into Bearhole.
Bots & webhooks
bin/bot.js — a headless member with commands and an HTTP webhook bridge.