Tipp · Proxmox · LXC · Docker

Docker und Hawser in einem Debian-13-LXC für Dockhand installieren

Private Anleitung, kostenlos und ohne Gewährleistung, soweit gesetzlich zulässig. Lies das Skript, bevor du es ausführst, und probiere es zuerst in einer Testumgebung aus. Die Nutzung erfolgt auf eigenes Risiko.

Skript herunterladenAnleitung als MarkdownAuf GitHub

Diese Anleitung beschreibt die Vorbereitung eines bereits vorhandenen Debian-13-LXC für die spätere Verwaltung von Docker-Containern mit Dockhand.

Das Installationsscript erledigt Folgendes:

  • aktualisiert Debian und installiert benötigte Werkzeuge,
  • installiert Docker, sofern Docker noch nicht vorhanden ist,
  • legt eine Verzeichnisstruktur für die betreffende Anwendung an,
  • lädt das Hawser-Image,
  • erzeugt oder übernimmt ein Hawser-Token,
  • startet den Hawser-Agenten als Docker-Container,
  • zeigt Endpoint und Token für den Dockhand-Eintrag an.

Voraussetzungen

Vor dem Start sollten folgende Punkte erfüllt sein:

  • Der Debian-13-LXC ist bereits erstellt und gestartet.
  • Du kannst dich als root im LXC anmelden.
  • Der LXC hat Netzwerkzugriff und kann Paketquellen sowie das Container-Image erreichen.
  • In der Proxmox-Konfiguration ist der LXC so eingerichtet, dass Docker darin betrieben werden kann.
  • Du kennst den Namen, den du für den Anwendungsordner verwenden möchtest, zum Beispiel paperless.

Ablauf

  1. Im LXC eine Script-Datei anlegen.
  2. Das Script einfügen und speichern.
  3. Das Script ausführbar machen.
  4. Das Script als root ausführen.
  5. Endpoint und Token notieren.
  6. Den Hawser-Agenten in Dockhand als Node hinzufügen.

1. Script-Datei erstellen

Melde dich im Debian-LXC als root an und erstelle die Datei:

nano /root/install-docker.sh

Füge anschließend das folgende Script vollständig in den Editor ein.

Alternative ohne Kopieren: Du kannst das Script auch direkt von GitHub herunterladen und ausführbar machen. Dann entfallen die Schritte 1 bis 4, weiter geht es mit Schritt 5.

wget -O /root/install-docker.sh https://raw.githubusercontent.com/luro611/homelab-tipps/main/docker-hawser-lxc/install-docker.sh
chmod +x /root/install-docker.sh

2. Installationsscript

#!/bin/bash
set -euo pipefail

# =====================================================
# Hawser Worker Setup (Debian 13 "Trixie") – clean & idempotent
# =====================================================

#--- Root check ---
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
  echo "Bitte als root ausführen (sudo -i)."
  exit 1
fi

export DEBIAN_FRONTEND=noninteractive

# ================================
# 🏷️ 0) LXC / App Name
# ================================
read -rp "👉 Name für diesen LXC / Stack (z.B. paperless): " APP_NAME

if [[ -z "$APP_NAME" ]]; then
  echo "❌ Name darf nicht leer sein!"
  exit 1
fi

APP_NAME="$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9-_')"

echo "✅ Verwende Namen: $APP_NAME"

# ================================
# 📁 Struktur
# ================================
BASE_ROOT="/opt/containers"
BASE_DIR="$BASE_ROOT/$APP_NAME"
DATA_DIR="$BASE_DIR/data"
VOLUMES_DIR="$BASE_DIR/volumes"
CONFIGS_DIR="$BASE_DIR/configs"

LOG_FILE="/var/log/hawser-${APP_NAME}-setup.log"

CONTAINER_NAME="hawser-agent"
HAWSER_IMAGE="ghcr.io/finsys/hawser:latest"

# FIX: richtiger Hawser Port
HAWSER_PORT="2376"

# Token-Datei (wird bei erneutem Lauf wiederverwendet)
TOKEN_FILE="$BASE_DIR/hawser.token"

# ================================
# 📝 Logging
# ================================
mkdir -p "$(dirname "$LOG_FILE")"
touch "$LOG_FILE"
chmod 0600 "$LOG_FILE"

exec > >(tee -a "$LOG_FILE") 2>&1

timestamp() { date +"%Y-%m-%d %H:%M:%S %z"; }
log() { echo "$(timestamp) | $*"; }

log "🚀 Starte Hawser Setup für: $APP_NAME"

