# Self-hosting

The whole stack on your hardware: dashboard (AGPL-3.0-only), Postgres, ClickHouse, and the Crucible agent (MIT) reporting to your own URL. Nothing phones home; there is no license key; every feature is enabled.

## Overview

The [github.com/glassmkr/crucible](https://github.com/glassmkr/crucible) repository ships a `docker-compose.yml` that runs three services: the dashboard, Postgres 16 (accounts, servers, alert state), and ClickHouse 24 (telemetry history). Database migrations apply automatically on boot. The compose file sets `GLASSMKR_SELF_HOSTED=1`, which removes every gate: no plans, no node limits, no billing. The programmatic API is on, and the AI analysis hook turns on as soon as you set `LLM_API_URL`. The MCP server ships in the box but is off by default: its OAuth server refuses a non-HTTPS origin in production, so it needs TLS in front plus a few environment variables. The flow has been exercised on the hosted origin, not yet on a self-hosted one. The [self-hosting guide](/docs/self-hosting) lists them.

## Prerequisites

- Docker with the compose plugin (or a Postgres 16 + ClickHouse 24 you operate yourself; the app is configured entirely by environment variables).
- 2 GB RAM is comfortable for a small fleet; ClickHouse is the hungriest part.
- Linux hosts to monitor, with Node 22.19.0+ for the npm agent install (the binary install needs no Node at all).
- Headroom on the monitored hosts for the agent itself. On a 12-disk Supermicro running Rocky 9.8 we measured about 180 MB resident in steady state, with a transient peak near 875 MB during collection. The peak is brief and the agent settles back, but it is real: size a small VPS for the peak, not the average, or the kernel may kill the agent on its first collection. Hosts with more disks and sensors to enumerate will sit higher.

## Quickstart

{#if !DASHBOARD_REPO_PUBLIC}

> **The dashboard repository is not public yet.** The clone below will fail until it is. The agent is already public and MIT at github.com/glassmkr/crucible, and everything else on this page is accurate for the moment the repository opens. If you want to be told when that happens, mail [hello@glassmkr.com](mailto:hello@glassmkr.com).

{/if}

```
git clone https://github.com/glassmkr/glassmkr.git
cd glassmkr
cp env.selfhost.example .env
./scripts/selfhost-setup.sh     # generates the secrets
docker compose up -d
```

Then open `http://localhost:3000`, register the first account, and you are on the dashboard.

> **GLASSMKR_KEY_PEPPER** is the pepper for API-key hashing. `selfhost-setup.sh` generates it, and re-running the script never overwrites it. Never change it after first boot; changing it invalidates every issued key.

Migrations run automatically: ClickHouse's on every `up` (they are idempotent), Postgres's from the dashboard container before the app starts (tracked, so only new ones apply).

## Pointing an agent at your dashboard

Create the server in your dashboard (or via `POST /api/v1/servers` with a write key from Settings); it returns the per-server `gmk_cru_live_...` key once. Then on the server, one command installs the agent and points it at YOUR dashboard:

```
curl -fsSL https://glassmkr.com/install.sh | sudo bash -s -- \
  --api-key "gmk_cru_live_..." \
  --ingest-url "http://your-dashboard-host:3000/api/v1/ingest"
```

The URL must be the full `/api/v1/ingest` endpoint (a bare base URL also works; the agent appends the path and says so). The installer allowlists exactly that origin for the agent's endpoint policy, which is what makes an http or private-network dashboard address work; https endpoints on public DNS need nothing extra.

If you install the npm package yourself instead, the same two flags on `glassmkr-crucible init` do the same thing (plus `--allow-endpoint-origin ` for http/private endpoints; run `glassmkr-crucible init --help` for the full surface).

The agent stores the URL in `/etc/glassmkr/crucible.yaml`, so the flags are only needed at init time. Snapshots arrive roughly every five minutes; the first one appears on the dashboard within one cycle.

## AI analysis (optional)

Set `LLM_API_URL` in `.env` to any OpenAI-compatible endpoint and `LLM_MODEL` to the model name. Local Ollama works:

```
LLM_API_URL=http://host.docker.internal:11434/v1
LLM_MODEL=llama3.1:8b
```

Leave it empty and the analyze button reports that AI analysis is not configured; everything else is unaffected.

## Upgrading

```
git pull
./scripts/selfhost-setup.sh
docker compose build dashboard
docker compose up -d
```

Run the setup script on every upgrade. It is idempotent and never changes a value you already have, so usually it just prints "already set" and does nothing. It matters on the upgrade where a release needs a secret your `.env` predates: without it, `docker compose` refuses to parse.

New migrations apply on boot, the ClickHouse set idempotently and the Postgres set tracked. Read the [changelog](/docs/changelog) before major version jumps.

## Backups

State lives in the two Docker volumes: `pgdata` (accounts, servers, alert state, keys) and `chdata` (telemetry history). Snapshot both. Postgres is the one you cannot afford to lose; telemetry history is rebuildable by time.

## Self-hosted vs hosted

Same codebase. The hosted instance at [app.glassmkr.com](https://app.glassmkr.com) is the maintained reference deployment and live demo; self-hosted is the same thing on your hardware with every gate removed by the `GLASSMKR_SELF_HOSTED` flag. If you ever move between them, agents re-point with one `init` and the API is identical.
