Coming from
Beszel
Install kogut agents alongside Beszel, replace its network monitors and alerts, and recreate its setup from data.db.
kogut does not import Beszel’s settings or history. Install the agent alongside Beszel’s and compare the data before switching. To rebuild systems, alerts, channels, and network monitors from Beszel’s database, see Recreate the configuration from data.db.
Connect each server
- Open Hosts → Add a server in kogut and use the system’s name from Beszel.
- Run the generated install command on that server, or see Installing the agent.
- Check the host’s metrics and containers in kogut.
The agent needs no inbound port.
Run both side by side
Both agents can run on one host. Beszel’s agent listens on port 45876 for the hub, or
connects out to HUB_URL. kogut’s agent only connects out. Both use host networking and
read the Docker socket read-only:
services:
beszel-agent:
image: henrygd/beszel-agent
restart: unless-stopped
network_mode: host
volumes:
- ./beszel_agent_data:/var/lib/beszel-agent
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
LISTEN: 45876
KEY: "<public key>"
HUB_URL: "<hub url>"
TOKEN: "<token>"
kogut-agent:
image: ghcr.io/traceworks-ai/kogut:0.1
command: ["agent"]
restart: unless-stopped
network_mode: host
pid: host
group_add:
- "${DOCKER_GID:?set DOCKER_GID to the group id of /var/run/docker.sock}"
environment:
- KOGUT_SERVER=https://kogut.acme.com
- KOGUT_TOKEN=${KOGUT_TOKEN}
- KOGUT_HOST_ROOT=/host
- KOGUT_STATE_DIR=/data
volumes:
- agent-state:/data
- /:/host:ro,rslave
- /var/run/docker.sock:/var/run/docker.sock:ro
volumes:
agent-state:Keep the beszel-agent service as Beszel generated it. Put the host token in .env as
KOGUT_TOKEN, then start both with
DOCKER_GID=$(stat -c %g /var/run/docker.sock) docker compose up -d. kogut’s agent reads
every filesystem through /host, so it needs no /extra-filesystems mounts.
The agent in Docker explains each setting of kogut-agent, and
how to use a socket proxy or Podman.
Link hosts to services
Add the host to each service that runs on it:
[[service]]
name = "api"
hosts = ["vps-1"]A host named in the file is created if needed; connect its agent from the panel. See
[[service]].
Compare the metrics
The agent samples every 15 s. Values can differ from Beszel’s where the definitions differ:
| Beszel | kogut |
|---|---|
| System | Host, with one agent each. |
| CPU | Percent of all cores in use, excluding idle, I/O wait, and steal time. Beszel counts steal time as busy, so a virtual machine with steal reads lower in kogut. kogut has no I/O wait or steal chart. |
| Memory | MemTotal - MemAvailable from /proc/meminfo. Kernels before 3.14 use total minus free, buffers, and cache. |
| Swap | SwapTotal - SwapFree. A host without swap has no swap chart. |
| Disk usage | Each disk-backed filesystem once, as df shows it: used space over used plus available. Network filesystems, tmpfs, and overlay are left out. The chart and the alert use the fullest one. |
| Disk I/O | Bytes read and written per second, summed over physical disks. Partitions, RAID arrays, and device-mapper volumes are not counted twice. |
| Bandwidth | Bytes received and sent per second, summed over network cards. Bridges, bonds, VLANs, tunnels, and container links are left out. |
| Load average | 1, 5, and 15 min, on a chart without an alert. |
| Temperatures | hwmon sensors. The chart shows the hottest one. |
| Containers | State, health, restarts, exit code, image, CPU as a percent of one CPU, and memory without inactive file cache. No network per container. |
| systemd services | State, restarts, CPU, and memory, when the agent is installed on the host. A failed unit is a likely cause, not an alert. |
| Alert thresholds per system | [[host]] or Alerts on the host page. CPU, memory, swap, and temperature must stay above their limits for 5 min. A full disk alerts at once. Beszel’s minutes per alert have no equivalent. |
| GPU, battery, S.M.A.R.T. | No equivalent. |
Replace network monitors
Each Beszel network monitor becomes one [[check]] that runs from the agent of the same
system. Set from to the host’s name in kogut, and list that host in a service’s
hosts:
| Beszel monitor | [[check]] key |
|---|---|
| ICMP | ping = "<target>" |
| TCP | tcp = "<target>:<port>" |
| HTTP | url = "<target>" |
| DNS | dns = "<target>" |
Beszel’s interval becomes every, which is at least "10s":
[[service]]
name = "api"
hosts = ["vps-1"]
[[check]]
name = "Gateway"
service = "api"
ping = "10.0.0.1"
from = "vps-1"
[[check]]
name = "Postgres port"
service = "api"
tcp = "10.0.0.5:5432"
from = "vps-1"
[[check]]
name = "API health"
service = "api"
url = "http://10.0.0.6:8080/health"
from = "vps-1"
every = "30s"
[[check]]
name = "Internal DNS"
service = "api"
dns = "db.internal"
from = "vps-1"A monitor that Beszel added to several systems is a copy on each of them. In kogut, write
one check per probing host, such as Postgres port from vps-2, because each check runs
from one place. See Choose where to probe.
For a public target, leave out from and the server runs the check.
DNS monitors that ask a specific server and HTTP monitors that skip certificate
verification have no equivalent. DNS checks ask the servers in /etc/resolv.conf of the
host that runs them, and HTTPS checks validate certificates against kogut’s own root
store. See DNS checks.
Check more than the status
Beszel’s HTTP monitors send GET and count an error or a status of 400 or higher as
loss. kogut requires a 2xx status by default, after up to 5 redirects for GET and
HEAD. An endpoint that answers with a redirect kogut does not follow, such as one from
a POST check, needs { status = "3xx" }.
HTTP checks can also set the method, request headers, and assertions:
[[check]]
name = "Orders API"
service = "api"
url = "http://10.0.0.6:8080/v1/orders?limit=1"
from = "vps-1"
method = "GET"
headers = { Authorization = "Bearer ${ORDERS_API_TOKEN}" }
expect = [
{ status = 200 },
{ json = "$.db", equals = "ok" },
{ body_not_contains = "maintenance" },
{ response_time_below = "500ms", else = "degraded" },
]${ORDERS_API_TOKEN} comes from the server’s environment. For Basic authentication, see
Request headers. Assertions
explains how they combine, and expect lists them
all.
Alerts from checks
Beszel alerts when a monitor’s packet loss over the last hour passes a threshold. kogut has no loss threshold. A check becomes down after two failed runs in a row, and recovers after two successes and 3 min. A ping check with some replies missing is degraded. See How alerts work.
When an agent goes silent, its checks become unknown, not down, and the host opens an incident of its own. See Handle an offline agent.
Replace alerts and notifications
Beszel’s per-system CPU, memory, disk, and temperature thresholds map to a
[[host]] table, or to Alerts on the host page
for hosts added in the panel. CPU, memory, and temperature must stay above their limits
for 5 min. Bandwidth and load average appear on charts without alerts. A container of a
service that restarts in a loop, exits with an error, or stays unhealthy raises an
alert, and its restarts and OOM kills become
likely causes.
Add channels under Settings → Notifications or in [[alert]] tables, with file
secrets in ${NAME} variables. Convert Shoutrrr URLs to channel settings:
| Beszel | kogut |
|---|---|
discord://<token>@<id> | type = "discord", url = "https://discord.com/api/webhooks/<id>/<token>" |
telegram://<token>@telegram?chats=<chat> | type = "telegram", bot_token from <token>, chat_id from chats |
See Notification channels and How alerts work.
Recreate the configuration from data.db
Beszel keeps its systems, alerts, notification settings, and network monitors in
beszel_data/data.db, a SQLite database. Read a copy, not the file the hub is writing.
-
Stop the hub, copy its data directory, and start the hub again. The copy includes
data.db-walwhen it exists, which holds the latest changes.docker compose stop beszel cp -a beszel_data beszel-copy docker compose start beszel -
Export the configuration from the copy. These queries match Beszel 0.20 and 0.21:
db=beszel-copy/data.db sqlite3 -readonly -json "$db" \ "SELECT name, host, port FROM systems ORDER BY name" > systems.json sqlite3 -readonly -json "$db" \ "SELECT s.name AS system, a.name AS alert, a.value, a.min, u.email AS user FROM alerts a JOIN systems s ON s.id = a.system LEFT JOIN users u ON u.id = a.user ORDER BY s.name, a.name, u.email" > alerts.json sqlite3 -readonly -json "$db" \ "SELECT u.email AS user, json_extract(us.settings, '$.emails') AS emails, json_extract(us.settings, '$.webhooks') AS webhooks FROM user_settings us JOIN users u ON u.id = us.user" > notifications.json sqlite3 -readonly -json "$db" \ "SELECT s.name AS system, m.protocol, m.target, m.port, m.server, m.interval, m.enabled FROM network_monitors m JOIN systems s ON s.id = m.system ORDER BY s.name, m.target" > monitors.jsonalerts.jsonlists each alert’s name, such asCPUorDisk, its threshold invalue, inminthe minutes it must last, and the user who set it. Each Beszel user sets their own alerts, so a system can have one row per user for the same alert.monitors.jsonhas the DNS server of each monitor inserver. If sqlite3 reportsno such column: m.server, your Beszel version has no server setting: removem.server,from the last query.notifications.jsonholds Shoutrrr URLs with their tokens, so keep it private and delete it afterwards. -
Create a
writetoken under Settings → Tokens, or withPOST /api/v1/tokensand{"scope": "write", "label": "beszel"}from an admin’s session.
To have an assistant create the hosts, checks, and channels over the HTTP API, give it the four files and this prompt:
Recreate my Beszel setup in kogut at https://kogut.acme.com over its HTTP API,
documented at https://docs.kogut.dev/reference/http-api/. Send the header
"Authorization: Bearer $KOGUT_TOKEN" with every request. systems.json, alerts.json,
notifications.json, and monitors.json come from Beszel's data.db. Read kogut.toml and
GET /api/v1/hosts and /api/v1/settings first, and show me the plan before changing
anything.
1. Hosts: POST /api/v1/hosts with {"name": <system name>} for each system kogut does
not have. Ask me before posting an existing host, because that replaces its token.
Give me each install command it returns.
2. Thresholds: for each host added in the panel, PUT /api/v1/hosts/{name}/alerts with
{cpu, memory, swap, disk, temperature, ignoreMounts}. Start from 90, 90, 50, 90,
null, and [], and take whole-number values from the CPU, Memory, Disk, and
Temperature alerts in alerts.json. When users set different values for the same
system and alert, ask me which to use. List the other alerts as not carried over.
Hosts in kogut.toml get a [[host]] table instead.
3. Checks: for each enabled monitor, POST /api/v1/checks with kind "ping" for icmp,
"tcp" with target "<target>:<port>", "http", or "dns"; "from" set to the system's
host; "everyMs" set to interval × 1000, at least 10000; and "expect": []. Leave out
DNS monitors with a server set, because kogut asks the host's own resolvers. Use
one service per system, named after it, unless I name others. Name each check after
its target and host. Then link each host to its service with
POST /api/v1/discovered/monitor and {"host", "service", "check": null}.
4. Channels: POST /api/v1/channels for each address in emails and each URL in
webhooks. Convert discord:// and telegram:// URLs with the table at
https://docs.kogut.dev/coming-from/beszel/#replace-alerts-and-notifications. Ask me
for SMTP settings and about any other scheme. Name channels such as ops-email, never
a bare type such as email or discord-2.
5. Keep panel and file objects apart. Never reuse a name from kogut.toml. Checks that
need request headers or Basic authentication, and checks of services defined in
kogut.toml, go into kogut.toml as [[check]] tables with secrets as ${NAME},
because the API cannot set headers.
Finish with a list of what you created and what you left out.Remove Beszel
People sign in to kogut with a password, or through OpenID Connect or forward auth. Every account sees every host; see Roles.
Keep Beszel if you need GPU metrics, per-user systems, alerts on bandwidth or load, or alerts on a network monitor’s hourly packet loss. Otherwise, check all hosts, the checks that replace network monitors, and channels in kogut, stop Beszel’s agents, then stop its hub. kogut’s history starts when its agents connect.