Self-HostingDocker

Beszel with Docker: Simple Server Monitoring with History and Alerts (Linux, Windows, Mac)

Before you start

  • 14 min read

Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

What you get in one minute#

Beszel is a small, open-source monitoring tool: one hub shows CPU, memory, disk, network and Docker container stats, with history and alerts, for every machine that runs its tiny agent.

It is the "I just want to know if my servers are healthy" tool. No Prometheus, no exporters, no dashboards to build. You start one hub container, copy a key into an agent on each machine, and the charts appear.

At a glance#

  • What you get: a dashboard with live and historical charts for each machine, per-container stats, optional GPU stats and alerts to your phone or chat.
  • Time needed: about 15 minutes for the hub and your first machine, 5 more minutes per extra machine.
  • Difficulty: easy. You copy a few commands and paste one key and one token.
  • You need: Docker with the Compose plugin on the hub machine, a browser, and a terminal (Linux/Mac) or PowerShell (Windows).
WhatValue
Current versionv0.21.1, released 22 Oct 2026 (GitHub releases)
Pinned images (official, Docker Hub)henrygd/beszel:0.21.1 and henrygd/beszel-agent:0.21.1
Ports8090 (hub web page), 45876 (agent, only if the hub connects to it)
Hub image sizeabout 12.8 MB compressed (Docker Hub, amd64)
Official docsbeszel.dev

Why this version? We checked the issue tracker and Reddit for reports about v0.21.1. We found no install failures and no data-loss reports, only a few narrow open bugs (listed under Known bugs). So this guide uses the latest release. The previous release is 0.21.0, your rollback tag.

How it fits together#

Each agent sends its stats to the hub over a WebSocket, the hub saves the history in its data folder, shows it on the dashboard and sends alerts through the channel you set up.

How Beszel works: agents on a Linux server (Docker), a Windows PC and a Mac send stats to the Beszel hub on port 8090; the hub saves the history in a data folder, shows charts in the web dashboard and can optionally send alert messages

Two simple rules to remember:

  1. The hub runs once, in Docker.
  2. An agent runs on every machine you want to watch. Use Docker for Linux servers. Use the native agent on Windows and Mac so you see the real computer, not the Docker Desktop virtual machine.

Before you start#

You need Docker on the machine that runs the hub, and a way to open a web page on port 8090.

SystemHubAgent
LinuxDocker Engine + Compose pluginDocker container (host network), or the binary install script
WindowsDocker Desktop (WSL2 backend)Native agent via a PowerShell script (Scoop or WinGet, runs as a service)
MacDocker DesktopNative agent via Homebrew

Check your tools:

Run in terminalbash
docker --version
docker compose version

Checklist:

  • [ ] Port 8090 is free on the hub machine
  • [ ] You backed up any config you are about to replace
  • [ ] The agent machines can reach the hub's address on port 8090

Tested on 2026-10-26: not run. Commands checked against the official Beszel docs (docs version 0.21.1).

Step 1: Start the hub#

You create a folder, save one Compose file and start it. Data lives in ~/docker_volumes/beszel.

Linux#

This file starts the hub and an agent for the same machine. They talk through a unix socket, because localhost does not work between two containers.

Run in terminalbash
mkdir -p ~/docker_volumes/beszel/{beszel_data,beszel_socket,beszel_agent_data}
cd ~/docker_volumes/beszel
nano docker-compose.yml
File contentyaml
services:
  beszel:
    image: henrygd/beszel:0.21.1
    container_name: beszel
    restart: unless-stopped
    environment:
      APP_URL: http://localhost:8090
    ports:
      - 8090:8090
    volumes:
      - ./beszel_data:/beszel_data
      - ./beszel_socket:/beszel_socket

  beszel-agent:
    image: henrygd/beszel-agent:0.21.1
    container_name: beszel-agent
    restart: unless-stopped
    network_mode: host
    volumes:
      - ./beszel_agent_data:/var/lib/beszel-agent
      - ./beszel_socket:/beszel_socket
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      LISTEN: /beszel_socket/beszel.sock
      HUB_URL: http://localhost:8090
      TOKEN: "<token>"
      KEY: "<key>"

