Skip to main content

Quickstart Guide

GitHub-logo.png logo.png

2025-02-01 23_19_45-Beszel _ Simple, lightweight server monitoring - Brave.png

Introduction

Architecture

Beszel consists of two main components:

  • Hub: A web application that provides a dashboard for viewing and managing connected systems. Built on PocketBase.
  • Agent: Runs on each system you want to monitor, creating a minimal SSH server to communicate system metrics to the hub.

Step 1: Create the Necessary Folders

If you have multiple nodes and a Docker Swarm environment, you can reference the GlusterFS guide for distributing folders across nodes.

mkdir -p /mnt/glustermount/data/beszel_data

Step 2: Docker Compose or Portainer for Initial Setup

You can either create a docker-compose.yaml file manually or use Portainer to set up Beszel. Below is an example configuration that should work out of the box for a standalone Docker setup.

IMPORTANT: After you add a new system in the Beszel web UI, you must update the KEY value with your public key (provided by the hub) and then restart the agent service. Also, use host.docker.internal as the Host/IP when prompted, instead of localhost or 127.0.0.1.

Docker Standalone Example

services:
  beszel:
    image: henrygd/beszel:latest
    container_name: beszel
    restart: unless-stopped
    extra_hosts:
      - host.docker.internal:host-gateway
    ports:
      - 8090:8090
    volumes:
      - /mnt/glustermount/data/beszel_data:/beszel_data

  beszel-agent:
    image: henrygd/beszel-agent:latest
    container_name: beszel-agent
    restart: unless-stopped
    network_mode: host
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      PORT: 45876
      # Do not remove quotes around the key
      KEY: 'UPDATE WITH YOUR PUBLIC KEY (copy from "Add system" dialog)'
Docker Swarm + Traefik Example
version: "3.7"

services:
  beszel:
    image: henrygd/beszel:latest
    container_name: beszel
    restart: unless-stopped
    networks:
      - management_net
    extra_hosts:
      - host.docker.internal:host-gateway
    ports:
      - 8090:8090
    volumes:
      - /mnt/glustermount/data/beszel_data:/beszel_data
    deploy:
      mode: replicated
      replicas: 1
      labels:
        - "traefik.enable=true"
        - "traefik.http.services.beszel-agent.loadbalancer.server.port=8090"

  beszel-agent:
    image: henrygd/beszel-agent:latest
    container_name: beszel-agent
    restart: unless-stopped
    network_mode: host
    depends_on:
      - beszel_beszel
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      PORT: 45876
      # Do not remove quotes around the key
      KEY: 'UPDATE WITH YOUR PUBLIC KEY (copy from "Add system" dialog)'
    deploy:
      mode: replicated
      replicas: 1
      labels:
        - "traefik.enable=true"
        - "traefik.http.services.beszel-agent.loadbalancer.server.port=45876"

networks:
  management_net:
    external: true

Why network_mode: host?
The agent must use host network mode to access network interface metrics, which automatically exposes the port. If you do not need network statistics, you can remove network_mode: host and map the port manually in the Compose file.

Step 3: Start the Containers

Once you've created your Compose file, you can deploy the services. The process differs slightly depending on your setup:

  • Docker Swarm:
    docker stack deploy -c docker-compose.yaml beszel
  • Docker Standalone:
    docker compose up -d
  • Portainer: Use the Portainer UI to import the docker-compose.yaml file and start the stack.

Step 4: Create an Admin User

Open http://localhost:8090 (or your chosen URL/port) in your browser and follow the prompts to create an admin user.

Step 5: Adding Systems / Nodes

When you add a new system from the Beszel hub's web UI, you'll be given a snippet for the beszel-agent configuration (Docker Compose or a binary install command). Copy the public key from the "Add system" dialog and place it in the KEY variable of your agent container or service.

If you plan to monitor multiple nodes with Docker Swarm, you can create separate agent services, each running on a different node with constraints to ensure proper placement:

beszel-agent1:
  image: henrygd/beszel-agent:latest
  container_name: beszel-agent1
  restart: unless-stopped
  network_mode: host
  depends_on:
    - beszel_beszel
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock:ro
  environment:
    PORT: 45876
    KEY: 'YOUR_PUBLIC_KEY_FROM_HUB'
  deploy:
    mode: replicated
    replicas: 1
    labels:
      - "traefik.enable=true"
      - "traefik.http.services.beszel-agent1.loadbalancer.server.port=45876"
    placement:
      constraints:
        - node.hostname == swarmpi1  # Only deploy on node 'swarmpi1'

