Run your instance / Install
Docker and Compose
Run the public image with Docker Compose or plain docker run, pin a build, and upgrade.
knotel ships as one public image, ghcr.io/kien-ngo/knotel, for linux/amd64 and linux/arm64. It holds the UI, the OTLP ingest endpoints, the browser script and the MCP server, and needs a ClickHouse server to store spans, logs and web events in. Nothing needs to be cloned or built: pull the image and run it.
There are two ways to run it. Docker Compose starts knotel and ClickHouse together from one file, and is what most instances should use. Plain Docker runs the same two containers with docker run, or the app alone next to a ClickHouse you already have.
What you need
- Docker 24 or newer, and for the Compose route the Compose plugin 2.23 or newer (
docker compose version). Older Compose versions don't read the inlineconfigsthe file below uses. - A machine with 2-4 vCPU and 4-8 GB of RAM handles millions of spans a day. See Usage and costs.
- Ports 3000 (UI and OTLP/HTTP) and, if you want OTLP/gRPC, 4317.
Docker Compose
1. Write the Compose file
Make a directory for the instance, such as ~/knotel, and save this as compose.yml in it. ClickHouse's settings are inline, so this one file is the whole stack.
services:
knotel:
image: ghcr.io/kien-ngo/knotel:${KNOTEL_TAG:-latest}
restart: unless-stopped
env_file: .env
environment:
CLICKHOUSE_URL: http://clickhouse:8123
CLICKHOUSE_USER: knotel
CLICKHOUSE_DB: knotel
ports:
- "${HOST_PORT:-3000}:3000"
# OTLP/gRPC, for SDKs and Collectors left on their default protocol.
- "${GRPC_HOST_PORT:-4317}:4317"
volumes:
- knotel-data:/data
depends_on:
clickhouse:
condition: service_healthy
clickhouse:
image: clickhouse/clickhouse-server:26.8
restart: unless-stopped
environment:
CLICKHOUSE_USER: knotel
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:?set CLICKHOUSE_PASSWORD in .env}
CLICKHOUSE_DB: knotel
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: "1"
volumes:
- clickhouse-data:/var/lib/clickhouse
configs:
- source: clickhouse-server
target: /etc/clickhouse-server/config.d/knotel.xml
- source: clickhouse-users
target: /etc/clickhouse-server/users.d/knotel.xml
ulimits:
nofile:
soft: 262144
hard: 262144
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8123/ping"]
interval: 5s
timeout: 3s
retries: 30
configs:
# Keeps ClickHouse from logging every query and part into its own system
# tables, which on a small server grow faster than the data does.
clickhouse-server:
content: |
<clickhouse>
<logger><level>warning</level><console>true</console></logger>
<query_log remove="remove"/>
<query_thread_log remove="remove"/>
<query_views_log remove="remove"/>
<part_log remove="remove"/>
<trace_log remove="remove"/>
<text_log remove="remove"/>
<metric_log remove="remove"/>
<asynchronous_metric_log remove="remove"/>
<session_log remove="remove"/>
<processors_profile_log remove="remove"/>
<opentelemetry_span_log remove="remove"/>
<error_log remove="remove"/>
<latency_log remove="remove"/>
<blob_storage_log remove="remove"/>
<background_schedule_pool_log remove="remove"/>
</clickhouse>
clickhouse-users:
content: |
<clickhouse>
<profiles><default>
<log_queries>0</log_queries>
<log_query_threads>0</log_query_threads>
</default></profiles>
</clickhouse>
volumes:
knotel-data:
clickhouse-data:Each container keeps its data in a named volume: knotel-data holds the SQLite file with accounts, projects and keys, clickhouse-data holds the telemetry. ClickHouse's port isn't published; only the knotel container reaches it.
2. Set the secrets
Next to it, create .env. Only the first two values are required:
# Required BETTER_AUTH_SECRET= # openssl rand -base64 32 CLICKHOUSE_PASSWORD= # openssl rand -hex 24 # Optional # KNOTEL_TAG=sha-c181485 # pin a build; see "Tags" below # SETUP_TOKEN=a-code-only-you-know # else a one-time code is logged # PUBLIC_URL=https://knotel.example.com # HOST_PORT=3000 # GRPC_HOST_PORT=4317
printf 'BETTER_AUTH_SECRET=%s\nCLICKHOUSE_PASSWORD=%s\n' \ "$(openssl rand -base64 32)" "$(openssl rand -hex 24)" > .env
BETTER_AUTH_SECRET signs sign-in sessions; changing it later signs everyone out. CLICKHOUSE_PASSWORD is read by both containers. Every other setting is optional; the full list is in Configuration, and any of them can go in the same .env.
3. Start it
docker compose up -d docker compose logs knotel # the one-time setup code, if SETUP_TOKEN isn't set
The first start pulls both images, waits for ClickHouse to report healthy, then runs the migrations for both stores. Open http://localhost:3000/setup (or the machine's address), enter the setup code and create the owner account. From there, Getting started walks through creating a project and sending the first trace.
Day to day
| Command | What it does |
|---|---|
docker compose ps | State and health of both containers |
docker compose logs -f knotel | Follow the app's log |
docker compose restart knotel | Restart the app, e.g. after editing .env |
docker compose down | Stop and remove the containers. The volumes, and so the data, stay |
docker compose down -v | Also delete the volumes: every account, project and span |
.env when they are created, so after changing it run docker compose up -d, which recreates whatever changed.restart alone keeps the old values.Plain Docker
knotel and ClickHouse
The same stack without Compose: a network for the two containers to find each other by name, a volume each, and the same .env as above. ClickHouse is started without the settings file, which is fine for trying knotel out; for a long-lived instance, use Compose or mount the two XML files from the Compose file above into /etc/clickhouse-server/config.d/ and /etc/clickhouse-server/users.d/.
set -a; . ./.env; set +a # read CLICKHOUSE_PASSWORD from .env docker network create knotel docker run -d --name knotel-clickhouse --network knotel --restart unless-stopped \ --ulimit nofile=262144:262144 \ -e CLICKHOUSE_USER=knotel -e CLICKHOUSE_PASSWORD="$CLICKHOUSE_PASSWORD" \ -e CLICKHOUSE_DB=knotel -e CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 \ -v knotel-clickhouse:/var/lib/clickhouse \ clickhouse/clickhouse-server:26.8 docker run -d --name knotel --network knotel --restart unless-stopped \ -p 3000:3000 -p 4317:4317 \ --env-file .env \ -e CLICKHOUSE_URL=http://knotel-clickhouse:8123 \ -e CLICKHOUSE_USER=knotel -e CLICKHOUSE_DB=knotel \ -v knotel-data:/data \ ghcr.io/kien-ngo/knotel:latest docker logs knotel # the one-time setup code
Until ClickHouse is up, requests to knotel fail; the first one after that runs the migrations, so give it a few seconds. With Compose, the health check does that waiting for you.
The app alone, with your own ClickHouse
Point CLICKHOUSE_URL, CLICKHOUSE_USER and CLICKHOUSE_PASSWORD at your server (in .env or as -e flags) and run only the app:
docker run -d --name knotel --restart unless-stopped \ -p 3000:3000 -p 4317:4317 \ --env-file .env \ -v knotel-data:/data \ ghcr.io/kien-ngo/knotel:latest
knotel creates CLICKHOUSE_DB (knotel by default) if it doesn't exist, then its tables, so the user needs to be allowed to create that database, and to insert, select, alter and delete in it. ClickHouse Cloud works the same way, with its https://…:8443 address as CLICKHOUSE_URL.
knotel.db: accounts, projects, ingest keys and alerts. Run the container without it and every restart is a new, unclaimed instance, with keys your senders no longer match.Tags
| Tag | What it is |
|---|---|
latest | The current build. Moves on its own |
main | The newest commit on main. Moves on every push |
sha-<commit> | One build, forever. What to pin a production instance to |
Pin a sha- tag for anything you depend on, so an instance only changes when you change it. In the Compose file above, that's KNOTEL_TAG=sha-<commit> in .env. The running build is at /api/version, and the changelog lists each build with the commit it reports.
Upgrading
# Compose: set a new KNOTEL_TAG in .env if you pin one, then docker compose pull knotel && docker compose up -d # Plain Docker docker pull ghcr.io/kien-ngo/knotel:latest docker rm -f knotel && docker run -d --name knotel … # the same flags as before
Migrations run when the new container starts, once each; there is no separate step. They don't run backwards, so before rolling back across a release that carries one, read Upgrading. Back up first: Backups covers both stores.
Next steps
- Put it behind HTTPS: reverse proxy or tunnel. Senders on other machines and passkey sign-in both need a real address.
- Claim the instance and send your first trace.
- Retention and the storage limit decide how much disk ClickHouse uses.