Docs

Deploy your own server

Noggin doesn't need to live on a computer at your desk. A small cloud VPS keeps it online 24/7 without your laptop staying awake. This guide is provider-agnostic — it works on DigitalOcean, Hetzner, Linode, AWS Lightsail, or anywhere you can get a fresh Ubuntu box.

For comfortable-with-a-terminal users

When to prefer this

VPS vs. a machine at home

Prefer a VPS when...

  • You don't have a machine that stays powered on 24/7.
  • You want a stable public hostname instead of relying on your home network.
  • You're fine paying a few dollars a month for a small cloud box.

Prefer /get's home setup when...

  • You already have a Mac, Linux box, or Windows machine that's always on.
  • You'd rather keep everything on hardware you physically control.
  • You want the simplest path — see /get.

Setup

Ubuntu server: bootstrap and harden

The noggin repo ships scripts under deploy/ that were built for Brandon's own multi-tenant pilot host, so treat them as a reference to crib from rather than a single button to press — but the Docker, hardening, and sandboxing pieces apply directly to a single-user deploy too.

1. Get Docker running

deploy/bootstrap-ubuntu.shinstalls Docker Engine from the official repo on a fresh Ubuntu Server box, plus a default firewall posture. It also sets up pieces specific to Brandon's tenant-hosting pilot (a control-plane service, Cloudflare Tunnel ingress) — read it before running the whole thing, and feel free to run just the Docker install section by hand if that's all you need.

run as root, from the noggin repo
sudo -E ./deploy/bootstrap-ubuntu.sh

2. Harden the host

deploy/harden-host.sh locks down SSH (no password auth, no root login), enforces a default-deny inbound firewall with nftables, and turns on unattended security upgrades. Run it after Tailscale is up so its rules can see the tailnet interface.

sudo tailscale up --ssh --hostname noggin-host
sudo ./deploy/harden-host.sh

3. Optional: gVisor sandboxing

deploy/install-gvisor.sh registers gVisor (runsc) as an available Docker runtime without touching your existing daemon config. It doesn't change Docker's default runtime — you opt a container into it explicitly. This is the real isolation boundary worth having, since the agent can run shell commands on the host it's deployed to.

sudo ./deploy/install-gvisor.sh

# then run the gateway container with:
docker run --runtime runsc ... hermes-agent

4. Start the gateway with Docker Compose

The repo's docker-compose.yml is built for exactly this — a single-user deploy. Set your provider key in ~/.hermes/.env, generate a strong API_SERVER_KEY (openssl rand -hex 32 — anything under 16 characters is refused at startup), uncomment API_SERVER_HOST / API_SERVER_KEY in the compose file, and bring it up.

HERMES_UID=$(id -u) HERMES_GID=$(id -g) docker compose up -d

restart: unless-stoppedin the compose file keeps the gateway running across reboots — the repo's deploy/systemd/units are specific to Brandon's multi-tenant pilot, so Compose's own restart policy is the simplest way to keep a single-user gateway alive. If you'd rather run the gateway as a bare process, wrap python -m hermes_cli.main gateway run in your own minimal systemd unit.

Exposure

Tailscale vs. Cloudflare Tunnel

Recommended

Tailscale

Private mesh network — only your own devices can reach the server, over an encrypted tunnel, with no public DNS record at all.

tailscale serve https / http://127.0.0.1:8642
Alternative

Cloudflare Tunnel

Gives you a real public HTTPS hostname without opening any inbound ports — useful if you want to reach the server from a device that isn't on your tailnet. Get a tunnel token from the Cloudflare Zero Trust dashboard, then:

curl -fsSL https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb -o cloudflared.deb
sudo apt-get install -y ./cloudflared.deb
sudo cloudflared service install <your-tunnel-token>

Checklist

Hardening checklist

  • Strong API_SERVER_KEY — generate with openssl rand -hex 32; the server refuses anything under 16 characters.
  • Dashboard stays localhost-only — the dashboard service binds to 127.0.0.1 by default and stores API keys; if you need remote access, tunnel it (ssh -L) rather than exposing it directly.
  • Default-deny inbound firewall— only allow the ports you actually need exposed (or none, if you're routing everything through Tailscale/Cloudflare Tunnel).
  • Keep the host updated — unattended security upgrades, and re-run deploy/harden-host.sh after major changes.

What NOT to do

  • Don't pair or serve over plain http:// on the open internet — the pairing token is a bearer credential.
  • Don't expose the dashboard beyond localhost/a tunnel — it stores API keys.
  • Don't run this as multi-user infrastructure from one instance — it's single-user software today. One server, one person.