beszel-agent2:
  image: henrygd/beszel-agent:latest
  container_name: beszel-agent2
  restart: unless-stopped
  network_mode: host
  depends_on:
    - beszel_beszel
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock:ro
  environment:
    PORT: 45876
    KEY: 'YOUR_PUBLIC_KEY_FROM_HUB'
  deploy:
    mode: replicated
    replicas: 1
    labels:
      - "traefik.enable=true"
      - "traefik.http.services.beszel-agent2.loadbalancer.server.port=45876"
    placement:
      constraints:
        - node.hostname == swarmpi2  # Only deploy on node 'swarmpi2'

Each agent service can then monitor the node on which it is running, allowing you to collect and consolidate metrics across your entire cluster.

 

 

start this wiki post with:
<h2 class="align-left"><a href="https://github.com/henrygd/beszel" target="_blank" rel="noopener"><img class="align-right" src="https://wiki.aeoneros.com/uploads/images/gallery/2025-01/scaled-1680-/814rhr5FohzOPEav-github-logo.png" alt="GitHub-logo.png" width="272" height="153"></a> <a href="https://hub.docker.com/r/henrygd/beszel-agent" target="_blank" rel="noopener"><img class="align-right" src="https://wiki.aeoneros.com/uploads/images/gallery/2024-09/scaled-1680-/rfuZed4CizAXYqTM-logo.png" alt="logo.png" width="187" height="167"></a></h2>
<h2 class="align-left"><a href="https://beszel.dev/" target="_blank" rel="noopener"><img src="https://wiki.aeoneros.com/uploads/images/gallery/2025-02/scaled-1680-/BLfnb6csoxA8Q8dk-2025-02-01-23-19-45-beszel-simple-lightweight-server-monitoring-brave.png" alt="2025-02-01 23_19_45-Beszel _ Simple, lightweight server monitoring - Brave.png" width="174" height="57"></a></h2>
<h2 id="bkmrk-live-demo" class="align-left">Introduction</h2>
<p id="bkmrk-beszel-summary"></p>

