Skip to content

Installing Hivemind

This is the walkthrough for a fresh install of self-hosted Hivemind. Read it once end-to-end before running anything — there are four prerequisites + one copy-paste command + a short verification step. Plain text, copy-pasteable. American English. The bootstrap path delivers a per-customer signed bundle; the install script generates host secrets locally and never carries any of them over the wire.

Prerequisites

Hivemind runs as a small Compose stack (one server image + a Postgres). You need:

Requirement Why Verify
Linux host (x86_64) The published image is glibc / amd64. uname -m shows x86_64.
Docker Engine 24+ and Docker Compose v2 The stack uses Compose's secrets: + per-service healthchecks. docker --version, docker compose version.
Outbound HTTPS to updates.seglamater.app and registry.seglamater.app The bootstrap fetches your bundle + the signed image. curl -fsS https://updates.seglamater.app/.well-known/keys/hivemind.pub returns a PEM.
~2 GB free disk + ~512 MB RAM Container + database + room for migrations. df -h /srv and free -h.
A user with permission to write to your chosen install directory The default is /srv/hivemind/. Use any path you own. mkdir -p /srv/hivemind && touch /srv/hivemind/.testwrite && rm /srv/hivemind/.testwrite

Optional, only if you want them:

  • An LLM API key if you want agents to answer (Anthropic, OpenAI, Ollama, or any OpenAI-compatible endpoint). Hivemind boots and runs without one; LLM-dependent surfaces report LLM startup ping FAILED and stay degraded until you configure a provider in .env (see byok-llm.md).
  • A reverse proxy (Caddy, Traefik, nginx) if you'll terminate TLS in front of Hivemind. The container binds 127.0.0.1:8585 by default — that is intentional so the unauthenticated API isn't exposed on every interface during install. See the operator notes for a Caddy snippet.
  • An OIDC IdP (Authentik / Keycloak / similar) if you want SSO. The default is local accounts; see sso-setup.md to switch.

Install

You should have received an invite URL from your Seglamater contact. It looks like https://s.seglamater.app/i/<invite-id>. Run:

curl -fsSL https://s.seglamater.app/i/<invite-id> | bash

That bootstrap script:

  1. Fetches the bundle for your customer (bundle.json) and the top-level install.sh.
  2. Pins the install layout under $HIVEMIND_INSTALL_DIR (default /srv/hivemind/). Override before piping with HIVEMIND_INSTALL_DIR=/path/you/own curl … | bash.
  3. Invokes install.sh with the bundle. The bundle tells install.sh which image digest and which channel to pin, plus your client_id, the public URL you should use, and a few customer-specific defaults.

You'll see a sequence of [install] lines as the script:

  • Verifies your bundle's signature against the pinned cosign public key.
  • Pulls the digest-pinned hivemind-server image from registry.seglamater.app/seglamater/hivemind-server@sha256:… and the Postgres image.
  • Generates per-host secrets (database password, admin API key, JWT signing key, cookie secret, the integration encryption key for chatalot bot tokens) into ./secrets/ under your install dir, mode 0600. These are written locally — they never leave your host and they are NOT in the bundle.
  • Renders .env from defaults and the bundle metadata.
  • Brings up the Compose stack and waits for the server healthcheck to pass.

Expected total runtime: 1-3 minutes on a warm Docker cache, 3-5 minutes for the first pull on a clean host.

The script is idempotent. Re-running it picks up an existing install: it reuses existing secrets (does not regenerate them), re-renders .env only for keys it doesn't already see, and a docker compose up -d brings the stack back to running. If you need a true fresh install, remove the install dir and start over.

First run

After install.sh exits success, you should see one of these on the host:

curl -fsS http://localhost:8585/api/v1/health
# {"status":"ok","version":"0.1.9"}

docker compose ps
# All three of hivemind-server, hivemind-db (postgres), hivemind-updater
# should show "Up" and "(healthy)".

