A simple UI for deploying Nostr services (relays, blossom servers, etc.) using Dokploy under the hood.
User deploys RelayKit once, then uses it to spin up Nostr services without touching Dokploy directly.
Browser → RelayKit App → Dokploy API
Stack:
- Frontend: Vite + React + tRPC client
- Backend: Node.js + TypeScript + tRPC server (serves frontend in prod)
- Communication: tRPC
- Auth: Nostr (NIP-07 browser extension, owner-only access)
- Presets: Docker-compose templates in
/presets/directory - State: PostgreSQL if needed (or just query Dokploy API)
docker-compose.yml runs:
- Dokploy + its Postgres/Redis
- dokploy-traefik (in prod: listens on 80 and 443, routes to services; in dev: no ports, Caddy in front)
- Caddy (dev only: listens on 80 and 443, terminates HTTPS with mkcert, forwards HTTP to Traefik)
- RelayKit app (one container: backend serves frontend)
- Backend reads preset docker-compose templates from
/presets/{service}/ - User clicks "add service", selects service type, provides config (domain, etc.)
- Backend calls Dokploy REST API to create and deploy the service
- Dokploy handles actual container orchestration
- User sees their deployed services and can manage them
| Service | Repo | Notes |
|---|---|---|
| nostr-rs-relay | scsibug/nostr-rs-relay | |
| nogringo/nostr-relay | nogringo/nostr-relay | NIP-17/59 relay with NIP-42 auth-gated gift-wrap reads (kind:1059). |
| Strfry | hoytech/strfry | |
| Blossom | hzrd149/blossom | |
| nPanel | hzrd149/nsite-gateway | Static sites on Nostr (NIP-5A) plus same-domain NIP-05 responses. Deploys nsite-gateway, a NIP-05 JSON sidecar, and a Caddy sidecar that serves /.well-known/nostr.json while rewriting Host for gateway traffic. |
relaykit/
├── docker-compose.yml
├── Dockerfile.dev
├── start-dev.sh
├── README.md
└── app/
├── frontend/ (React + tRPC client)
├── backend/ (Node.js + tRPC server)
├── shared/ (shared TypeScript utils, e.g. nsite.ts)
└── presets/ (service docker-compose templates)
├── strfry-relay/
├── nostr-rs-relay/
├── nogringo-nostr-relay/
├── blossom/
└── npanel/
├── docker-compose.yml
└── metadata.json
Prerequisites (dev): Docker. For local HTTPS: brew install mkcert && mkcert -install, then ./scripts/gen-dev-certs.sh (creates certs + Caddyfile). Without mkcert/certs, Caddy will fail on 80/443.
Dev: Everything runs in Docker
docker compose --profile dev up --build- Dokploy: http://localhost:3020
- RelayKit Frontend: http://localhost:5173
- RelayKit Backend: http://localhost:4000
Quick Start
Run these commands from the project directory (the folder containing docker-compose.yml):
-
Set JWT_SECRET
Copy the example env file and set a secret:cp .env.example .env
Edit
.envand setJWT_SECRETto a random string (e.g. runopenssl rand -base64 32and paste the result). -
Create external Dokploy network (one-time)
docker network create dokploy-network
If it already exists, Docker will tell you and you can continue.
-
Start the stack
In the same project directory:docker compose --profile dev upWait until the containers are up (Dokploy at http://localhost:3020, RelayKit at http://localhost:5173).
-
Run the setup script
Still in the project directory. Replacenpub1your...with your real Nostr public key (the same one you’ll use to log in):OWNER_NPUB=npub1your... ADMIN_PASSWORD=your_secure_password ./scripts/setup-relaykit-auth.sh
This creates a Dokploy admin account, gets an API key, and writes it to the RelayKit container. After it succeeds, reload RelayKit in your browser (default
http://localhost:5173) and sign in with your Nostr extension (Alby, nos2x, etc.). -
If step 4 fails (e.g. “Registration failed” because Dokploy already has an admin): log in to Dokploy, go to Settings → Profile → API/CLI, create an API key, then in the project directory run (paste your key in place of
PASTE_THE_KEY_HERE):docker compose exec relaykit sh -c 'printf "%s" "PASTE_THE_KEY_HERE" > /app/.relaykit/bootstrap-key'
Local HTTPS (any domain you want): The cert covers whatever hostnames you list in scripts/dev-domains.txt (e.g. relay.local, myrelay.test, reallyrelay.io). Flow when adding a new relay in local dev:
- Add your chosen domain to
/etc/hosts(e.g.127.0.0.1 reallyrelay.io). - Add that domain to
scripts/dev-domains.txt(copy fromscripts/dev-domains.example.txtif you don't have one). - Run
./scripts/gen-dev-certs.sh. If compose is already running, restart it so Caddy picks up the new cert. - In RelayKit, create the relay and set its domain to that hostname; choose "No SSL" for local.
Then https://your-domain works in the browser and routes to the relay.
Prod: docker compose --profile prod up -d. No Caddy; Traefik on 80/443 with real certs. RelayKit: build frontend, backend serves static + tRPC, one port.
Set DEPLOY_HOST (e.g. root@1.2.3.4 or an SSH alias) and DEPLOY_PATH (repo path on server) in .env.
- Legacy source-build deploy on server:
./scripts/deploy.sh - Image-based deploy (recommended):
./scripts/deploy-image.sh
Image-based flow:
- GitHub Actions builds and pushes
ghcr.io/<owner>/relaykiton pushes tomaster. - Server pulls the image and recreates only
relaykit-prod.
Optional overrides for image deploy:
IMAGE_TAG=<sha-or-tag> ./scripts/deploy-image.shIMAGE_NAME=ghcr.io/<owner>/relaykit ./scripts/deploy-image.sh
If GHCR package is private, run docker login ghcr.io on the server first (PAT with read:packages).
Dokploy Integration:
- Backend calls Dokploy's REST API (need to find API docs)
- Dokploy runs on
http://dokploy:3000(accessible via Docker network)
- Header wordmark uses
Ethnocentric-Regular.otfinapp/frontend/src/assets/fonts/. - Source archive was provided as
~/Desktop/ethnocentric.zip. - License reference is included at
app/frontend/src/assets/fonts/Typodermic Desktop EULA 2023.pdf.
RelayKit has two domain flows: create a service (with domain in one go) and change a service's domain later.
| RelayKit action | Dokploy APIs (in order) |
|---|---|
| List services | project.all; then for each project → each environment → each compose in that environment, we build one list entry. |
| Create service (domain set at creation) | project.all or project.create → compose.create → compose.update → domain.create → compose.deploy |
| Change domain (edit existing service) | domain.delete → domain.create → compose.redeploy |
Presets:
- Each service has a folder in
/app/presets/ docker-compose.yml= standard Docker Compose file using${ENV_VAR}syntaxmetadata.json= service info (name, description, required config fields)- Backend collects config from user and passes as env vars to Dokploy's API
- Users can update env vars later without redeploying
- For routing: metadata must include
serviceName(compose service name) andinternalPort. Certificate type ("No SSL" for local, "Let's Encrypt" for prod) is chosen in the deploy modal and can be edited per service in the UI; editing a domain triggers redeploy. - For unique data per instance: use
{{DEPLOY_SUFFIX}}in volume names in the compose file; the backend replaces it at deploy time so each deployment gets its own volumes.
State:
- Option 1: Store deployment metadata in our own Postgres
- Option 2: Just query Dokploy API for deployed services (simpler)
- Decision: Start with option 2, add Postgres only if needed
issues:
- dns check in prod not working right
next:
- convey real deployment status in UI (not assumed success) - especially as it starts up, to know when it's "ready"
- inc adding a new service, doesn't appear immediately in the ui
- restart ui option.
- loader while service comes online, until started (and healthy?)
- what happens if I try to create a project with a domain already in use on another project?
- figure out full ssl for relaykit instance itself
- haven and or other dm relays
- way better relay explorer app
- improve blossom app
- improve/make nPanel app
- negentropy app
- uptime/service monitoring app
- convey service count in header or sidebar thing
- in relayexplorer, when viewing an event with encyrpted content, and I'm the author/receiver, show a decrypt button/option
maybe later:
- expose volumes to user so they can manage (view/delete/optional: create service from volume)
- publicly exposed relayexplorer?