this is an formating example from a different setup guide step by step guide:
<p id="bkmrk-"><a href="https://wiki.aeoneros.com/ghcr.io/linkwarden/linkwarden:latest" target="_blank" rel="noopener"><img class="align-right" src="https://wiki.aeoneros.com/uploads/images/gallery/2024-09/scaled-1680-/rfuZed4CizAXYqTM-logo.png" alt="logo.png" width="163" height="145"></a></p>
<h2 id="bkmrk-introduction"><a href="https://github.com/linkwarden/linkwarden/" target="_blank" rel="noopener"><img class="align-right" src="https://wiki.aeoneros.com/uploads/images/gallery/2024-09/scaled-1680-/Ot77lQOoFS2b2cPK-github-logo.png" alt="GitHub-logo.png" width="231" height="130"></a>Introduction</h2>
<p id="bkmrk-in-this-guide%2C-we%27ll">In this guide, we'll walk through the process of installing Linkwarden, a self-hosted bookmark manager, on Docker Swarm. This installation will include setting up Traefik as the reverse proxy and configuring persistent storage using GlusterFS. Additionally, we'll cover how to securely set up environment variables, such as the NEXTAUTH_SECRET, and ensure proper file permissions using a custom Linux user.</p>
<p id="bkmrk-%C2%A0"></p>
<h4 id="bkmrk-step-1%3A-create-a-use">Step 1: Create a User for Linkwarden Folders</h4>
<p id="bkmrk-to-ensure-that-only-">To ensure that only one Linux user has the necessary rights for the Linkwarden folders, we'll create a custom user and group called <code>linkwardenuser</code>. This user will not have a home directory, and the shell will be set to <code>/usr/sbin/nologin</code> for security purposes.</p>
<ol id="bkmrk-create-the-custom-us">
<li>
<p>Create the custom user and group with the GID and UID set to 10002:<br></p>
<pre><code class="language-bash">sudo groupadd -g 10002 linkwardenuser
sudo useradd -u 10002 -g linkwardenuser -s /usr/sbin/nologin -M linkwardenuser</code></pre>
<ul>
<li>The <code>-s /usr/sbin/nologin</code> option ensures that this user cannot log in interactively, which is a security best practice for service users.</li>
<li>The <code>-M</code> option prevents the creation of a home directory, as this user will only be managing Linkwarden's folder permissions and not need a home directory for other purposes.</li>
</ul>
</li>
</ol>
<p id="bkmrk-%C2%A0-1"></p>
<h4 id="bkmrk-step-2%3A-create-folde">Step 2: Create Folders for Linkwarden</h4>
<p id="bkmrk-next%2C-we%E2%80%99ll-create-t">Next, we&rsquo;ll create the necessary folders to store Linkwarden&rsquo;s data on the GlusterFS mount.</p>
<ol id="bkmrk-create-the-folders-f">
<li>
<p>Create the folders for Linkwarden's data:<br></p>
<pre><code class="language-bash">mkdir -p /mnt/glustermount/data/linkwarden_data
mkdir -p /mnt/glustermount/data/linkwarden_data/pgdata
mkdir -p /mnt/glustermount/data/linkwarden_data/lwdata
mkdir -p /mnt/glustermount/data/linkwarden_data/storage</code></pre>
</li>
<li>Adjust ownership of these folders to the <code>linkwardenuser</code>, ensuring all files inside are accessible to this user:<br>This ensures that only the <code>linkwardenuser</code> has access to these folders and files, maintaining data security.<br>
<pre><code class="language-bash">sudo chown -R linkwardenuser:linkwardenuser /mnt/glustermount/data/linkwarden_data</code></pre>
<p><br></p>
</li>
<li><strong>Permissions</strong>: The following permissions ensure that the owner (user <code>10002</code>) has full access (read, write, execute) to the directories and files, and that no one else can modify the files.<br>
<pre><code class="language-bash">sudo chmod -R 750 /mnt/glustermount/data/linkwarden_data</code></pre>
<p class="callout info"><strong>750</strong> means:<br>- 7: Owner (user 10002) has read, write, and execute permissions.<br>- 5: Group has read and execute permissions (but not write).<br>- 0: Others have no permissions.<br></p>
<p data-pm-slice="0 0 []"><br></p>
<p><br></p>
</li>
</ol>
<h4 id="bkmrk-step-3%3A-create-docke">Step 3: Create <code>docker-compose.yaml</code></h4>
<p id="bkmrk-now%2C-we%E2%80%99ll-create-th">Now, we&rsquo;ll create the Docker Compose file that will define two services: one for Linkwarden and another for its PostgreSQL database. We will also configure Traefik to route traffic to Linkwarden.</p>
<p id="bkmrk-to-configure-the-com" class="callout warning">To Configure the Composefile check out the <a href="https://wiki.aeoneros.com/books/linkwarden/page/enviroment-variables-list">ENVIROMENT-VARIABLES</a> Wiki Article.</p>
<pre id="bkmrk-version%3A-%223.5%22-servi"><code class="language-bash">version: "3.5"

services:
  linkwarden:
    image: ghcr.io/linkwarden/linkwarden:latest
    environment:
      - DATABASE_URL=postgresql://linkwarden:${POSTGRES_PASSWORD}@postgres:5432/linkwardendb
      - NEXTAUTH_URL=http://localhost:3000/api/v1/auth
      - NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
      - STORAGE_FOLDER=/mnt/glustermount/data/linkwarden_data/storage
      - NEXT_PUBLIC_EMAIL_PROVIDER=${NEXT_PUBLIC_EMAIL_PROVIDER}
      - EMAIL_FROM=${EMAIL_FROM}
      - EMAIL_SERVER=${EMAIL_SERVER}
      - BASE_URL=${BASE_URL}
      - TZ=Europe/Zurich
      - GID=10002
      - UID=10002
    restart: always
    ports:
      - 3000:3000
    volumes:
      - /mnt/glustermount/data/linkwarden_data/lwdata:/data/data
    depends_on:
      - postgres
    networks:
     - management_net
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.role == manager
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.linkwarden.rule=Host(`linkwarden.domain.tld`)"
        - "traefik.http.services.linkwarden.loadbalancer.server.port=3000"
        - "traefik.http.routers.linkwarden.entrypoints=websecure"
        - "traefik.http.routers.linkwarden.tls.certresolver=leresolver"

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: linkwarden
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: linkwardendb
      TZ: Europe/Zurich
      GID: 10002
      UID: 10002
    restart: always
    volumes:
      - /mnt/glustermount/data/linkwarden_data/pgdata:/var/lib/postgresql/data
    ports:
      - 5432:5432
    networks:
     - management_net
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.role == manager
      labels:
        - "traefik.enable=true"
        - "traefik.http.services.postgres.loadbalancer.server.port=5432"

