OrbitDB relay used by our apps and tests, including media pinning and sync.
See AGENTS.md for an architecture and feature guide (entrypoints, data flow, env vars, and extension points).
Full documentation lives in the Docusaurus site under docs/docusaurus/.
Build and browse it locally:
pnpm --dir docs/docusaurus install
pnpm --dir docs/docusaurus start # dev server on http://localhost:3000/orbitdb-relay/
# or a production build:
pnpm --dir docs/docusaurus buildIt covers, among others:
- Overview — what orbitdb-relay is and when you need it.
- Getting Started — Quickstart (CLI), Docker Compose, systemd.
- Concepts — Architecture, Sync & the #1255 workaround, Media pinning flow, Peer recovery.
- Guides — Access controllers, Identity providers, Using as a library, libp2p integration.
- Reference — HTTP API, CLI, Environment variables, Version compatibility, Ports.
Internal design notes that are not user documentation live in docs/internal/.
Install:
npm i -g orbitdb-relayRun:
orbitdb-relayOptional connectivity debug protocols for test tooling are disabled by default in the CLI/runtime. Enable them with env vars before startup:
RELAY_CONNECTIVITY_ECHO_ENABLED=1 orbitdb-relay
RELAY_CONNECTIVITY_BULK_ENABLED=1 orbitdb-relayBulk tuning is also available through RELAY_CONNECTIVITY_BULK_MAX_FRAME_BYTES, RELAY_CONNECTIVITY_BULK_READ_TIMEOUT_MS, and RELAY_CONNECTIVITY_BULK_IDLE_TIMEOUT_MS.
Default listener ports:
- TCP:
9091viaRELAY_TCP_PORT - WebSocket:
9092viaRELAY_WS_PORT - WebRTC-direct:
9093viaRELAY_WEBRTC_PORT - QUIC:
9094viaRELAY_QUIC_PORT - Metrics HTTP:
9090viaMETRICS_PORT - Metrics HTTPS:
9443viaMETRICS_HTTPS_PORTwhenMETRICS_HTTPS_ENABLED=1
AutoTLS notes:
- AutoTLS is enabled by default. It is only disabled if
disableAutoTLSis set in the environment. - The default startup logs do not print much AutoTLS-specific output, so it is normal not to see certificate activity unless you enable the relevant debug namespaces.
- When AutoTLS has provisioned a certificate for the WebSocket listener, the relay should advertise secure WebSocket multiaddrs ending in
/tls/ws. - If you use
VITE_APPEND_ANNOUNCE, keep the public WebSocket address as plain/wswith your real public IP and port. Do not manually change it to/tls/ws; AutoTLS/domain mapping adds the secure advertised addresses at runtime. - To verify WSS is live, check
GET /multiaddrsand look for entries containing/tls/ws.
Show AutoTLS logs:
DEBUG='libp2p:auto-tls,libp2p:auto-tls:*,libp2p:websockets:listener' ENABLE_GENERAL_LOGS=1 orbitdb-relayIf you also want the relay to expose the metrics routes over HTTPS once AutoTLS has provisioned a certificate:
METRICS_HTTPS_ENABLED=1 DEBUG='libp2p:auto-tls,libp2p:auto-tls:*,libp2p:websockets:listener' ENABLE_GENERAL_LOGS=1 orbitdb-relayWith those flags enabled, you should see AutoTLS messages such as certificate fetch attempts, reasons it is not fetching yet, and WebSocket HTTPS listener updates.
Test mode (deterministic peer id via TEST_PRIVATE_KEY or RELAY_PRIV_KEY):
orbitdb-relay --testThe package still exports startRelay() as the compatibility wrapper used by the CLI and the existing tests.
For a fuller install + integration walkthrough, see the libp2p integration guide in the
Docusaurus site (docs/docusaurus/docs/guides/libp2p-integration.md).
It also exports reusable building blocks for embedded consumers:
orbitdbReplicationService()connectivityDebugProtocolsService()for opt-in test/debug echo + bulk protocolscreatePinningHttpRequestHandler()andPinningHttpServerfor/health,/multiaddrs,/pinning/*, and/ipfs/*
orbitdbReplicationService() mounts the OrbitDB replication + Helia pinning logic directly in any libp2p node:
import { createLibp2p } from "libp2p";
import { LevelDatastore } from "datastore-level";
import { LevelBlockstore } from "blockstore-level";
import { gossipsub } from "@chainsafe/libp2p-gossipsub";
import { identify } from "@libp2p/identify";
import { orbitdbReplicationService } from "orbitdb-relay";
const datastore = new LevelDatastore("./tmp/ipfs/data");
const blockstore = new LevelBlockstore("./tmp/ipfs/blocks");
await datastore.open();
await blockstore.open();
const libp2p = await createLibp2p({
datastore,
services: {
identify: identify(),
pubsub: gossipsub(),
orbitdbReplication: orbitdbReplicationService({
datastore,
blockstore,
orbitdbDirectory: "./tmp/orbitdb",
}),
},
});
await libp2p.services.orbitdbReplication.syncAllOrbitDBRecords("/orbitdb/...");Notes:
orbitdbReplicationService()expects caller-owneddatastoreandblockstore.- Stopping the libp2p node stops the replication service, OrbitDB, and its Helia instance.
- The caller still closes
datastoreandblockstoreafterlibp2p.stop(). - Since
0.9.7, subscription changes retain an in-memory database-topic-to-peer hint.POST /pinning/syncmay reconnect those known subscribers after opening the database so OrbitDB's native heads protocol can recover a newer writer head. No application records travel through this directory.
orbitdb-relay supports the following Access Controller types when opening OrbitDB databases:
orbitdb(built-in OrbitDB access controller)orbitdb-deferred(custom deferred OrbitDB ACL registered by this package)todo-delegation— delegated todo writes (same rules as the app; see below)
Notes:
- Existing databases are opened using the manifest
accessController.type. - Creating a new database without explicitly passing an
AccessControllerstill defaults toorbitdb.
The delegated todo access controller lives in @le-space/orbitdb-access-controller-delegated-todo (shared with simple-todo) so pinning uses the same canAppend and verifyDelegationWriterIdentity behavior as the browser. This relay depends on ^0.1.0 of that package.
If npm install cannot resolve it yet, install from a local checkout of simple-todo:
npm install ../path/to/simple-todo/packages/orbitdb-access-controller-delegated-todoThe relay registers OrbitDB identity providers so it can verify oplog entries from peers that use passkey-backed identities (same stack as @le-space/orbitdb-identity-provider-webauthn-did):
publickey— default from@orbitdb/coredid—@orbitdb/identity-provider-didwebauthn— worker WebAuthn + keystore (e.g. Ed25519did:keyvia keystore)webauthn-varsig— hardware varsig identities (verification uses embedded public key only; no passkey on the server)
See .env.example for a full list including circuit relay v2 tuning (RELAY_CIRCUIT_*).
RELAY_TCP_PORT,RELAY_WS_PORT,RELAY_WEBRTC_PORTRELAY_DISABLE_WEBRTC=trueto disable UDP/webrtc-directlistener in constrained environmentsMETRICS_PORT=0to bind metrics on an ephemeral port (avoidEADDRINUSE)METRICS_CORS_ORIGIN— CORS for HTTP helpers (/health,/multiaddrs,/pinning/*, …); default*; use a comma-separated origin allowlist in productionDATASTORE_PATHorRELAY_DATASTORE_PATHto control where LevelDB data is storedPUBSUB_TOPICSto override pubsub peer discovery topics (default:todo._peer-discovery._p2p._pubsub)TEST_PRIVATE_KEY/RELAY_PRIV_KEYfor--testruns (optional)- Circuit relay (v0.4+):
RELAY_CIRCUIT_HOP_TIMEOUT_MS,RELAY_CIRCUIT_MAX_RESERVATIONS,RELAY_CIRCUIT_RESERVATION_TTL_MS,RELAY_CIRCUIT_DEFAULT_DATA_LIMIT_BYTES,RELAY_CIRCUIT_DEFAULT_DURATION_LIMIT_MS— defaults are set to 10× the pre-0.4 hardcoded limits (seesrc/config/circuit-relay-env.ts).
Mocha suites live under mocha/ (not test/) so node --test (Node’s built-in runner) does not auto-load them; those files use Mocha’s describe / it. Run npm test for Mocha.
npm i
npm run build
node dist/cli.js --testSee docker-compose.example.yml for a minimal deployment example with:
- persistent datastore volume (PeerId/key survives restarts)
- relay + metrics ports exposed (
9091/tcp,9092/tcp,9093/udp,9090/tcp) - WebRTC enabled and AutoTLS left enabled by default