21W idle Low-Power Home-Docker-Server (Debian 12)

April 2026:

Nachdem ich etwas mehr als 1 Jahr Docker unter macOS 13 verwendet habe und mit allen möglichen Hürden zu kämpfen hatte, wurde auf Debian 12, ein reines minimalistisch installiertes Linux, migriert. Ich denke, ich kann zufrieden sein.

Das System ist auf hohe Verfügbarkeit der Daten und maximale Stromersparnis im 24/7-Betrieb ausgelegt.

KomponenteDetails
MainboardIntel S1200V3RP (LGA1150, Server-Chipsatz)
CPUIntel Core i5-5675C (4 Kerne, Broadwell, 65W TDP)
RAM16 GB DDR3L (4x 4GB)
OS-Storage2x 240 GB SSD (LVM Mirror / RAID 1)
Docker-Storage2x 480 GB SSD (LVM Mirror / RAID 1)
Backup-Storage500 GB SSD (Extern via USB) + DS3622XS+ 30TB NAS
Leistungsaufnahme~21W Idle (gemessen an der Dose)

Der i5-5675C Vorteil: > „Warum diese spezielle CPU? Dank des integrierten 128MB eDRAM (L4-Cache) bietet dieser Broadwell-Chip eine Effizienz, die viele Nachfolgegenerationen im Idle-Bereich erst viel später erreichten. Gepaart mit dem S1200V3RP Server-Board ergibt das eine stabile Basis mit IPMI-Support bei nur 21W Leistungsaufnahme – ideal für einen 24/7 Home-Server mit anspruchsvoller Docker-Umgebung.“

Speicher-Architektur (LVM Mirroring)

Um Ausfallsicherheit zu gewährleisten, wurde konsequent auf LVM-Mirroring gesetzt. Dies ermöglicht den Austausch einer defekten SSD im laufenden Betrieb ohne Datenverlust.

  • Volume Group system: Spiegelt das Betriebssystem (Debian 12).

  • Volume Group docker: Trennung von OS und Anwendungsdaten. Alle Docker-Volumes und Container-Configs liegen hier.

  • Vorteil: Sollte eine SSD sterben, läuft der Server ohne Unterbrechung weiter.

Mein Container:

 

Die Backup-Strategie (3-Stufen-Konzept)

Die Sicherung erfolgt vollautomatisch über das Tool Restic, orchestriert durch Systemd-Timer auf dem Host und visualisiert durch Backrest im Docker-Container.

Stufe 1: Lokale SSD-Sicherung (6-stündlich)

  • Ziel: 500 GB USB-SSD (/mnt/restic-usb)

  • Intervall: Alle 6 Stunden via Systemd-Timer.

  • Besonderheit: Extrem schnelle Wiederherstellung von versehentlich gelöschten Dateien.

Stufe 2: NAS-Sicherung (Nächtlich)

  • Ziel: Synology NAS via NFS/SMB (/mnt/backup)

  • Intervall: Alle 24 Stunden, 3 Uhr nachts, via Systemd-Timer.
  • Workflow: Das System weckt das NAS per Wake-on-LAN (WOL), mountet das Dateisystem, führt den Restic-Snapshot aus und fährt das NAS danach wieder herunter. Sollte das Synology NAS bereits eingeschaltet gewesen sein, wird es natürlich nicht heruntergefahren.

  • Effizienz: Das NAS verbraucht nur Strom, wenn es wirklich benötigt wird.

Stufe 3: Monitoring & GUI

  • Tool: Backrest (Web-Interface für Restic)

  • Funktion: Visualisierung aller Snapshots, einfaches Browsen in Backups und schneller Datei-Restore ohne Kommandozeile.

The Ultimate Persistent NAS-Backup Orchestrator

/usr/local/bin/restic-backup-all.sh

#!/usr/bin/env bash
set -euo pipefail

source /etc/restic/env

LOG="/var/log/restic-backup-n-p.log"
LOCKFILE="/run/restic-backup-n-p.lock"