# ================================
# 🔄 1) System Update
# ================================
log "🔄 Systemupdate..."
apt update
apt -y upgrade
apt -y autoremove

# ================================
# 🔧 2) Tools
# ================================
log "🔧 Installiere Tools..."
apt install -y curl ca-certificates gnupg lsb-release apt-transport-https openssl

# ================================
# 🐳 3) Docker installieren
# ================================
if ! command -v docker >/dev/null 2>&1; then
  log "🐳 Installiere Docker..."

  install -d -m 0755 /etc/apt/keyrings
  curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
  chmod a+r /etc/apt/keyrings/docker.gpg

  CODENAME="$(lsb_release -cs || echo trixie)"
  ARCH="$(dpkg --print-architecture)"

  cat >/etc/apt/sources.list.d/docker.list <<EOF
deb [arch=${ARCH} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian ${CODENAME} stable
EOF

  if ! apt update; then
    log "⚠️ Fallback auf bookworm..."
    cat >/etc/apt/sources.list.d/docker.list <<EOF
deb [arch=${ARCH} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian bookworm stable
EOF
    apt update
  fi

  apt install -y --no-install-recommends docker-ce docker-ce-cli containerd.io
  apt install -y --no-install-recommends docker-buildx-plugin docker-compose-plugin

  systemctl enable --now docker
  log "✅ Docker installiert."
else
  log "✅ Docker bereits vorhanden."
  systemctl enable --now docker || true
fi

# ================================
# 📁 4) Verzeichnisse
# ================================
log "📁 Erstelle Verzeichnisstruktur..."
mkdir -p "$DATA_DIR" "$VOLUMES_DIR" "$CONFIGS_DIR"
chmod -R 755 "$BASE_DIR"
chown -R root:root "$BASE_DIR"

# ================================
# 📦 5) Hawser Image
# ================================
log "📦 Lade Hawser Image..."
docker pull "$HAWSER_IMAGE" || log "⚠️ Pull fehlgeschlagen (offline?)"

# ================================
# 🔑 6) Token erzeugen / wiederverwenden
# ================================
if [ -s "$TOKEN_FILE" ]; then
  HAWSER_TOKEN="$(cat "$TOKEN_FILE")"
  log "🔑 Vorhandenes Token wird wiederverwendet."
else
  HAWSER_TOKEN="$(openssl rand -hex 24)"
  ( umask 077; echo "$HAWSER_TOKEN" > "$TOKEN_FILE" )
  log "🔑 Neues Token erzeugt."
fi

# ================================
# 🚀 7) CLEAN DEPLOY (WICHTIG!)
# ================================
log "🚀 Entferne alte Hawser Instanz (falls vorhanden)..."
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true

log "🚀 Starte Hawser Agent neu..."

docker run -d \
  --name "$CONTAINER_NAME" \
  --restart unless-stopped \
  -p ${HAWSER_PORT}:2376 \
  -e TOKEN="$HAWSER_TOKEN" \
  -v /var/run/docker.sock:/var/run/docker.sock \
  "$HAWSER_IMAGE"

sleep 3

if [ "$(docker inspect -f '{{.State.Running}}' "$CONTAINER_NAME" 2>/dev/null)" != "true" ]; then
  log "❌ Hawser Container läuft nicht! Letzte Logs:"
  docker logs "$CONTAINER_NAME" 2>&1 | tail -20
  exit 1
fi

log "✅ Hawser Agent läuft."

# ================================
# 🌐 Endpoint + Token
# ================================
HOST_IP="$(hostname -I | awk '{print $1}')"

log "🌐 Hawser Endpoint: http://${HOST_IP}:${HAWSER_PORT}"

# ================================
# 🏁 Done
# ================================
log "🏁 Setup abgeschlossen für: $APP_NAME"
log "📁 Base: $BASE_DIR"

echo
echo "=================== DOCKHAND-EINTRAG ==================="
echo "Endpoint : http://${HOST_IP}:${HAWSER_PORT}"
echo "Token    : ${HAWSER_TOKEN}"
echo "(Token gespeichert in: ${TOKEN_FILE})"
echo "========================================================"
echo
echo "➡️ Jetzt in Dockhand als Node hinzufügen (Endpoint + Token)"

3. Datei speichern

In nano:

  1. CTRL + O drücken.
  2. Mit ENTER den Dateinamen bestätigen.
  3. CTRL + X drücken, um den Editor zu schließen.

4. Script ausführbar machen

chmod +x /root/install-docker.sh

5. Script ausführen

/root/install-docker.sh

Das Script fragt nach einem Namen für den LXC beziehungsweise Stack, zum Beispiel:

paperless

Der eingegebene Name wird in Kleinbuchstaben umgewandelt. Zeichen, die nicht zu a-z, 0-9, - oder _ gehören, werden entfernt.

Erklärung der Script-Abschnitte

Start und Root-Prüfung

#!/bin/bash
set -euo pipefail
  • #!/bin/bash legt Bash als Interpreter fest.
  • set -e beendet das Script bei einem nicht abgefangenen Fehler.
  • set -u behandelt nicht gesetzte Variablen als Fehler.
  • set -o pipefail sorgt dafür, dass auch Fehler innerhalb einer Befehlskette mit | erkannt werden.

Anschließend wird geprüft, ob das Script mit Root-Rechten ausgeführt wird. Ist das nicht der Fall, erscheint eine Fehlermeldung und das Script beendet sich.

export DEBIAN_FRONTEND=noninteractive

Diese Einstellung verhindert bei der Paketinstallation interaktive Rückfragen von Debian-Paketinstallern.

Name für LXC beziehungsweise Anwendung

Das Script fragt nach einem Namen:

read -rp "👉 Name für diesen LXC / Stack (z.B. paperless): " APP_NAME

Dieser Name dient unter anderem dazu, die Verzeichnisse und den Logdateinamen zu bestimmen. Er ist nicht der Name des Hawser-Containers. Dieser heißt im Script fest:

CONTAINER_NAME="hawser-agent"

Verzeichnisstruktur

Bei der Eingabe paperless werden folgende Variablen gesetzt:

BASE_ROOT="/opt/containers"
BASE_DIR="$BASE_ROOT/$APP_NAME"
DATA_DIR="$BASE_DIR/data"
VOLUMES_DIR="$BASE_DIR/volumes"
CONFIGS_DIR="$BASE_DIR/configs"

Daraus ergibt sich:

/opt/containers/                    Wurzelverzeichnis für Anwendungen
└── paperless/                      BASE_DIR
    ├── data/                       DATA_DIR
    ├── volumes/                    VOLUMES_DIR
    └── configs/                    CONFIGS_DIR

Wofür sind die Ordner gedacht?

  • data/ – vorgesehener Ablageort für persistente Anwendungsdaten.
  • volumes/ – vorgesehener Ablageort für Daten, die als Bind-Mounts in Docker-Container eingebunden werden.
  • configs/ – vorgesehener Ablageort für Konfigurationsdateien.

Protokollierung

LOG_FILE="/var/log/hawser-${APP_NAME}-setup.log"

Das Script legt für jeden eingegebenen Namen eine eigene Logdatei unter /var/log an. Bei paperless lautet der Pfad beispielsweise:

/var/log/hawser-paperless-setup.log

Die Ausgabe des Scripts wird sowohl im Terminal angezeigt als auch an die Logdatei angehängt.

Systemupdate und benötigte Werkzeuge

Das Script aktualisiert die Paketlisten, installiert verfügbare Updates und entfernt nicht mehr benötigte Pakete:

apt update
apt -y upgrade
apt -y autoremove

Danach werden Werkzeuge installiert, die das Script für den Docker-Schritt und die Token-Erzeugung benötigt:

apt install -y curl ca-certificates gnupg lsb-release apt-transport-https openssl

Docker installieren oder starten

Das Script prüft zunächst, ob der Befehl docker vorhanden ist.

  • Docker ist noch nicht installiert: Das Script richtet den Docker-Paketquelleneintrag ein und installiert Docker sowie die Compose- und Buildx-Plugins.
  • Docker ist bereits installiert: Das Script versucht, den Docker-Dienst zu aktivieren und zu starten.

Der Dienst wird mit folgendem Befehl aktiviert und gestartet:

systemctl enable --now docker

enable sorgt dafür, dass Docker beim Start des LXC automatisch gestartet wird. --now startet den Dienst direkt.

Ordner anlegen und Berechtigungen setzen

mkdir -p "$DATA_DIR" "$VOLUMES_DIR" "$CONFIGS_DIR"
chmod -R 755 "$BASE_DIR"
chown -R root:root "$BASE_DIR"

Was bedeuten die Befehle?

  • mkdir -p erstellt die Verzeichnisse einschließlich der benötigten übergeordneten Ordner. Bereits vorhandene Verzeichnisse führen dabei nicht allein deshalb zu einem Fehler.
  • chmod -R 755 setzt rekursiv die Berechtigungen unterhalb von BASE_DIR auf 755.
  • chown -R root:root setzt rekursiv Eigentümer und Gruppe auf root.

Bei 755 darf der Eigentümer lesen, schreiben und Verzeichnisse betreten; Gruppe und andere Benutzer dürfen lesen und Verzeichnisse betreten.

Beispiel für eine Anwendung, die mit UID und GID 1000 arbeiten soll:

chown -R 1000:1000 /opt/containers/paperless

Diesen Befehl nicht blind verwenden: Die benötigten IDs hängen von der jeweiligen Anwendung ab.

Hawser-Image herunterladen

HAWSER_IMAGE="ghcr.io/finsys/hawser:latest"
docker pull "$HAWSER_IMAGE"

Das Image wird aus der angegebenen Container-Registry geladen. Falls der Download fehlschlägt, schreibt das Script eine Warnung ins Log und fährt weiter.

Hawser-Token erzeugen oder wiederverwenden

Das Token wird unter folgendem Pfad gespeichert:

/opt/containers/<APP_NAME>/hawser.token

Beispiel:

/opt/containers/paperless/hawser.token
  • Ist die Datei bereits vorhanden und nicht leer, wird ihr Inhalt wiederverwendet.
  • Andernfalls wird ein neues Token erzeugt und in der Datei gespeichert.

Das Token ist ein Zugangsschlüssel für die Verbindung mit Hawser. Behandle es vertraulich und teile es nicht öffentlich.

Hawser-Container starten

Vor dem Start entfernt das Script einen eventuell bereits vorhandenen Container namens hawser-agent:

docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true

Anschließend wird Hawser gestartet:

docker run -d \
  --name "$CONTAINER_NAME" \
  --restart unless-stopped \
  -p ${HAWSER_PORT}:2376 \
  -e TOKEN="$HAWSER_TOKEN" \
  -v /var/run/docker.sock:/var/run/docker.sock \
  "$HAWSER_IMAGE"

Bedeutung der wichtigsten Optionen

  • -d – startet den Container im Hintergrund.
  • --name hawser-agent – vergibt den Containernamen.
  • --restart unless-stopped – Docker startet den Container nach einem Neustart erneut, sofern er nicht ausdrücklich gestoppt wurde.
  • -p 2376:2376 – veröffentlicht Port 2376 des Containers auf Port 2376 des LXC.
  • -e TOKEN=... – übergibt das Token als Umgebungsvariable.
  • -v /var/run/docker.sock:/var/run/docker.sock – stellt dem Hawser-Container den Docker-Socket des LXC bereit.

Nach dem Start wartet das Script drei Sekunden und prüft, ob der Hawser-Container läuft. Falls nicht, gibt es die letzten Container-Logs aus und beendet sich mit einem Fehler.

Endpoint ermitteln

Das Script nimmt die erste von hostname -I ausgegebene IP-Adresse:

HOST_IP="$(hostname -I | awk '{print $1}')"

Anschließend wird der Endpoint nach diesem Schema ausgegeben:

http://<IP-Adresse-des-LXC>:2376

Hawser in Dockhand hinzufügen

Nach erfolgreichem Abschluss zeigt das Script einen Abschnitt ähnlich diesem an:

=================== DOCKHAND-EINTRAG ===================
Endpoint : http://192.168.1.50:2376
Token    : <dein-token>
(Token gespeichert in: /opt/containers/paperless/hawser.token)
========================================================

Trage in Dockhand die angezeigten Werte als Hawser-Node ein:

  • Endpoint: die angezeigte Adresse, zum Beispiel http://192.168.1.50:2376
  • Token: das vom Script angezeigte Token

Der Endpoint muss vom System, auf dem Dockhand läuft, erreichbar sein. Wenn keine Verbindung zustande kommt, prüfe insbesondere:

  1. Läuft der Hawser-Container?
  2. Ist die verwendete IP-Adresse die richtige?
  3. Ist Port 2376 vom Dockhand-System aus erreichbar?
  4. Stimmen Endpoint und Token exakt?
  5. Gibt es eine Firewall- oder LXC-Netzwerkeinstellung, die die Verbindung blockiert?

Status und Fehlersuche

Docker-Status prüfen

systemctl status docker

Hawser-Container anzeigen

docker ps -a --filter name=hawser-agent

Hawser-Logs anzeigen

docker logs hawser-agent

Setup-Log anzeigen

Ersetze paperless durch den beim Start eingegebenen Namen:

cat /var/log/hawser-paperless-setup.log

Das Log wird bei einem weiteren Lauf ergänzt.

NFS-Shares verwenden

Wenn eine Docker-Anwendung später auf ein NFS-Share zugreifen soll, muss im LXC ein NFS-Client installiert sein:

apt-get update
apt-get install -y nfs-common

Damit ist der NFS-Client im LXC installiert. Das Einbinden des NFS-Shares und dessen Verwendung als Docker-Bind-Mount sind separate Schritte und werden von diesem Hawser-Installationsscript nicht automatisch eingerichtet.

Ergänzende Hinweise zur Verzeichnisstruktur

Beispiel mit paperless

Wenn beim Scriptstart paperless eingegeben wird, entsteht folgende Struktur:

/opt/containers/paperless/
├── data/
├── volumes/
├── configs/
└── hawser.token

Die Ordner können später beispielsweise in einer Docker-Konfiguration als Bind-Mounts verwendet werden. Ein allgemeines Beispiel:

/opt/containers/paperless/data:/Pfad/im/Anwendungscontainer

Der Zielpfad innerhalb des Containers muss zur jeweiligen Anwendung passen. Er ist nicht für alle Anwendungen gleich.

Erneuter Scriptlauf

Das Script kann erneut ausgeführt werden. Dabei gilt:

  • Ein vorhandenes Hawser-Token wird wiederverwendet, sofern die Token-Datei vorhanden und nicht leer ist.
  • Ein bereits vorhandener Container mit dem Namen hawser-agent wird entfernt und anschließend neu erstellt.
  • Die Ordner werden erneut angelegt, falls sie fehlen.
  • Die Berechtigungen und Eigentümer unter BASE_DIR werden erneut rekursiv auf 755 beziehungsweise root:root gesetzt.
  • Die Systempakete werden erneut aktualisiert.

Abschluss

Nach erfolgreichem Abschluss sind Docker und der Hawser-Agent im Debian-13-LXC vorbereitet. Notiere Endpoint und Token sicher und füge den Agenten in Dockhand als Node hinzu.

Die eigentlichen Anwendungen sowie ihre Volumes, Netzwerke und Konfigurationen werden danach in Dockhand eingerichtet.

Tip · Proxmox · LXC · Docker

Installing Docker and Hawser in a Debian 13 LXC for Dockhand

A private guide, free of charge and without warranty to the extent permitted by law. Read the script before you run it and try it in a test environment first. Use at your own risk.

Download scriptGuide as MarkdownOn GitHub

This guide describes how to prepare an existing Debian 13 LXC so that its Docker containers can later be managed with Dockhand.

The install script does the following:

  • updates Debian and installs the required tools,
  • installs Docker if it is not already present,
  • creates a directory structure for the application in question,
  • pulls the Hawser image,
  • creates a Hawser token or reuses an existing one,
  • starts the Hawser agent as a Docker container,
  • shows the endpoint and token for the Dockhand entry.

Prerequisites

Before you start, the following should be true:

  • The Debian 13 LXC has already been created and started.
  • You can log in to the LXC as root.
  • The LXC has network access and can reach the package sources and the container image.
  • The LXC is configured in Proxmox so that Docker can run inside it.
  • You know which name you want to use for the application folder, for example paperless.

Overview

  1. Create a script file in the LXC.
  2. Paste the script and save it.
  3. Make the script executable.
  4. Run the script as root.
  5. Note down the endpoint and token.
  6. Add the Hawser agent to Dockhand as a node.

1. Create the script file

Log in to the Debian LXC as root and create the file:

nano /root/install-docker.sh

Then paste the complete script below into the editor.

Alternative without copying: You can also download the script straight from GitHub and make it executable. Steps 1 to 4 are then not needed; continue with step 5.

wget -O /root/install-docker.sh https://raw.githubusercontent.com/luro611/homelab-tipps/main/docker-hawser-lxc/install-docker.sh
chmod +x /root/install-docker.sh

2. Install script

The script is the same as in the German guide; its comments and messages are in German. You can also open it as a separate file: install-docker.sh.

#!/bin/bash
set -euo pipefail

# =====================================================
# Hawser Worker Setup (Debian 13 "Trixie") – clean & idempotent
# =====================================================

#--- Root check ---
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
  echo "Bitte als root ausführen (sudo -i)."
  exit 1
fi

export DEBIAN_FRONTEND=noninteractive

# ================================
# 🏷️ 0) LXC / App Name
# ================================
read -rp "👉 Name für diesen LXC / Stack (z.B. paperless): " APP_NAME

if [[ -z "$APP_NAME" ]]; then
  echo "❌ Name darf nicht leer sein!"
  exit 1
fi

APP_NAME="$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z0-9-_')"

echo "✅ Verwende Namen: $APP_NAME"

# ================================
# 📁 Struktur
# ================================
BASE_ROOT="/opt/containers"
BASE_DIR="$BASE_ROOT/$APP_NAME"
DATA_DIR="$BASE_DIR/data"
VOLUMES_DIR="$BASE_DIR/volumes"
CONFIGS_DIR="$BASE_DIR/configs"

LOG_FILE="/var/log/hawser-${APP_NAME}-setup.log"

CONTAINER_NAME="hawser-agent"
HAWSER_IMAGE="ghcr.io/finsys/hawser:latest"

# FIX: richtiger Hawser Port
HAWSER_PORT="2376"

# Token-Datei (wird bei erneutem Lauf wiederverwendet)
TOKEN_FILE="$BASE_DIR/hawser.token"

# ================================
# 📝 Logging
# ================================
mkdir -p "$(dirname "$LOG_FILE")"
touch "$LOG_FILE"
chmod 0600 "$LOG_FILE"

exec > >(tee -a "$LOG_FILE") 2>&1

timestamp() { date +"%Y-%m-%d %H:%M:%S %z"; }
log() { echo "$(timestamp) | $*"; }

log "🚀 Starte Hawser Setup für: $APP_NAME"

# ================================
# 🔄 1) System Update
# ================================
log "🔄 Systemupdate..."
apt update
apt -y upgrade
apt -y autoremove

# ================================
# 🔧 2) Tools
# ================================
log "🔧 Installiere Tools..."
apt install -y curl ca-certificates gnupg lsb-release apt-transport-https openssl

# ================================
# 🐳 3) Docker installieren
# ================================
if ! command -v docker >/dev/null 2>&1; then
  log "🐳 Installiere Docker..."

  install -d -m 0755 /etc/apt/keyrings
  curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
  chmod a+r /etc/apt/keyrings/docker.gpg

  CODENAME="$(lsb_release -cs || echo trixie)"
  ARCH="$(dpkg --print-architecture)"

  cat >/etc/apt/sources.list.d/docker.list <<EOF
deb [arch=${ARCH} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian ${CODENAME} stable
EOF

  if ! apt update; then
    log "⚠️ Fallback auf bookworm..."
    cat >/etc/apt/sources.list.d/docker.list <<EOF
deb [arch=${ARCH} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian bookworm stable
EOF
    apt update
  fi

  apt install -y --no-install-recommends docker-ce docker-ce-cli containerd.io
  apt install -y --no-install-recommends docker-buildx-plugin docker-compose-plugin

  systemctl enable --now docker
  log "✅ Docker installiert."
else
  log "✅ Docker bereits vorhanden."
  systemctl enable --now docker || true
fi

# ================================
# 📁 4) Verzeichnisse
# ================================
log "📁 Erstelle Verzeichnisstruktur..."
mkdir -p "$DATA_DIR" "$VOLUMES_DIR" "$CONFIGS_DIR"
chmod -R 755 "$BASE_DIR"
chown -R root:root "$BASE_DIR"

# ================================
# 📦 5) Hawser Image
# ================================
log "📦 Lade Hawser Image..."
docker pull "$HAWSER_IMAGE" || log "⚠️ Pull fehlgeschlagen (offline?)"

# ================================
# 🔑 6) Token erzeugen / wiederverwenden
# ================================
if [ -s "$TOKEN_FILE" ]; then
  HAWSER_TOKEN="$(cat "$TOKEN_FILE")"
  log "🔑 Vorhandenes Token wird wiederverwendet."
else
  HAWSER_TOKEN="$(openssl rand -hex 24)"
  ( umask 077; echo "$HAWSER_TOKEN" > "$TOKEN_FILE" )
  log "🔑 Neues Token erzeugt."
fi

# ================================
# 🚀 7) CLEAN DEPLOY (WICHTIG!)
# ================================
log "🚀 Entferne alte Hawser Instanz (falls vorhanden)..."
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true

log "🚀 Starte Hawser Agent neu..."

docker run -d \
  --name "$CONTAINER_NAME" \
  --restart unless-stopped \
  -p ${HAWSER_PORT}:2376 \
  -e TOKEN="$HAWSER_TOKEN" \
  -v /var/run/docker.sock:/var/run/docker.sock \
  "$HAWSER_IMAGE"

sleep 3

if [ "$(docker inspect -f '{{.State.Running}}' "$CONTAINER_NAME" 2>/dev/null)" != "true" ]; then
  log "❌ Hawser Container läuft nicht! Letzte Logs:"
  docker logs "$CONTAINER_NAME" 2>&1 | tail -20
  exit 1
fi

log "✅ Hawser Agent läuft."

# ================================
# 🌐 Endpoint + Token
# ================================
HOST_IP="$(hostname -I | awk '{print $1}')"

log "🌐 Hawser Endpoint: http://${HOST_IP}:${HAWSER_PORT}"

# ================================
# 🏁 Done
# ================================
log "🏁 Setup abgeschlossen für: $APP_NAME"
log "📁 Base: $BASE_DIR"

echo
echo "=================== DOCKHAND-EINTRAG ==================="
echo "Endpoint : http://${HOST_IP}:${HAWSER_PORT}"
echo "Token    : ${HAWSER_TOKEN}"
echo "(Token gespeichert in: ${TOKEN_FILE})"
echo "========================================================"
echo
echo "➡️ Jetzt in Dockhand als Node hinzufügen (Endpoint + Token)"

3. Save the file

In nano:

  1. Press CTRL + O.
  2. Press ENTER to confirm the file name.
  3. Press CTRL + X to close the editor.

4. Make the script executable

chmod +x /root/install-docker.sh

5. Run the script

/root/install-docker.sh

The script asks for a name for the LXC or stack, for example:

paperless

The name is converted to lower case. Characters other than a-z, 0-9, - or _ are removed.

What each part of the script does

Start and root check

#!/bin/bash
set -euo pipefail
  • #!/bin/bash sets Bash as the interpreter.
  • set -e stops the script on any error that is not handled.
  • set -u treats unset variables as an error.
  • set -o pipefail makes sure errors inside a pipeline with | are detected as well.

Next, the script checks whether it is running with root privileges. If not, it prints an error message and exits.

export DEBIAN_FRONTEND=noninteractive

This setting prevents Debian package installers from asking interactive questions during installation.

Name for the LXC or application

The script asks for a name:

read -rp "👉 Name für diesen LXC / Stack (z.B. paperless): " APP_NAME

Among other things, this name determines the directories and the name of the log file. It is not the name of the Hawser container, which is fixed in the script:

CONTAINER_NAME="hawser-agent"

Directory structure

If you enter paperless, the following variables are set:

BASE_ROOT="/opt/containers"
BASE_DIR="$BASE_ROOT/$APP_NAME"
DATA_DIR="$BASE_DIR/data"
VOLUMES_DIR="$BASE_DIR/volumes"
CONFIGS_DIR="$BASE_DIR/configs"

This results in:

/opt/containers/                    root directory for applications
└── paperless/                      BASE_DIR
    ├── data/                       DATA_DIR
    ├── volumes/                    VOLUMES_DIR
    └── configs/                    CONFIGS_DIR

What are the folders for?

  • data/ – intended for persistent application data.
  • volumes/ – intended for data that is mounted into Docker containers as bind mounts.
  • configs/ – intended for configuration files.

Logging

LOG_FILE="/var/log/hawser-${APP_NAME}-setup.log"

The script creates a separate log file under /var/log for each name you enter. For paperless, the path is:

/var/log/hawser-paperless-setup.log

The script's output is shown in the terminal and appended to the log file at the same time.

System update and required tools

The script updates the package lists, installs available updates and removes packages that are no longer needed:

apt update
apt -y upgrade
apt -y autoremove

It then installs the tools the script needs for the Docker step and for creating the token:

apt install -y curl ca-certificates gnupg lsb-release apt-transport-https openssl

Install or start Docker

The script first checks whether the docker command exists.

  • Docker is not installed yet: The script adds the Docker package source and installs Docker together with the Compose and Buildx plugins.
  • Docker is already installed: The script tries to enable and start the Docker service.

The service is enabled and started with:

systemctl enable --now docker

enable makes Docker start automatically whenever the LXC starts. --now starts the service right away.

Create folders and set permissions

mkdir -p "$DATA_DIR" "$VOLUMES_DIR" "$CONFIGS_DIR"
chmod -R 755 "$BASE_DIR"
chown -R root:root "$BASE_DIR"

What do these commands do?

  • mkdir -p creates the directories including any parent folders they need. Directories that already exist do not cause an error just because they exist.
  • chmod -R 755 recursively sets the permissions below BASE_DIR to 755.
  • chown -R root:root recursively sets owner and group to root.

With 755, the owner may read, write and enter directories; the group and other users may read and enter directories.

Example for an application that is meant to run with UID and GID 1000:

chown -R 1000:1000 /opt/containers/paperless

Do not use this command blindly: the IDs you need depend on the application.

Pull the Hawser image

HAWSER_IMAGE="ghcr.io/finsys/hawser:latest"
docker pull "$HAWSER_IMAGE"

The image is pulled from the container registry given here. If the download fails, the script writes a warning to the log and carries on.

Create or reuse the Hawser token

The token is stored at:

/opt/containers/<APP_NAME>/hawser.token

Example:

/opt/containers/paperless/hawser.token
  • If the file already exists and is not empty, its content is reused.
  • Otherwise a new token is created and saved to the file.

The token is an access key for the connection to Hawser. Keep it confidential and do not share it publicly.

Start the Hawser container

Before starting, the script removes any existing container named hawser-agent:

docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true

Hawser is then started:

docker run -d \
  --name "$CONTAINER_NAME" \
  --restart unless-stopped \
  -p ${HAWSER_PORT}:2376 \
  -e TOKEN="$HAWSER_TOKEN" \
  -v /var/run/docker.sock:/var/run/docker.sock \
  "$HAWSER_IMAGE"

What the main options mean

  • -d – runs the container in the background.
  • --name hawser-agent – sets the container name.
  • --restart unless-stopped – Docker starts the container again after a restart unless it was stopped explicitly.
  • -p 2376:2376 – publishes port 2376 of the container on port 2376 of the LXC.
  • -e TOKEN=... – passes the token as an environment variable.
  • -v /var/run/docker.sock:/var/run/docker.sock – gives the Hawser container access to the LXC's Docker socket.

After starting it, the script waits three seconds and checks whether the Hawser container is running. If it is not, it prints the last container logs and exits with an error.

Determine the endpoint

The script takes the first IP address printed by hostname -I:

HOST_IP="$(hostname -I | awk '{print $1}')"

It then prints the endpoint in this form:

http://<IP-address-of-the-LXC>:2376

Add Hawser to Dockhand

When the script finishes successfully, it shows a block similar to this one (DOCKHAND-EINTRAG means “Dockhand entry”):

=================== DOCKHAND-EINTRAG ===================
Endpoint : http://192.168.1.50:2376
Token    : <your-token>
(Token gespeichert in: /opt/containers/paperless/hawser.token)
========================================================

Enter the values shown in Dockhand as a Hawser node:

  • Endpoint: the address shown, for example http://192.168.1.50:2376
  • Token: the token shown by the script

The endpoint must be reachable from the system Dockhand runs on. If no connection can be made, check in particular:

  1. Is the Hawser container running?
  2. Is the IP address the right one?
  3. Can port 2376 be reached from the Dockhand system?
  4. Do the endpoint and token match exactly?
  5. Is a firewall or an LXC network setting blocking the connection?

Status and troubleshooting

Check Docker status

systemctl status docker

Show the Hawser container

docker ps -a --filter name=hawser-agent

Show the Hawser logs

docker logs hawser-agent

Show the setup log

Replace paperless with the name you entered at the start:

cat /var/log/hawser-paperless-setup.log

Each further run appends to the log.

Using NFS shares

If a Docker application later needs to access an NFS share, an NFS client must be installed in the LXC:

apt-get update
apt-get install -y nfs-common

This installs the NFS client in the LXC. Mounting the NFS share and using it as a Docker bind mount are separate steps that this Hawser install script does not set up.

More on the directory structure

Example with paperless

If you enter paperless when the script starts, this structure is created:

/opt/containers/paperless/
├── data/
├── volumes/
├── configs/
└── hawser.token

The folders can later be used as bind mounts in a Docker configuration, for example. A general example:

/opt/containers/paperless/data:/path/inside/the/application/container

The target path inside the container has to match the application. It is not the same for every application.

Running the script again

You can run the script again. In that case:

  • An existing Hawser token is reused, as long as the token file exists and is not empty.
  • An existing container named hawser-agent is removed and then created again.
  • The folders are created again if they are missing.
  • Permissions and ownership below BASE_DIR are again set recursively to 755 and root:root.
  • The system packages are updated again.

Done

When the script has finished successfully, Docker and the Hawser agent are ready in the Debian 13 LXC. Store the endpoint and token safely and add the agent to Dockhand as a node.

The actual applications and their volumes, networks and configurations are then set up in Dockhand.