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).
| What | Value |
|---|---|
| Current version | v0.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 |
| Ports | 8090 (hub web page), 45876 (agent, only if the hub connects to it) |
| Hub image size | about 12.8 MB compressed (Docker Hub, amd64) |
| Official docs | beszel.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.
Two simple rules to remember:
- The hub runs once, in Docker.
- 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.
| System | Hub | Agent |
|---|---|---|
| Linux | Docker Engine + Compose plugin | Docker container (host network), or the binary install script |
| Windows | Docker Desktop (WSL2 backend) | Native agent via a PowerShell script (Scoop or WinGet, runs as a service) |
| Mac | Docker Desktop | Native agent via Homebrew |
Check your tools:
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.
mkdir -p ~/docker_volumes/beszel/{beszel_data,beszel_socket,beszel_agent_data}
cd ~/docker_volumes/beszel
nano docker-compose.yml
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:
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.
$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:
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
docker compose up -d
Mac (Terminal)#
Same hub-only file as Windows:
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#
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.
- Open
http://localhost:8090. - Create the admin account (email and a strong password).
- Click Add System.
- Give the machine a name.
- 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:
/beszel_socket/beszel.sock
Then paste the real key and token into docker-compose.yml and restart:
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:
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):
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.
& 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)#
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.
- In the hub open Settings > Notifications.
- Add a notification URL. Beszel uses Shoutrrr URLs. A few examples (see the notifications page for all of them):
| Service | URL format |
|---|---|
| Discord | discord://<token>@<webhook-id> |
| ntfy | ntfy://:<accesstoken>@<host>/<topic> |
| Telegram | telegram://<token>@telegram?chats=<chat-id> |
| Slack | slack://<token-a>/<token-b>/<token-c> |
| Generic webhook | generic://example.com?template=json |
- 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 want | Do this |
|---|---|
| Per-container CPU, memory, network | Mount /var/run/docker.sock:/var/run/docker.sock:ro into the agent (already in the files above) |
| Container memory shows zero | Enable cgroup memory accounting on the host |
| NVIDIA GPU | Use 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 GPU | Add pid: host to the agent for utilization |
| AMD GPU | Set GPU_COLLECTOR=amd_sysfs |
| Apple Silicon | Experimental: install macmon and set GPU_COLLECTOR=macmon |
| Turn GPU stats off | Set SKIP_GPU=true |
NVIDIA example, added to the agent service:
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".
| Beszel | Uptime Kuma | Grafana + Prometheus | |
|---|---|---|---|
| Main job | Host and container metrics with history | Up/down checks | Dashboards over any data |
| Setup | Hub + one agent per machine | One container | Several parts to wire up |
| Container stats | Yes, built in | No | Needs exporters |
| Windows / Mac host | Native agent | Not applicable | Needs exporters |
| Alerts | Built in | Built in | Built 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.
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):
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.
| Case | Result | You do |
|---|---|---|
| Public domain | Automatic Let's Encrypt | DNS A record to the server, ports 80 and 443 open |
| Home only | tls internal (local certificate) | Trust Caddy's root certificate on each device |
Caddyfile for a public domain:
beszel.example.com {
reverse_proxy localhost:8090
}
Caddyfile for home only (point the name at the Caddy machine):
beszel.home.arpa {
tls internal
reverse_proxy localhost:8090
}
Trust the root certificate (pki/authorities/local/root.crt in Caddy's data directory):
# 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):
# 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):
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_FILEandTOKEN_FILEinstead of plainKEYandTOKEN. Put them in a.envor secret file and lock it:
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_FILEon the agent. - Update promptly. v0.21.1 fixed a record-ownership flaw and a universal-token leak in request URLs.
Tests:
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#
| Problem | Solution |
|---|---|
| System stays red | Open 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 agent | Use the unix socket /beszel_socket/beszel.sock as Host / IP |
| Live stats stop or changes do not save behind a proxy | Disable gzip for text/event-stream and allow WebSockets |
| Wrong root disk numbers | Set FILESYSTEM on the agent. Find the value with lsblk or df -h |
| Container charts are empty or show 0 memory | Enable cgroup memory accounting; check docker stats |
| Gaps in week or month charts | The agent must run without long pauses (over about a minute) |
| Rootless Docker or Podman: no sensors, GPU, SMART | Use the binary agent as a systemd service |
| Authelia keeps asking to log in | On Authelia 4.39.15+ add authn_strategies: CookieSession |
docker exec beszel /bin/sh fails | The images have no shell by design |
Exact error: certificate#
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#
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):
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:
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:
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:
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:
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#
- Caddy docs: automatic HTTPS, tls internal, reverse_proxy, install (Caddy 2.11.7, read 2026-10-26)
- Getting Started, Agent Installation, Notifications, GPU Monitoring, Common Issues (docs version 0.21.1, read 2026-10-26)
- GitHub releases, v0.21.1 (22 Oct 2026) and issues
- Docker Hub: henrygd/beszel and henrygd/beszel-agent
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.