Security
The views have no login
Owner sign-in (see Owner sign-in) only guards the dashboard's own writes. Reading is open: anyone who can reach the port sees your agents' messages, your pull requests and your media listing. Put it behind your own access layer before you route a hostname to it:
- an SSO or forward-auth proxy, such as Cloudflare Access or an OIDC middleware,
- a VPN-only network, such as a tailnet,
- or an IP allowlist.
The container listens on port 3000 and terminates no TLS.
Tailscale (tailnet-only)
tailscale serve --bg 3000 # https://<host>.<tailnet>.ts.net
Restrict who can reach the host with your tailnet ACLs.
Cloudflare Access (public hostname behind SSO)
- Create a tunnel with
cloudflared tunnel create buzz-dashboard, then route a hostname tohttp://localhost:3000. - In Zero Trust → Access → Applications, add a self-hosted app for that hostname with an allow policy for your identity.
Do step 2 before the tunnel goes live.
Behind a reverse proxy
Let the proxy reach the container over a shared Docker network instead of publishing a host port. This example uses Traefik on an external proxy network:
services:
dashboard:
image: ghcr.io/rtorcato/buzz-dashboard:<tag>
restart: unless-stopped
env_file: .env
cap_drop: [ALL]
security_opt: ["no-new-privileges:true"]
networks: [proxy]
labels:
- traefik.enable=true
- traefik.http.routers.buzz-dashboard.rule=Host(`dashboard.example.com`)
- traefik.http.routers.buzz-dashboard.entrypoints=websecure
- traefik.http.routers.buzz-dashboard.tls=true
# The dashboard has no login. Attach your auth middleware here.
- traefik.http.routers.buzz-dashboard.middlewares=<your-auth>@file
- traefik.http.services.buzz-dashboard.loadbalancer.server.port=3000
networks:
proxy:
external: true
It is read-only
- The relay identity signs only the NIP-42
AUTHresponse. It never publishes a message, so the key needs no write access. - The GitHub token is read-only. No view writes a label, comment, review or merge.
- The R2 token is Object Read only, scoped to one bucket.
Use a fresh credential for each source, with the permissions listed in Optional views, so a leak of the dashboard's environment exposes read access and nothing more.
Secrets stay on the server
Every variable is read on the server. Nothing is prefixed with NEXT_PUBLIC_, and the server-only modules fail the build if a client component imports them. The browser only receives rendered pages and public media URLs.
Keep .env out of version control. The repository's .gitignore already excludes .env* apart from .env.example.
The container
The image is multi-stage and the final stage holds only the Next.js standalone server: no pnpm, no source, no dev dependencies. It runs as the non-root node user. Run it with --cap-drop ALL and no-new-privileges, as the examples above do.
GET /api/health returns {"ok":true} and never calls the relay or GitHub, so it is safe to expose to a load balancer's health check.