networks:
  management_net:
    external: true
</code></pre>
<details id="bkmrk-smtp-settings-%23-the-">
<summary>SMTP Settings</summary>
<ul>
<li class="null"># The base URL of your Linkwarden installation (replace with your domain or local IP)<br>BASE_URL=https://linkwarden.domain.tld</li>
<li class="null"># Email provider for sending notification emails (example: SMTP settings)<br>NEXT_PUBLIC_EMAIL_PROVIDER=smtp</li>
<li class="null"># Email address that Linkwarden will use to send emails from (replace with your actual email)<br>[email protected]</li>
<li class="null"># SMTP server details (example: for Gmail's SMTP server)<br>EMAIL_SERVER=smtp://smtp.gmail.com:587<br></li>
</ul>
</details>
<p id="bkmrk-%C2%A0-2"></p>
<hr id="bkmrk--1">
<h4 id="bkmrk-step-4%3A-create-the-n">Step 4: Create the <code>NEXTAUTH_SECRET</code></h4>
<p id="bkmrk-the-nextauth_secret-">The <code>NEXTAUTH_SECRET</code> is used to sign authentication tokens securely. You need to generate a random string to be used as the <code>NEXTAUTH_SECRET</code>.</p>
<p id="bkmrk-you-can-generate-a-s">You can generate a secure <code>NEXTAUTH_SECRET</code> using the following command:<br></p>
<pre id="bkmrk-openssl-rand--base64"><code class="language-bash">openssl rand -base64 32</code></pre>
<p id="bkmrk-this-command-will-ge">This command will generate a 32-byte random string that you can add to your <code>.env</code> file as the <code>NEXTAUTH_SECRET</code>.</p>
<h5 id="bkmrk-what-does-nextauth_s">What Does <code>NEXTAUTH_SECRET</code> Do?</h5>
<p id="bkmrk-the-nextauth_secret--1">The <code>NEXTAUTH_SECRET</code> is critical for securing the authentication process in Linkwarden. It is used to sign and encrypt tokens, ensuring that user sessions are protected from tampering or unauthorized access.</p>
<p id="bkmrk-%C2%A0-3"></p>
<hr id="bkmrk--2">
<h4 id="bkmrk-step-5%3A-start-the-st">Step 5: Start the Stack</h4>
<p id="bkmrk-once-everything-is-c">Once everything is configured, you can start the Linkwarden stack either manually or through Portainer.</p>
<h5 id="bkmrk-start-with-docker-sw">Start with Docker Swarm</h5>
<p id="bkmrk-to-start-the-stack-m">To start the stack manually, run:<br></p>
<pre id="bkmrk-docker-stack-deploy-"><code class="language-bash">docker stack deploy -c docker-compose.yaml linkwarden</code></pre>
<h5 id="bkmrk-start-with-portainer">Start with Portainer</h5>
<p id="bkmrk-alternatively%2C-you-c">Alternatively, you can use Portainer&rsquo;s graphical interface to import the <code>docker-compose.yaml</code> file and start the stack.</p>
<p id="bkmrk-once-the-stack-is-de" class="callout success">Once the stack is deployed, Linkwarden will be available at <code>https://linkwarden.domain.tld</code> (or whatever domain you've configured).</p>
<p id="bkmrk-%C2%A0-4"></p>
<hr id="bkmrk--3">
<h2 id="bkmrk-conclusion">Conclusion</h2>
<p id="bkmrk-this-guide-provides-">This guide provides a detailed walkthrough for setting up Linkwarden with Traefik on Docker Swarm. From user and folder management to Docker Compose configuration, these steps ensure a secure and scalable deployment of your self-hosted bookmark manager.</p>

now we write a guid on how to setup:
Introduction 
Beszel consists of two main components: the hub and the agent.

Hub: A web application that provides a dashboard for viewing and managing connected systems. Built on PocketBase⁠.

Agent: Runs on each system you want to monitor, creating a minimal SSH server to communicate system metrics to the hub.
https://pocketbase.io/

1. create necessary folders:
if you have multiple nodes and docker Swarm i reference here to my setup guide on how to setup GlusterFS https://wiki.aeoneros.com/books/glusterfs-keepalived-setup/chapter/glusterfs

mkdir /mnt/glustermount/data/beszel_data

2. go to Portainer or create a docker-compose.yaml for Initial setup