PAPERLESS="/srv/docker/paperless/docker-compose.yml"
NEXTCLOUD="/srv/docker/nextcloud/docker-compose.yml"

NAS_IP="192.168.1.5"
NAS_USER="credel"

# -------------------------
# NAS STATE (persistent)
# -------------------------
NAS_STATE_FILE="/run/restic-nas-started.lock"
NAS_STARTED_BY_SCRIPT=0
[[ -f "$NAS_STATE_FILE" ]] && NAS_STARTED_BY_SCRIPT=1

log() {
  echo "[$(date '+%F %T')] $*" | tee -a "$LOG"
}

fail() {
  log "ERROR: $*"
  exit 1
}

nas_is_up() {
  ssh -o BatchMode=yes \
      -o ConnectTimeout=2 \
      -o StrictHostKeyChecking=no \
      "$NAS_USER@$NAS_IP" "exit" >/dev/null 2>&1
}

start_nas_if_needed() {
  log "CHECK NAS STATE"

  if nas_is_up; then
    log "NAS already running - skip start"
    return 0
  fi

  log "START NAS (WOL)"
  echo "1" > "$NAS_STATE_FILE"
  NAS_STARTED_BY_SCRIPT=1

  /usr/local/bin/syno6x6-start.sh

  log "WAIT NAS READY"

  for i in {1..90}; do
    if nas_is_up; then
      log "NAS READY"

      log "NAS STORAGE CHECK"
      for j in {1..30}; do
        if mountpoint -q /mnt/backup && [[ -d /mnt/backup/restic ]]; then
          log "NAS STORAGE READY"
          break
        fi
        sleep 2
      done

      mountpoint -q /mnt/backup || fail "NAS storage not mounted"
      [[ -d /mnt/backup/restic ]] || fail "RESTIC repo missing"

      return 0
    fi
    sleep 2
  done

  fail "NAS not reachable after startup"
}

stop_nas_if_started_by_script() {
  if [[ "${NAS_STARTED_BY_SCRIPT:-0}" -ne 1 ]]; then
    log "NAS was already running → skip shutdown"
    return 0
  fi

  log "STOP NAS"

  ssh "$NAS_USER@$NAS_IP" \
    "echo '**************' | sudo -S /usr/syno/sbin/synoshutdown --shutdown" \
    >/dev/null 2>&1 || log "NAS shutdown failed or already offline"

  rm -f "$NAS_STATE_FILE"
}

cleanup() {
  log "START containers"

  docker compose -f "$PAPERLESS" start || true
  docker compose -f "$NEXTCLOUD" start || true

  sleep 10

  log "HEALTH CHECK"
  docker ps --format '{{.Names}} {{.Status}}' | tee -a "$LOG"

  stop_nas_if_started_by_script

  rm -f "$LOCKFILE"
}

trap cleanup EXIT

# -------------------------
# LOCK
# -------------------------
if [[ -e "$LOCKFILE" ]]; then
  fail "Backup already running"
fi

touch "$LOCKFILE"

log "BACKUP START"

# -------------------------
# NAS HANDLING
# -------------------------
start_nas_if_needed

# -------------------------
# PRECHECKS
# -------------------------
docker info >/dev/null 2>&1 || fail "Docker not running"
restic snapshots >/dev/null 2>&1 || fail "Restic repo not reachable"

# -------------------------
# STOP STACKS
# -------------------------

# -------------------------
# STOP STACKS (DETERMINISTIC FAST MODE)
# -------------------------

log "STOP containers (paperless + nextcloud)"

# 1) Paperless zuerst (kritisch, DB nah)
docker compose -f "$PAPERLESS" stop --timeout 30 || {
  log "WARNING: Paperless graceful stop failed → forcing"
  docker compose -f "$PAPERLESS" kill || true
}

# 2) Nextcloud Stack (worker + DB + redis)
docker compose -f "$NEXTCLOUD" stop --timeout 30 || {
  log "WARNING: Nextcloud graceful stop failed → forcing"
  docker compose -f "$NEXTCLOUD" kill || true
}