If /api/v1/health returns ok, Hivemind is running. The unauthenticated API is intentionally bound to 127.0.0.1:8585 — to expose it on the internet you need a reverse proxy in front of it; see operator-notes.md for a Caddy snippet that terminates TLS, sets the right Host header, and forwards /api/v1/* to the container.

Logs live in the container; tail them with:

cd /srv/hivemind   # or your install dir
docker compose logs --tail=100 hivemind-server
docker compose logs --tail=100 postgres

The first time the server starts, look for these specific lines (they're the load-bearing init steps and they each name what's wrong if they fail):

  • database migrations applied
  • BYOK LLM provider initialised … (the line below it may be a WARN about the LLM ping failing — that's expected until you set HIVEMIND_LLM_API_KEY in .env, see byok-llm.md).
  • integration credential key loaded — chatalot integrations enabled
  • listening addr=0.0.0.0:3000

Admin setup

The first POST to /auth/register becomes the admin account. After install, do this once (only allowed while the users table has no local accounts — the second POST 409s):

curl -fsS -X POST -H 'Content-Type: application/json' \
  -d '{"email":"you@example.com","password":"<strong password>"}' \
  http://localhost:8585/auth/register

Or, if you prefer the web UI: open the URL from your .env's HIVEMIND_PUBLIC_URL (set this before you enable any operator-facing surface) and register from the form on the home page. The first registered user is the admin.

For service-to-service authentication (your other tools calling Hivemind), use the ADMIN_API_KEY from ./secrets/admin_api_key as the X-API-Key header. The local admin account is for browsers; the API key is for daemons.

If you'll switch to SSO instead of local accounts, do that before registering — see sso-setup.md. With forward auth enabled, the POST /auth/register path is closed and identities come from your IdP.

What lives where

<install-dir>/                      (default /srv/hivemind)
├── docker-compose.yml              # The stack. Don't edit; rerun install.sh to upgrade.
├── docker-compose.override.yml     # (optional) Your local additions: reverse-proxy labels,
│                                   # extra networks, host-specific extra_hosts. Survives
│                                   # upgrades — install.sh never touches it.
├── .env                            # Configuration. Edit to set LLM keys, OIDC, public URL.
├── scripts/
│   ├── install.sh                  # Bootstrap also dropped this here; run with --help.
│   └── bundle.json                 # Your signed bundle. Pins image digest + channel.
├── secrets/                        # 0700 dir, 0600 files. Local-only, never shipped.
│   ├── db_password
│   ├── admin_api_key
│   ├── jwt_signing_key
│   ├── cookie_secret
│   ├── integration_encryption_key  # ChaCha20-Poly1305 key for chatalot bot tokens at rest
│   ├── updater_token               # HMAC for the in-process updater sidecar
│   └── cosign_pub                  # Pinned cosign pubkey (does not rotate without a release)
└── (a `postgres-data` Docker volume, not a host path)

The postgres data is a Docker named volume (hivemind_pgdata), not a host bind mount. Back it up via docker compose exec postgres pg_dump … or with Borg / Restic against /var/lib/docker/volumes/hivemind_pgdata/.

Common follow-ups

  • Configure your LLM provider (the boot log will be loud until you do) — see byok-llm.md.
  • Set the public URL in .env (HIVEMIND_PUBLIC_URL=https://hivemind.example.com) before you put a reverse proxy in front.
  • Switch to SSO — see sso-setup.md.
  • Apply updates — managed-update path detects new signed releases and applies them on operator approval. See upgrade.md.

If anything in the install errored, the install dir is left in place for inspection (no auto-teardown on failure). Re-run install.sh after fixing the cause; it's idempotent.

If the post-install verifications above don't pass, see troubleshooting.md — it has the boot-time failure modes and their fixes, including the most common one ("integration routes return 503 integration_key_missing" — almost always a .env not loading or secrets/ not mounted).