nano docker-compose.yaml

IMPORTANT
This configuration should work out of the box, but you must follow these steps when adding the system in the web UI:
Update the KEY value with your public key, then run docker compose up -d again to restart the agent.
Use host.docker.internal als the Host / IP. Do not use localhost or 127.0.0.1.

Docker Standalone:
services:
  beszel:
    image: henrygd/beszel:latest
    container_name: beszel
    restart: unless-stopped
    extra_hosts:
      - host.docker.internal:host-gateway
    ports:
      - 8090:8090
    volumes:
      - /mnt/glustermount/data/beszel_data:/beszel_data

  beszel-agent:
    image: henrygd/beszel-agent:latest
    container_name: beszel-agent
    restart: unless-stopped
    network_mode: host
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      PORT: 45876
      # Do not remove quotes around the key
      KEY: 'UPDATE WITH YOUR PUBLIC KEY (copy from "Add system" dialog)'

Docker Swarm + Traefik
version: "3.7"

services:
  beszel:
    image: henrygd/beszel:latest
    container_name: beszel
    restart: unless-stopped
    networks:
     - management_net
    extra_hosts:
      - host.docker.internal:host-gateway
    ports:
      - 8090:8090
    volumes:
      - /mnt/glustermount/data/beszel_data:/beszel_data
    deploy:
      mode: replicated
      replicas: 1
      labels:
        - 'traefik.enable=true'
        - 'traefik.http.services.beszel-agent.loadbalancer.server.port=8090'

  beszel-agent:
    image: henrygd/beszel-agent:latest
    container_name: beszel-agent
    restart: unless-stopped
    network_mode: host
    depends_on:
      - beszel_beszel
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      PORT: 45876
      # Do not remove quotes around the key
      KEY: 'UPDATE WITH YOUR PUBLIC KEY (copy from "Add system" dialog)'
    deploy:
      mode: replicated
      replicas: 1
      labels:
        - 'traefik.enable=true'
        - 'traefik.http.services.beszel-agent.loadbalancer.server.port=45876'
networks:
  management_net:
    external: true


Why host network mode?
The agent must use host network mode to access network interface metrics, which automatically exposes the port. Change the port using an environment variable if needed.
If you don't need network stats, you can remove that line from the compose file and map the port manually.


3. start container with docker initial start
with Swarm:
docker stack deploy -c docker-compose.yaml beszel
or docker standalone:
docker compose up -d
or portainer start the stack

4. Open http://localhost:8090⁠ and create an admin user.

5. Adding Systems / Nodes 
The docker-compose.yml or binary install command is provided for copy/paste in the hub's web UI when adding a new system.

So if youre using docker swarm with traefik again you can just adjust your docker compose by the following example:
This is just the Service of the beszel-agent you Need to adjust:
Paste the SSH-Key that was generated in the Web-UI to the KEY Section.
The Deploy Mode Replicated 1 says that the container only should run on 1 node of our swarm cluster.

Now an Important one:
You want your different agents running on different nodes. so we Need to define them in the deploy section and create a constraint so this specific container only starts on this node.

Example:

  beszel-agent1:
    image: henrygd/beszel-agent:latest
    container_name: beszel-agent1
    restart: unless-stopped
    network_mode: host
    depends_on:
      - beszel_beszel
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      PORT: 45876
      # Do not remove quotes around the key
      KEY: 'UPDATE WITH YOUR PUBLIC KEY (copy from "Add system" dialog)'
    deploy:
      mode: replicated
      replicas: 1
      labels:
        - 'traefik.enable=true'
        - 'traefik.http.services.beszel-agent1.loadbalancer.server.port=45876'
      placement:
        constraints:
          - node.hostname == swarmpi1 # Ensures deployment on the node with the label swarmpi1

  beszel-agent2:
    image: henrygd/beszel-agent:latest
    container_name: beszel-agent2
    restart: unless-stopped
    network_mode: host
    depends_on:
      - beszel_beszel
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      PORT: 45876
      # Do not remove quotes around the key
      KEY: 'UPDATE WITH YOUR PUBLIC KEY (copy from "Add system" dialog)'
    deploy:
      mode: replicated
      replicas: 1
      labels:
        - 'traefik.enable=true'
        - 'traefik.http.services.beszel-agent2.loadbalancer.server.port=45876'
      placement:
        constraints:
          - node.hostname == swarmpi2 # Ensures deployment on the node with the label swarmpi2