Skip to main content

Server setup

This guide shows how to run nodalmerge-server, choose durability/auth posture, and verify the instance is healthy before clients depend on it.

Before you start

Make sure you can provide:
  • A reachable bind address/port for WebSocket traffic
  • A persistence path (for durable environments)
  • Basic observability endpoint strategy (metrics/logs)
  • Authentication/token strategy for locked rooms (if required)

Quick start (native)

From the nodalmerge repo root:
cargo run -p nodalmerge-server -- --store ./data --metrics-addr 127.0.0.1:9090
Default listen endpoint is ws://127.0.0.1:7878/ws/<room> unless AS_BIND_ADDR is set.

Quick start (container)

docker build -t nodalmerge-server .
docker run --rm -p 7878:7878 -v "$PWD/data:/data" -e RUST_LOG=info nodalmerge-server
Use persistent volume mounts for any environment where restart durability matters.

Durability choice

In-memory mode

No --store flag means volatile in-memory operation. Use for:
  • Local dev
  • Short-lived demos
  • Ephemeral test runs
Do not use in-memory mode for production persistence guarantees.

Durable mode

Set --store <path> to enable persistent server storage. Durable mode enables:
  • Room/node/blob recovery across restarts
  • Safe idle-room eviction behavior
  • Blob GC on persisted blob data
Start with these explicit flags in non-trivial environments:
  • --store <path> for durability
  • --metrics-addr <ip:port> for Prometheus scraping
  • --idle-timeout <seconds> to reclaim inactive rooms safely
  • --broadcast-capacity <N> for room broadcast buffer sizing
  • --peer-rate-nodes <N> and --peer-rate-bytes <MiB> for ingress abuse protection
  • --blob-gc-interval <seconds> and --blob-gc-grace <seconds> for blob lifecycle automation
Defaults are reasonable for bootstrap, but pinning values explicitly helps operations stay reproducible across environments.

Bind and network configuration

Server bind address is controlled by AS_BIND_ADDR. Example:
AS_BIND_ADDR=0.0.0.0:7878 nodalmerge-server --store /data
For production, place TLS termination in front of the server so clients connect via wss://.

Auth posture options

NodalMerge supports open and locked room patterns.
  • Open rooms: no room token required
  • Locked rooms: token/capability validation required at handshake
For hosted auth integration, run a token-minting bridge service that issues room-capability tokens based on your identity provider.

Embedding the host as a library

The standalone nodalmerge-server binary above is one deployment shape. If you’re embedding NodalMerge inside your own web application instead of running it as a separate process, use one of the embeddable host libraries:
  • .NET — the NodalMerge.DotNetHost NuGet package exposes MapNodalMergeEndpoints(), an ASP.NET Core extension method you call on your own WebApplication. You own Program.cs and the full request pipeline.
  • Rust — the nodalmerge-host-axum crate exposes build_router(rooms), which returns an axum::Router you compose into your own application, alongside build_router_with_permissive_cors(rooms) as an explicit dev-only convenience.
Both embeddable hosts hand you the request pipeline directly rather than owning it themselves — which means CORS is your responsibility as the consumer, not something the package configures for you. This is different from the standalone nodalmerge-server binary above, which has a permissive CORS layer baked in since it’s a deployable service with no external “consumer” to hand that decision to.

Configuring CORS when embedding

.NET (NodalMerge.DotNetHost):
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(policy =>
        policy.WithOrigins("https://your-app.example.com")
              .AllowAnyHeader()
              .AllowAnyMethod());
});

var app = builder.Build();
app.UseCors();
app.MapNodalMergeEndpoints();
app.Run();
Rust (nodalmerge-host-axum):
use nodalmerge_host_axum::build_router;
use tower_http::cors::{CorsLayer, AllowOrigin, Any};

let app = build_router(rooms).layer(
    CorsLayer::new()
        .allow_origin(AllowOrigin::exact("https://your-app.example.com".parse().unwrap()))
        .allow_methods(Any)
        .allow_headers(Any),
);
Do not reach for build_router_with_permissive_cors() outside local development — it allows any origin and should never front internet-exposed production traffic.

Health and verification checklist

After startup, verify:
  • Server logs show listening endpoint
  • WebSocket upgrade succeeds at /ws/<room>
  • Metrics endpoint responds when enabled (/metrics)
  • A test client can connect, sync, and exchange packs
  • Restart preserves expected state when --store is enabled

Common setup mistakes

  • Running production traffic without --store
  • Enabling lifecycle knobs in in-memory mode and expecting durability effects
  • Shipping without ingress rate limits
  • Treating direct blob I/O as required (it is an optimization path)
  • Skipping startup verification after changing bind/auth settings

Minimal production baseline

Use this as a starting template:
  • Durable storage enabled (--store)
  • Metrics enabled and scraped
  • Rate limits set and monitored
  • Idle-room eviction enabled with conservative timeout
  • Blob GC enabled with non-zero grace
  • Locked-room/token strategy documented and tested