GemmaPoddocs
Guides

Run your own Host

The Host is the device-side of GemmaPod — proxies pods to a local Gemma 4 on Ollama, enforces signed-manifest tool allow-lists, and stores conversation memory in SQLite.

The Host is the device side of GemmaPod. It runs on your machine (Mac mini, NAS, VPS, Raspberry Pi — anything that runs Node 22 and can reach the signaling broker outbound). One Host process serves many pods. For each visitor that opens one of your pods, it:

  1. Accepts a WebRTC offer through the signaling broker.
  2. Exchanges signed dartc.hello envelopes with the visitor's session key.
  3. Verifies the inlined signed manifest carried in dartc.hello.
  4. Intersects the manifest's signed tool allow-list with its local registry — only tools in both are exposed to the model.
  5. Streams gemmapod.chat.delta frames + signed gemmapod.ui.event envelopes as the model generates.

Prereqs

  • Node 22+

  • The unified CLI: npm i -g gemmapod

  • Ollama (or any OpenAI-compatible endpoint) with gemma4:e4b pulled:

    ollama pull gemma4:e4b
    ollama serve
  • A reachable signaling broker — either the default wss://signal.gemmapod.com/signal or your self-hosted broker.

Start the Host

The simplest path: just gemmapod run a pod. If no Host is running, one starts in the foreground automatically.

gemmapod run ./my-pod

If you want a Host with no pods yet (e.g. so the CLI sees it on the next gemmapod list), start it explicitly:

gemmapod start             # foreground
gemmapod start --detach    # background, returns immediately
gemmapod start --headless  # no dashboard UI

What you'll see (foreground):

[host] ready at http://localhost:57447
[host] dashboard at http://localhost:57447

When a visitor opens one of your pods you'll see:

[host] offer  session=… (1234 bytes)
[host] dartc.hello verified  visitor=… manifest_ok=true tools=[…]
[host] chat.request  conversation=…  model=gemma4:e4b
[host] chat.done     conversation=…

The Host writes a discovery file at ~/.gemmapod/host.json containing its port, PID, and an auth token. The CLI reads it to talk to the running Host; gemmapod status will show whether it's healthy.

Configuration

Most configuration lives in each pod's pod.toml (signal URL, owner pubkey, model). The Host itself takes a few process-level inputs:

Env / flagDefaultMeaning
--port <n> / GEMMAPOD_PORTfirst free in 57447..57456Loopback port for the HTTP API + dashboard.
OLLAMA_URLhttp://localhost:11434Where the Host proxies model requests.
OWNER_PUBKEYunsetIf set, reject any pod manifest not signed by this key. Recommended.
GEMMAPOD_HOST_DB~/.gemmapod/host.sqliteSQLite path for conversation memory.
GEMMAPOD_CONTACT_JSONunsetJSON returned by the built-in share_contact tool.

Per-pod settings (signal URL, pod id, model) come from each pod's pod.toml. See pod.toml reference.

Docker

docker run --rm \
  -e OLLAMA_URL=http://host.docker.internal:11434 \
  -e OWNER_PUBKEY=<your_pubkey_hex> \
  -v ~/.gemmapod:/root/.gemmapod \
  -v $(pwd)/my-pod:/pod \
  -p 57447:57447 \
  ghcr.io/gemmapod/host:latest /pod

host.docker.internal only resolves on Docker Desktop. On Linux, either run with --network host or expose Ollama on a routable address.

Run under launchd / systemd / pm2

The Host is a long-lived foreground process. Reasonable runners:

  • macOS — launchd: a plist with KeepAlive=true invoking gemmapod start --headless.
  • Linux — systemd: a unit file with Restart=always and ExecStart=/usr/bin/gemmapod start --headless.
  • Anywhere — pm2: pm2 start gemmapod --name gemmapod-host -- start --headless.

Use --detach if you're scripting and want the CLI invocation to return; use --headless to skip serving the dashboard UI.

Conversation memory

The Host keeps (podId, conversationId) rows in SQLite. A visitor's browser refresh creates a new WebRTC peer and a new ephemeral DARTC session key, but sends the same conversationId in the next dartc.hello — so the Host reattaches to the existing thread.

To clear memory for a pod:

sqlite3 ~/.gemmapod/host.sqlite "DELETE FROM conversations WHERE pod_id = 'hello-pod';"

Production hardening

  1. Set OWNER_PUBKEY in each pod's pod.toml. Without it, anyone running a pod with the same pod_id could request your tools.
  2. No inbound HTTP. The Host is outbound-only — it dials the signaling broker via WSS. Don't forward ports.
  3. Single Ollama URL. The Host won't talk to arbitrary backends.
  4. Audit log. Stream the Host's stderr to a file or your log aggregator. Each tool call is logged.

See also