Change APP_URL to the address you will really use. Then start it:

Run in terminalbash
docker compose up -d

The agent will not connect yet. It needs the real key and token, which you get in the first-run section.

Windows (PowerShell)#

On Windows the hub runs in Docker Desktop and the agent runs natively (Step 2). So the file has the hub only.

Codepowershell
$d = "$HOME\docker_volumes\beszel"
New-Item -ItemType Directory -Force "$d\beszel_data" | Out-Null
Set-Location $d
notepad docker-compose.yml

Paste this, save and start:

File contentyaml
services:
  beszel:
    image: henrygd/beszel:0.21.1
    container_name: beszel
    restart: unless-stopped
    environment:
      APP_URL: http://localhost:8090
    ports:
      - 8090:8090
    volumes:
      - ./beszel_data:/beszel_data
Run in terminalpowershell
docker compose up -d

Mac (Terminal)#

Same hub-only file as Windows:

Run in terminalbash
mkdir -p ~/docker_volumes/beszel/beszel_data && cd ~/docker_volumes/beszel
nano docker-compose.yml   # paste the hub-only file from the Windows tab, save
docker compose up -d

Check it worked#

Run in terminalbash
docker compose ps

Expected: the beszel container is running (Linux: the agent is too, but it keeps retrying until the first-run step below). Then open http://localhost:8090. You should see a page asking you to create the first account.

First run: create your admin account and add a system#

The first account you create is the admin. Then the hub gives you the key and token for each machine.

  1. Open http://localhost:8090.
  2. Create the admin account (email and a strong password).
  3. Click Add System.
  4. Give the machine a name.
  5. Copy the public key and the token shown on the screen. The page can also give you a ready-made Compose snippet or install command with both already filled in.

For the same-machine Linux agent from Step 1, set Host / IP to the socket path:

Codetext
/beszel_socket/beszel.sock

Then paste the real key and token into docker-compose.yml and restart:

Run in terminalbash
docker compose up -d

Check it worked#

Wait a minute. The system row turns green and charts start to fill in. Red means it cannot connect: see Troubleshooting.

Step 2: Add more machines#

Run one agent per machine you want to watch. Use Add System in the hub each time: it shows the exact command with your key and token inside.

Prefer not to pre-register each machine? Create a universal token at /settings/tokens. An agent started with it can register itself.

Linux#

Option A, Docker agent. On the remote machine, save this as docker-compose.yml and run docker compose up -d:

File contentyaml
services:
  beszel-agent:
    image: henrygd/beszel-agent:0.21.1
    container_name: 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: "<YOUR_KEY>"
      HUB_URL: "<your-hub-url>"
      TOKEN: "<YOUR_TOKEN>"

Why host network mode? The agent needs it to read the machine's real network stats.

Option B, no Docker: the install script (needs root to create a beszel user and a service; the agent itself does not run as root):

Run in terminalbash
curl -sL https://get.beszel.dev -o /tmp/install-agent.sh && chmod +x /tmp/install-agent.sh && /tmp/install-agent.sh

Windows (PowerShell)#

The script installs the agent with Scoop or WinGet and creates a service with NSSM, so it starts after a reboot. It asks for the key interactively.

Codepowershell
& iwr -useb https://get.beszel.dev -OutFile "$env:TEMP\install-agent.ps1"; & Powershell -ExecutionPolicy Bypass -File "$env:TEMP\install-agent.ps1"

Logs are in C:\ProgramData\beszel-agent\logs. Change settings with nssm edit beszel-agent, then nssm restart beszel-agent.

Mac (Terminal)#

