name: nostrsearch services: # ── Unified node ─────────────────────────────────────────────────────────── # ONE process: search API + archive HTTP + nostr archive relay + live # firehose, all over a shared Tantivy index, one archive database, and one # stats/WoT engine. Writes are serialized through a single writer task, and # the search reader auto-reloads on commit, so relay/firehose events become # searchable within the commit interval. server: image: voidic/nostrsearch:latest build: context: . dockerfile: Dockerfile restart: unless-stopped ports: - "8080:8080" # Archive downloads and relay websockets are descriptors too, on top of # RocksDB SSTs and Tantivy writer locks. The process raises its own soft # limit at startup, but it cannot exceed the hard limit set here. ulimits: nofile: soft: 65536 hard: 65536 environment: INDEX_ROOT: /data/index BIND: 0.0.0.0:8080 RUST_LOG: nostrsearch=info,tower_http=info # Publish the raw .jsonl.zst corpus at /archive and archive into it. ARCHIVE_DIR: /data/archive STATE_DIR: /data/stats WOT_OUT: /data/wot.bin # Accept inbound writes at / (nostr archive relay). Relay events are # archived AND indexed AND folded into stats by the same process. ENABLE_RELAY: "1" # Comma-separated upstream relays enable the live firehose in-process. RELAYS: "wss://relay.damus.io,wss://nos.lol,wss://relay.primal.net" volumes: - index-data:/data # Search is mmap-heavy; give the page cache room. Tune to the host. deploy: resources: limits: memory: 24G reservations: memory: 4G healthcheck: test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8080/healthz"] interval: 30s timeout: 5s retries: 3 start_period: 10s # ── One-shot archive ingest ──────────────────────────────────────────────── # Unified pipeline in backfill mode: streams a directory of Nostr JSONL dumps # (.jsonl/.json/.zst/.gz/.bz2) into BOTH the Tantivy index and the stats/WoT # engine, then exits. Run on demand: # # DUMPS_DIR=/path/to/dumps docker compose --profile ingest run --rm ingest # # Writes into the same index volume the server reads. Analysis state persists # under /data/stats so a later run (or the firehose) warm-starts with WoT. ingest: image: voidic/nostrsearch:latest build: context: . dockerfile: Dockerfile profiles: ["ingest"] restart: "no" entrypoint: ["./bin/ingest"] command: - "--index-root" - "/data/index" - "--input-dir" - "/dumps" - "--heap-mb" - "64" # Total writer heap is max-open-shards x heap-mb. Keep the cap at or # above the number of months the corpus spans: dumps are often not # date-ordered, and a smaller cap evicts writers that are immediately # needed again, paying a commit + fsync each time. - "--max-open-shards" - "64" - "--commit-docs" - "200000" # Stats/WoT are computed from the same stream and hot-swapped into scoring. - "--state-dir" - "/data/stats" - "--wot-out" - "/data/wot.bin" environment: RUST_LOG: nostrsearch=info,nostr_archive_cursor=info volumes: - index-data:/data - ${DUMPS_DIR:-./dumps}:/dumps:ro deploy: resources: limits: memory: 16G volumes: index-data: driver: local