sleep 5

# 3) Targeted cleanup for known stubborn containers
for c in nc_cron nc_app nc_redis nc_db; do
  if docker ps --format '{{.Names}}' | grep -q "^${c}$"; then
    log "FORCE STOP (stubborn container): $c"
    docker stop --time=20 "$c" || docker kill "$c" || true
  fi
done

sleep 3

# 4) Final verification (non-blocking)
log "STOP VERIFY"
docker ps --format '{{.Names}} {{.Status}}' | tee -a "$LOG"

# -------------------------
# BACKUP CORE
# -------------------------
log "RESTIC BACKUP"

restic backup \
  /srv/docker/nextcloud \
  /srv/docker/paperless \
  /etc \
  /root \
  /home \
  /usr/local \
  /var/spool/cron \
  /var/lib/systemd

# -------------------------
# BACKUP ALL OTHER DOCKER CONTAINERS
# -------------------------
log "DOCKER CONTAINER EXPORT (NON CORE)"

docker ps --format '{{.Names}}' \
  | grep -v -E "paperless|nc_" \
  > /tmp/docker-containers.txt

restic backup /tmp/docker-containers.txt

for c in $(docker ps --format '{{.Names}}' | grep -v -E "paperless|nc_"); do
  docker inspect "$c" > "/tmp/docker-$c.json"
done

restic backup /tmp/docker-*.json || true

# -------------------------
# RETENTION
# -------------------------
log "RETENTION POLICY"

restic forget \
  --keep-daily 7 \
  --keep-weekly 4 \
  --keep-monthly 6 \
  --prune

log "BACKUP DONE"

/usr/local/bin/restic-backup-usb.sh

#!/bin/bash
set -euo pipefail

export RESTIC_REPOSITORY=/mnt/restic-usb/repo
export RESTIC_PASSWORD_FILE=/root/.restic/password-usb

# ???? WICHTIG: systemd environment fix
export HOME=/root
export XDG_CACHE_HOME=/root/.cache

LOGTAG="[RESTIC-USB]"

echo "$LOGTAG START $(date)"

# Safety check: USB muss gemountet sein
if ! mountpoint -q /mnt/restic-usb; then
  echo "$LOGTAG USB not mounted - abort"
  exit 1
fi

# Backup
restic backup \
  /etc \
  /home \
  /root \
  /srv/docker \
  /usr/local \
  --exclude "/srv/docker/*/tmp" \
  --exclude "/var/cache" \
  --exclude "/var/tmp" \
  --exclude "/swapfile"

# Retention: 1 Jahr
restic forget \
  --keep-daily 30 \
  --keep-weekly 12 \
  --keep-monthly 12 \
  --prune

echo "$LOGTAG DONE $(date)"

Zum Schluss noch:

Das „Efficiency-Tuning“ Logbuch

Die Fehlentscheidung: i5-9500 & B360M

  • Versprechen: 15W Idle.

  • Realität: ~20W Idle.

  • Lerneffekt: Neuere Consumer-Hardware ist nicht automatisch effizienter als optimierte Server-Hardware, wenn man die restlichen Komponenten (LVM, mehrere SSDs) einbezieht.

Energetischer Kontext (Balkonkraftwerk)

  • PV-Anlage: 2000W Peak / 6,3kWh Akku.

  • Grundlast Nacht: 75W gesamt.

  • Server-Anteil: 21W (~28%).

  • Fazit: Dank des großen Speichers wird der Server zu 100% autark durch Sonnenenergie betrieben, selbst in bewölkten Phasen reicht die Kapazität locker aus.

 

Ganz zum Schluss noch:

Dieses Setup hat mich nicht Stunden, es hat etliche Tage gebraucht um hier endlich an diesem für mich finalen Punkt anzukommen. Sicherlich wird hier und da noch zu optimieren sein. Vor allem muss ein (auch Desaster) Recovery simuliert und dokumentiert werden.

#