Run in terminalbash
curl -sL https://get.beszel.dev/brew -o /tmp/install-agent.sh && chmod +x /tmp/install-agent.sh && /tmp/install-agent.sh

Settings: ~/.config/beszel/beszel-agent.env. Log: ~/.cache/beszel/beszel-agent.log. After an upgrade macOS may ask again for the Local Network permission (open issue #2686). Allow it again.

Check it worked#

The new machine shows up green in the hub. Click it to see CPU, memory, disk and network charts.

Turn on alerts#

Add a notification channel once, then switch alerts on per machine.

  1. In the hub open Settings > Notifications.
  2. Add a notification URL. Beszel uses Shoutrrr URLs. A few examples (see the notifications page for all of them):
ServiceURL format
Discorddiscord://<token>@<webhook-id>
ntfyntfy://:<accesstoken>@<host>/<topic>
Telegramtelegram://<token>@telegram?chats=<chat-id>
Slackslack://<token-a>/<token-b>/<token-c>
Generic webhookgeneric://example.com?template=json
  1. In the systems table click the bell icon of a machine and pick what to watch. Alerts cover CPU, memory, disk, bandwidth, temperature, load average and system up/down status. Newer releases add container health, failed systemd services, CPU I/O wait and steal time and TLS certificate expiry.

Check it worked#

Use the test option next to your notification URL (VERIFY the exact button label in your version). A message should arrive in your chat or phone.

Docker container stats and GPU stats#

Container stats work when the agent can read the Docker socket; GPU stats work when the agent can see the card.

You wantDo this
Per-container CPU, memory, networkMount /var/run/docker.sock:/var/run/docker.sock:ro into the agent (already in the files above)
Container memory shows zeroEnable cgroup memory accounting on the host
NVIDIA GPUUse henrygd/beszel-agent-nvidia, install the NVIDIA Container Toolkit, add the deploy block below. Set GPU_COLLECTOR=nvml (experimental) so the card can still idle
Intel GPUAdd pid: host to the agent for utilization
AMD GPUSet GPU_COLLECTOR=amd_sysfs
Apple SiliconExperimental: install macmon and set GPU_COLLECTOR=macmon
Turn GPU stats offSet SKIP_GPU=true

NVIDIA example, added to the agent service:

File contentyaml
    image: henrygd/beszel-agent-nvidia:0.21.1
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities:
                - utility

How Beszel compares#

Beszel answers "how is my machine doing, and how was it last week?". Uptime Kuma answers "is my service up?". Grafana answers "show me anything".

BeszelUptime KumaGrafana + Prometheus
Main jobHost and container metrics with historyUp/down checksDashboards over any data
SetupHub + one agent per machineOne containerSeveral parts to wire up
Container statsYes, built inNoNeeds exporters
Windows / Mac hostNative agentNot applicableNeeds exporters
AlertsBuilt inBuilt inBuilt in (Alertmanager / Grafana)

The Beszel column comes from the official docs. The other two columns are our general summary, not official claims. They are great tools and work well next to Beszel. We have an older Uptime Kuma guide too.

Put it behind HTTPS (with or without Traefik)#

With Traefik#

This is for a server with a domain name and Traefik already running. Running only at home on localhost? Skip this section.

Prerequisites: a domain, DNS pointing to the server, ports 80 and 443 open, and Traefik on the external proxy network.

Run in terminalbash
docker network create proxy

Change the hub service like this. The labels are our example, not from the official docs (VERIFY against your Traefik setup):

File contentyaml
  beszel:
    image: henrygd/beszel:0.21.1
    container_name: beszel
    restart: unless-stopped
    environment:
      APP_URL: https://<your-domain>
    volumes:
      - ./beszel_data:/beszel_data
      - ./beszel_socket:/beszel_socket
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      - traefik.http.routers.beszel.rule=Host(`<your-domain>`)
      - traefik.http.routers.beszel.entrypoints=https
      - traefik.http.routers.beszel.tls=true
      - traefik.http.services.beszel.loadbalancer.server.port=8090

networks:
  proxy:
    external: true

Two proxy rules from the official docs: it must pass WebSockets, and it must not gzip text/event-stream, or live stats and saving break.

Without Traefik: Caddy#

Choose Caddy if you have no Traefik yet and run one small server.

CaseResultYou do
Public domainAutomatic Let's EncryptDNS A record to the server, ports 80 and 443 open
Home onlytls internal (local certificate)Trust Caddy's root certificate on each device

Caddyfile for a public domain:

File contenttext
beszel.example.com {
    reverse_proxy localhost:8090
}

Caddyfile for home only (point the name at the Caddy machine):

File contenttext
beszel.home.arpa {
    tls internal
    reverse_proxy localhost:8090
}

Trust the root certificate (pki/authorities/local/root.crt in Caddy's data directory):

Run in terminalbash
# Linux: on the Caddy machine
sudo caddy trust
# other Linux devices, with root.crt copied over
sudo cp root.crt /usr/local/share/ca-certificates/caddy-root.crt && sudo update-ca-certificates
# Windows (PowerShell as administrator)
certutil -addstore -f ROOT root.crt
# Mac
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain root.crt

Run Caddy (Linux: install from the official apt repository on caddyserver.com/docs/install first):

Run in terminalbash
# Linux
sudo systemctl reload caddy
# Windows (PowerShell)
winget install CaddyServer.Caddy
caddy run
# Mac
brew install caddy && caddy run

Or use Docker on the app's network. Add the proxy network to the beszel service, use reverse_proxy beszel:8090 in the Caddyfile and save this in ~/docker_volumes/caddy (create the network once: docker network create proxy):

File contentyaml
services:
  caddy:
    image: caddy:2.11.7
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy_data:/data
      - ./caddy_config:/config
    networks:
      - proxy
networks:
  proxy:
    external: true

Beszel needs WebSockets, which Caddy passes by default, and live stats break if text/event-stream is compressed: do not add an encode line for it. Change APP_URL in your Compose file to the https address and run docker compose up -d.

Check it worked: curl -I https://beszel.example.com answers HTTP/2 200 (or the login redirect).

Security and testing#

Keep the hub private, keep keys out of the Compose file and check the basics.

  • Do not forward port 8090 to the internet. Reach the hub through WireGuard or Tailscale instead.
  • Secrets in files. The agent can read KEY_FILE and TOKEN_FILE instead of plain KEY and TOKEN. Put them in a .env or secret file and lock it:
Run in terminalbash
chmod 600 .env
  • Docker socket is read-only (:ro) in every example.
  • Self-signed hub certificate? Since v0.19.0 agents verify HTTPS certificates. Set CA_CERT_FILE on the agent.
  • Update promptly. v0.21.1 fixed a record-ownership flaw and a universal-token leak in request URLs.

Tests:

Run in terminalbash
docker compose ps
docker logs beszel --tail 100
curl -I https://<your-domain>

Expected: containers running, no errors in the log, and an HTTP/2 200 (or a redirect to the login page) from the last command.

Troubleshooting#

ProblemSolution
System stays redOpen the PocketBase logs at /_/#/logs. Make sure /api/beszel/agent-connect is reachable from the agent. If a firewall sits between, test with telnet <agent-ip> 45876
localhost does not work for the local agentUse the unix socket /beszel_socket/beszel.sock as Host / IP
Live stats stop or changes do not save behind a proxyDisable gzip for text/event-stream and allow WebSockets
Wrong root disk numbersSet FILESYSTEM on the agent. Find the value with lsblk or df -h
Container charts are empty or show 0 memoryEnable cgroup memory accounting; check docker stats
Gaps in week or month chartsThe agent must run without long pauses (over about a minute)
Rootless Docker or Podman: no sensors, GPU, SMARTUse the binary agent as a systemd service
Authelia keeps asking to log inOn Authelia 4.39.15+ add authn_strategies: CookieSession
docker exec beszel /bin/sh failsThe images have no shell by design

Exact error: certificate#

File contenttext
tls: failed to verify certificate: x509: certificate signed by unknown authority

Mount your CA file read-only into /etc/ssl/certs/ of the container. For agent-to-hub HTTPS set CA_CERT_FILE.

Exact error: Podman socket#

Run in terminaltext
dial unix /run/podman/podman.sock: connect: permission denied

SELinux is blocking the socket. See the Podman section on beszel.dev.

Known bugs#

Open on GitHub at the time of writing (read 2026-10-26):

  • #2699: high agent CPU in one report, not yet reproduced. Watch agent CPU after you upgrade.
  • #2686: Mac Homebrew agent loses the Local Network permission after each upgrade. Allow it again.
  • #2683, #2682, #2655: AMD and NVIDIA GPU edge cases.
  • If 0.21.1 misbehaves for you, roll back by setting the tag to 0.21.0.

Updates and backups#

Update by changing the tag, back up by copying the data folder.

Update (Linux/Mac; on Windows use the same docker compose lines in PowerShell):

Run in terminalbash
cd ~/docker_volumes/beszel
# read the release notes, edit the image tags, then:
docker compose pull && docker compose up -d

Native agents: Windows with Scoop nssm stop beszel-agent; scoop update beszel-agent; nssm start beszel-agent. The binary agent updates itself with ./beszel-agent update.

Backup script (Linux/Mac). It stops the hub briefly so the database is consistent:

Run in terminalbash
mkdir -p ~/docker_volumes/beszel/backups
cd ~/docker_volumes/beszel
docker compose stop beszel
tar czf backups/beszel-$(date +%F).tgz beszel_data
docker compose start beszel

Schedule: Linux/Mac cron at 03:00 (0 3 * * * <path-to-script>). Windows: Task Scheduler running the same steps in PowerShell with Compress-Archive.

Restore test#

Do this once, before you need it:

Run in terminalbash
mkdir -p /tmp/restore && tar xzf ~/docker_volumes/beszel/backups/beszel-*.tgz -C /tmp/restore
docker run --rm -d --name beszel-restore -p 8091:8090 -v /tmp/restore/beszel_data:/beszel_data henrygd/beszel:0.21.1

Open http://localhost:8091, log in and check that your systems and charts are there. Then clean up:

Run in terminalbash
docker stop beszel-restore && rm -rf /tmp/restore

Performance and resources#

Beszel is built to be light. The hub image is about 12.8 MB compressed and the agent reports roughly once a minute. We did not measure RAM or CPU ourselves and the docs give no official numbers, so measure on your own machine:

Run in terminalbash
docker stats --no-stream beszel beszel-agent

Tips:

  • Native agents on Windows and Mac report the real host. Docker Desktop's virtual machine would show its own limits.
  • Keep the agent running without breaks, or charts get gaps.
  • Watch agent CPU after upgrades (issue #2699).

FAQ#

Can the hub and an agent run on the same machine? Yes. Use the unix socket shown in Step 1 and the first-run section.

Which image tag should I use? The pinned 0.21.1. Use 0.21.0 only to roll back. The official docs use latest; we pin so updates are your choice.

Why does the Linux agent use host network mode? To read the host's real network interface stats.

Does it work on Windows and Mac? Yes. The hub runs in Docker Desktop. The agents run natively: WinGet or Scoop with NSSM on Windows, Homebrew on Mac.

Can it monitor GPUs? Yes: NVIDIA, AMD and Intel, plus experimental Apple Silicon. See the GPU table above.

Sources#

Checked against the official docs on 2026-10-26. Not run by DIY Lab Hub. Items marked VERIFY are not confirmed in the official docs.