Die Lesezeichenleiste im Browser: Uptime Kuma, Jellyfin, Paperless, Pi-hole, Router, dann noch der Proxmox-Host, und irgendwann zeigt Firefox nur noch Pfeile, weil die Leiste voll ist. Auf dem Handy gibt es die Leiste gar nicht, da tippt man Adressen aus dem Gedächtnis. Und wenn jemand aus der Familie fragt, wo nochmal die Filme sind, zeigt man auf eine Zahl.

Homepage ist die Startseite fürs Homelab: eine einzige Adresse, dahinter alle Dienste als Kacheln mit Icon, Link und einem Punkt, der grün ist, wenn der Dienst antwortet. Dazu oben eine Zeile mit CPU, Arbeitsspeicher, Uhrzeit und Suchfeld. Konfiguriert wird sie in ein paar Textdateien, nicht per Klick, und genau das ist der Grund, warum sie sich sichern und umziehen lässt wie jeder andere Container auch.

Kurz gesagt: Am Ende läuft Homepage als Container auf Port 3000, zeigt deine Dienste in Gruppen mit Status-Punkt, oben CPU, RAM, Uhr und Suche, und ein Uptime-Kuma-Widget liefert die ersten Live-Zahlen. Dauer: rund 30 Minuten. Stufe: Standard, du kennst Docker und die Kommandozeile im Groben. Stand: Homepage v2.4.0, geprüft am 17-09-2026.
Schaubild: Homepage-Dashboard in drei Schritten: Compose-Datei mit HOMEPAGE_ALLOWED_HOSTS anlegen, Container starten und Port 3000 öffnen, services.yaml und widgets.yaml füllen
Der ganze Weg auf einen Blick: Compose anlegen, starten, Kacheln füllen.

Was Homepage ist

Homepage, im Netz meist „gethomepage“ nach der Adresse der Doku, ist ein Dashboard für Dienste, die man selbst betreibt. Das Projekt steht unter GPL-3.0, hat auf GitHub 32.700 Sterne und über 200 Beitragende, und die Doku zählt mehr als hundert Integrationen: kleine Widgets, die eine Kachel mit Live-Daten aus dem Dienst füllen, etwa wie viele Monitore in Uptime Kuma gerade grün sind oder wie viel Prozent der Anfragen Pi-hole heute geblockt hat. Version 2.4.0 ist von heute, dem 17-09-2026, und bringt laut Release Notes vor allem Fehlerbehebungen und aktualisierte Abhängigkeiten; die Anleitung gilt für jede 2.x.

Der Name ist ein Ärgernis, das sich niemand ausgesucht hat. Wer „homepage“ googelt, landet bei Baukästen und Telekom-Portalen; die Suchphrase, die wirklich zum Ziel führt, lautet „homepage dashboard“ oder „homepage docker compose“. Ich schreibe hier Homepage mit großem H und meine damit immer dieses Projekt.

Alles, was Homepage zeigt, steht in YAML-Dateien in einem Ordner: Welche Dienste, in welchen Gruppen, mit welchem Icon, welche Widgets oben. Es gibt keine Datenbank, keinen Einrichtungsassistenten und keine Anmeldeseite. Das klingt spartanisch, ist aber der Punkt: Der Ordner ist das ganze Dashboard, und ein Backup davon ist eine Kopie.

Browser→ Port 3000 →Homepage→ fragt alle paar Sekunden →Uptime Kuma, Pi-hole, Jellyfin …

Die Pfeile zeigen die Sache, die man beim ersten Widget verstehen muss: Nicht dein Browser holt die Zahlen bei Uptime Kuma, sondern der Homepage-Container. Die API-Schlüssel bleiben also auf dem Server, und der Container muss die anderen Dienste über das Netz erreichen, nicht dein PC.

Was du brauchst

  • Einen Linux-Rechner mit Docker und Docker Compose v2. Das Image gibt es für amd64 und arm64, ein Raspberry Pi 4 oder 5 reicht locker; Homepage ist eine statische Webseite mit einem kleinen Node-Server dahinter. Wer Docker noch nicht hat: Bei Paperless-ngx steht der Weg.
  • Port 3000 frei auf diesem Rechner.
  • Mindestens einen Dienst, der schon läuft und eine Kachel verdient. Für das Widget nehme ich Uptime Kuma mit einer eingerichteten Status-Seite; ohne Uptime Kuma lässt du den Widget-Teil einfach aus, der Rest geht genauso.
  • Einen Editor auf der Kommandozeile. Ich nehme nano, weil er auf fast jedem System schon da ist.
So liest du diese Anleitung: Alles, was du selbst tun sollst, steht in einem farbigen Kasten. [ per klick ] heißt: Das erledigst du mit der Maus in einer Web-Oberfläche, hier das Homepage-Dashboard im Browser. [ über die shell ] heißt: Den Befehl kopieren, ins Terminal einfügen, Enter. Stehen beide Kästen direkt untereinander, führen sie zum selben Ergebnis. Du gehst EINEN davon, nicht beide.

Die prüfbaren Punkte klärst du mit drei Befehlen auf dem Server:

docker --version
docker compose version
sudo ss -tulpn | grep ':3000 '

Die ersten beiden Zeilen liefern je eine Versionsnummer. Die dritte bleibt leer; steht dort eine Zeile, lauscht auf 3000 schon ein Programm, und wer das ist, findest du hier heraus. Alle Befehle rechnen mit denselben Beispielwerten; wo sie unten auftauchen, setzt du deine ein:

  • IP-Adresse des Docker-Rechners: 192.168.178.20
  • Adresse des Dashboards danach: http://192.168.178.20:3000
  • Ordner für Homepage: ~/homepage
  • Uptime Kuma: http://192.168.178.20:3001, Status-Seite mit dem Kürzel homelab
  • Pi-hole: http://192.168.178.53
  • Jellyfin: http://192.168.178.20:8096, Paperless-ngx: http://192.168.178.20:8000
  • Benutzer- und Gruppen-ID auf dem Server: 1000 und 1000

Schritt 1: Ordner und Compose-Datei anlegen

Homepage bekommt einen eigenen Ordner und darin einen Unterordner config. Die Doku sagt ausdrücklich, dass der Konfigurationsordner vorher existieren soll, und es gibt einen zweiten Grund, ihn selbst anzulegen: Dann gehört er dir und nicht root, und der Container darf gleich als dein Benutzer hineinschreiben.

mkdir -p ~/homepage/config
cd ~/homepage
id -u && id -g

Die letzten beiden Zahlen sind deine Benutzer- und Gruppen-ID. Beim ersten Benutzer eines Debian- oder Ubuntu-Systems sind das 1000 und 1000, so wie in den Beispielwerten. Stehen bei dir andere, merk sie dir für die nächste Datei.

Jetzt das Rezept. Es ist das Compose-Beispiel aus der Doku mit drei Änderungen: eine feste Versionsnummer statt latest, die Zeilen für den Benutzer, und die Zeile mit dem Docker-Socket habe ich weggelassen. Warum, steht gleich unter der Datei.

Datei anlegen:

nano docker-compose.yml

Inhalt einfügen, mit Strg+O und Enter speichern, mit Strg+X schließen:

services:
  homepage:
    image: ghcr.io/gethomepage/homepage:v2.4.0
    container_name: homepage
    ports:
      - 3000:3000
    volumes:
      - ./config:/app/config
    environment:
      HOMEPAGE_ALLOWED_HOSTS: 192.168.178.20:3000
      PUID: 1000
      PGID: 1000
    restart: unless-stopped

Ob Compose die Datei versteht, zeigt eine Probe ohne Start:

docker compose config --quiet && echo OK

Kommt OK, stimmt die Einrückung. Kommt eine Fehlermeldung mit Zeilennummer, ist es fast immer ein Leerzeichen zu viel oder zu wenig.

Die Zeile, an der die meisten hängen bleiben, ist HOMEPAGE_ALLOWED_HOSTS. Seit Version 1.0 nimmt Homepage nur Anfragen an, deren Adresszeile im Browser auf dieser Liste steht; localhost:3000 ist immer erlaubt, alles andere musst du eintragen, mit Port. Du wirst das Dashboard über 192.168.178.20:3000 aufrufen, also steht genau das dort. Später mehrere Adressen, etwa noch einen Namen vom Reverse Proxy? Dann mit Komma dazwischen und ohne Leerzeichen, so will es die Doku. Ein * schaltet die Prüfung ab; die Doku rät davon ab, und ich sehe zu Hause keinen Grund, das zu tun, weil die Liste ja nur zwei Einträge hat.

PUID und PGID lassen den Container als dein Benutzer laufen statt als root. Für Homepage ist das ein kleiner Schritt mit einem großen Nebeneffekt: Die Dateien in config gehören danach dir, du kannst sie ohne sudo bearbeiten, und ein Angreifer, der den Dashboard-Prozess übernimmt, ist nicht automatisch root auf dem Server. Die Doku warnt nur, dass die eingebundenen Ordner dann auch diesem Benutzer gehören müssen; dafür haben wir config oben selbst angelegt.

Und der Docker-Socket, die Zeile /var/run/docker.sock, die im Doku-Beispiel steht? Über ihn kann Homepage deine Container automatisch erkennen und ihren Zustand anzeigen. Die Doku nennt die Zeile optional, und wer dem Dashboard den Socket gibt, gibt ihm die Kontrolle über alle Container auf dem Rechner. Die Doku selbst empfiehlt dafür einen Socket-Proxy, der nur lesende Anfragen durchlässt. Das ist ein eigener Container und ein eigener Beitrag; heute bleibt der Socket draußen, und alles in dieser Anleitung funktioniert ohne ihn.

Schritt 2: Starten und die Beispielseite ansehen

docker compose up -d
docker compose ps

In der Spalte STATUS muss Up stehen. Beim ersten Start legt Homepage alle Konfigurationsdateien, die noch fehlen, aus seinen Vorlagen an; im Quelltext heißt der Ordner dafür „skeleton“. Ein Blick in den Ordner zeigt sie:

ls config

Vier davon fasst du gleich an: settings.yaml für Titel und Sprache, services.yaml für die Kacheln, widgets.yaml für die Kopfzeile, bookmarks.yaml für Links ohne Kachel. Die anderen, etwa docker.yaml oder kubernetes.yaml, brauchst du heute nicht; sie dürfen liegen bleiben. Jetzt der erste Aufruf:

Im Browser http://192.168.178.20:3000 öffnen. Homepage zeigt eine Beispielseite: oben eine Zeile mit CPU, Arbeitsspeicher und Festplatte sowie ein Suchfeld, darunter die Gruppen My First Group, My Second Group und My Third Group mit je einer Kachel: My First Service (Homepage is awesome), My Second Service (Homepage is the best) und My Third Service (Homepage is 😎). Unter den drei Gruppen folgen die Lesezeichen-Gruppen Developer, Social und Entertainment mit Github, Reddit und YouTube. Stand Homepage v2.4.

Die Beispielseite von Homepage: oben CPU, Arbeitsspeicher, Festplatte und ein Suchfeld, darunter die Gruppen My First Group, My Second Group und My Third Group mit je einer Kachel.
(1) Suchfeld; (2) My First Group; (3) Homepage is awesome

Die Beispielseite ist der Beweis, dass alles zusammenpasst: Container läuft, Port ist offen, die Adresse steht auf der erlaubten Liste. Kommt stattdessen eine Fehlerseite mit „Host validation failed“, steht die Lösung unten bei „Wo es klemmt“, und sie ist kurz.

Schritt 3: Die Kacheln, services.yaml

Die Datei ist eine Liste von Gruppen, jede Gruppe eine Liste von Diensten, jeder Dienst ein Block mit Adresse, Beschreibung und Icon. Die Einrückung ist die Grammatik: vier Leerzeichen vor dem Dienst, acht vor seinen Feldern. Wir ersetzen die drei Beispielgruppen komplett.

Datei öffnen, den gesamten Inhalt löschen (in nano: Strg+K löscht die aktuelle Zeile, so oft, bis nichts mehr da ist):

nano config/services.yaml

Diesen Inhalt einfügen, mit Strg+O und Enter speichern, mit Strg+X schließen:

- Überwachung:
    - Uptime Kuma:
        href: http://192.168.178.20:3001
        description: Wachdienst für alle Dienste
        icon: uptime-kuma.png
        siteMonitor: http://192.168.178.20:3001
        widget:
          type: uptimekuma
          url: http://192.168.178.20:3001
          slug: homelab

- Netz:
    - Pi-hole:
        href: http://192.168.178.53/admin
        description: DNS und Werbefilter
        icon: pi-hole.png
        siteMonitor: http://192.168.178.53/admin

- Medien und Dokumente:
    - Jellyfin:
        href: http://192.168.178.20:8096
        description: Filme und Serien
        icon: jellyfin.png
        siteMonitor: http://192.168.178.20:8096
    - Paperless-ngx:
        href: http://192.168.178.20:8000
        description: Alle Dokumente, durchsuchbar
        icon: paperless-ngx.png
        siteMonitor: http://192.168.178.20:8000

Im Browser die Seite http://192.168.178.20:3000 neu laden (F5). Statt der Beispielgruppen stehen jetzt Überwachung, Netz und Medien und Dokumente mit ihren Kacheln; rechts oben in jeder Kachel ein kleiner Punkt, grün, wenn der Dienst antwortet. In der Kachel Uptime Kuma stehen zusätzlich Zahlen zu Up, Down und Uptime. Die Lesezeichen-Gruppen Developer, Social und Entertainment bleiben darunter stehen, denn sie kommen aus bookmarks.yaml und nicht aus services.yaml. Stand Homepage v2.4.

Vier Felder tragen die Datei. href ist der Link, den ein Klick auf die Kachel öffnet. siteMonitor ist die Adresse, die Homepage regelmäßig anfragt, um den Punkt zu färben, meist dieselbe; die Doku misst dabei auch die Antwortzeit. icon nimmt einen Dateinamen aus der Sammlung „Dashboard Icons“, die Homepage kennt, ohne dass du etwas herunterlädst; die vier oben gibt es dort, und für jeden anderen Dienst schaust du im Icon-Verzeichnis nach dem Namen. Wer lieber ohne Bild arbeitet, lässt die Zeile weg.

Der widget-Block bei Uptime Kuma ist das erste Live-Widget. Uptime Kuma hat laut Homepage-Doku noch keine vollständige API, deshalb liest das Widget eine Status-Seite aus: slug ist der Teil der Adresse nach /status/, bei http://192.168.178.20:3001/status/homelab also homelab. Ohne Status-Seite in Uptime Kuma bleibt das Widget leer, dann lässt du den Block weg und die Kachel ist eine normale mit Punkt. Ein Pi-hole-Widget gibt es auch, mit type: pihole und für Pi-hole 6 zwingend version: 6; es zeigt Anfragen und Blockquote, braucht aber ein App-Passwort aus Pi-hole. Das hebe ich mir für den Abschnitt zu den Schlüsseln auf.

Eine Sache zum Neuladen, weil sie in den Discussions auf GitHub ständig gefragt wird: Für services.yaml reicht F5 im Browser. Für settings.yaml sagt die Doku, dass die statische Seite neu erzeugt werden muss, und dafür gibt es unten rechts auf dem Dashboard ein Aktualisieren-Symbol. Ein Neustart des Containers ist laut Doku nur nötig, wenn du eigene Bilddateien hinzufügst.

Schritt 4: Die Kopfzeile, widgets.yaml und settings.yaml

Die Kopfzeile kommt aus widgets.yaml. Die Vorlage zeigt schon CPU, RAM und Festplatte plus DuckDuckGo-Suche; wir ergänzen die Uhr und stellen die Suche auf Deutsch ein.

Datei öffnen, Inhalt löschen (Strg+K zeilenweise), Neues einfügen, Strg+O, Enter, Strg+X:

nano config/widgets.yaml
- resources:
    cpu: true
    memory: true
    disk: /
    uptime: true

- datetime:
    text_size: xl
    locale: de
    format:
      dateStyle: short
      timeStyle: short
      hourCycle: h23

- search:
    provider: duckduckgo
    target: _blank

Zwei Dinge, die man wissen sollte. Das resources-Widget misst nicht deinen Server, sondern das, was der Container sieht; CPU und RAM sind dabei die des Hosts, aber disk: / ist die Platte, wie sie im Container aussieht. Willst du eine bestimmte Platte sehen, muss ihr Pfad laut Doku als Volume in den Container gereicht werden. Für den Anfang reicht /. Und die Uhr: locale: de mit hourCycle: h23 ergibt 17.09.26, 15:03 statt 3:03 PM; die Format-Felder gehen laut Doku direkt an die Datumsfunktion des Browsers, deshalb heißen sie so englisch.

Jetzt settings.yaml. Die Vorlage enthält nur auskommentierte Zeilen für Wetter-Schlüssel; wir hängen drei Zeilen an das Ende.

Datei öffnen, mit Strg+Ende ans Ende springen, diese Zeilen anhängen, Strg+O, Enter, Strg+X:

nano config/settings.yaml
title: Homelab
language: de
theme: dark

Auf dem Dashboard unten rechts das kleine Aktualisieren-Symbol (ein runder Pfeil) anklicken, dann die Seite neu laden. Der Browser-Tab heißt jetzt Homelab, die Kopfzeile zeigt Datum und Uhrzeit im deutschen Format, und die Beschriftungen der Widgets sind auf Deutsch. Stand Homepage v2.4.

Das Dashboard mit dem Titel Homelab, in der Kopfzeile CPU, freier Speicher, Betriebszeit und Datum mit Uhrzeit im deutschen Format, unten rechts das Aktualisieren-Symbol.
Aktualisieren, dann neu laden

theme: dark nimmt den Umschalter für Hell und Dunkel aus der Seite; wer ihn behalten will, lässt die Zeile weg. Wer mag, legt noch bookmarks.yaml an, das Format ist dasselbe Spiel mit Gruppen, nur ohne Kachel: ein Kürzel aus zwei Buchstaben oder ein Icon, dazu die Adresse. Das ist der Platz für den Router, das Proxmox-Login und die Seiten, die man dreimal am Tag braucht.

Was am Ende eingestellt ist

1. Es gibt keine Anmeldung, und das ist so gedacht. Homepage hat keine Benutzerverwaltung und keine Anmeldeseite; wer die Adresse im Heimnetz kennt, sieht das Dashboard mit allen Kacheln und Widget-Zahlen. Im eigenen LAN ist das für mich in Ordnung, Uptime-Prozente und Pi-hole-Statistiken sind keine Geheimnisse. Ins Internet gehört diese Seite so aber nicht. Wer sie von außen will, legt sie hinter einen Reverse Proxy mit einer Anmeldung davor, und dann kommt der Name des Proxys in HOMEPAGE_ALLOWED_HOSTS.

2. Die Schlüssel liegen im Klartext in config. Sobald das erste Widget einen API-Schlüssel braucht, Jellyfin etwa oder Pi-hole mit App-Passwort, steht er als Text in services.yaml. Die Doku bietet dafür Platzhalter an: In der Compose-Datei eine Variable mit dem Präfix HOMEPAGE_VAR_, in der YAML-Datei dann {{HOMEPAGE_VAR_JELLYFIN_KEY}}. Das verschiebt das Geheimnis nur von einer Datei in die andere, aber es trennt Konfiguration, die man zeigen kann, von Schlüsseln, die man nicht zeigt. Wer die YAML-Dateien in ein Git-Repository legt, will genau das.

3. Der Container sieht Docker nicht. Ohne Socket weiß Homepage nichts von deinen Containern; der Status-Punkt kommt allein von siteMonitor, also davon, ob die Web-Adresse antwortet. Für die meisten Dienste ist das die ehrlichere Messung: Ein Container kann laufen und trotzdem keine Seite liefern. Willst du Container-Status und CPU pro Container sehen, ist der Socket-Proxy aus der Doku der Weg, nicht der nackte Socket.

4. Updates machst du von Hand. Die Versionsnummer v2.4.0 steht fest in der Compose-Datei. Ein Update ist: Release Notes lesen, Nummer ändern, ziehen, neu starten.

cd ~/homepage
nano docker-compose.yml

In der Zeile image: die Versionsnummer ersetzen, Strg+O, Enter, Strg+X. Dann:

docker compose pull && docker compose up -d
docker compose ps

Warum nicht latest: Der Sprung auf 1.0 hat HOMEPAGE_ALLOWED_HOSTS zur Pflicht gemacht, und wer das per latest mitgenommen hat, stand vor einem Dashboard, das plötzlich nur noch eine Fehlermeldung zeigte. Die Discussions dazu auf GitHub sind lang. Wer den Handgriff sparen will, nimmt v2 als Tag: Das gibt es auf der Registry, es folgt allen 2.x-Versionen und bleibt vor dem nächsten großen Sprung stehen.

5. Das Backup ist ein Ordner. ~/homepage mit docker-compose.yml und config ist alles, was Homepage ausmacht. Kopie davon in dein Backup, fertig; wer Backrest oder etwas Ähnliches laufen hat, nimmt den Ordner mit auf. Wiederherstellen heißt: Ordner zurück, docker compose up -d.

Wo es klemmt

„Host validation failed for: 192.168.178.20:3000. Hint: Set HOMEPAGE_ALLOWED_HOSTS to allow requests from this host.“ Das ist die häufigste Meldung in den Discussions des Projekts, und sie sagt schon, was fehlt: Die Adresse, die du im Browser aufgerufen hast, steht nicht in der Liste, mit Port. Ein Fall vom Frühjahr 2025 zeigt die zweite Falle: Der Nutzer hatte die Adresse eingetragen, aber mit einem Leerzeichen nach dem Komma, und die Antwort des Projekts war, dass die Liste ohne Leerzeichen sein muss und der Container danach neu erzeugt werden will. Umgebungsvariablen liest Homepage nur beim Start.

In docker-compose.yml die Zeile HOMEPAGE_ALLOWED_HOSTS prüfen, speichern, dann den Container neu erzeugen:

cd ~/homepage
docker compose up -d --force-recreate

Die Seite lädt, aber eine Gruppe fehlt oder alles ist leer. Fast immer ein YAML-Fehler: ein fehlender Doppelpunkt, ein Tab statt Leerzeichen, ein Dienstname mit Sonderzeichen ohne Anführungszeichen. Die Doku rät, Zeichenketten mit Sonderzeichen in Anführungszeichen zu setzen, und das gilt besonders für API-Schlüssel. Was Homepage an der Datei stört, steht im Log:

docker compose logs --tail 50 homepage

Dieselben Zeilen landen laut Doku auch in config/logs/homepage.log.

Das Widget zeigt „API Error“. Der Knopf mit den Fehlerdetails im Widget selbst ist laut Doku der erste Blick. Zwei Ursachen führen die Troubleshooting-Seite an: Die url im Widget endet auf einen Schrägstrich, das mag Homepage nicht, jedes Widget hängt seinen eigenen Pfad an. Oder der Container kommt nicht an den Dienst, obwohl dein PC ihn erreicht; das prüfst du aus dem Container heraus:

docker exec homepage ping -c 3 192.168.178.53

Antwortet der Ping nicht, liegt es am Netz zwischen den Containern, nicht an Homepage. Bei Uptime Kuma kommt eine dritte Ursache dazu, die im Widget nicht steht: Die Status-Seite mit dem slug muss existieren und mindestens einen Monitor enthalten.

Ein eigenes Icon aus config/icons erscheint nicht. Hier hilft nur ein Neustart des Containers; die Doku nennt das eine Grenze des statischen Servers, den Homepage benutzt. docker compose restart homepage, und das Bild ist da.

Was bleibt, ist eine Adresse. Eine, die man der Familie sagen kann, ohne eine Zahl zu nennen, und auf der ein grüner Punkt mehr über den Zustand des Homelabs erzählt als jede Portnummer. Dass die ganze Einrichtung aus vier Textdateien besteht, hat mich am Anfang gestört und ist inzwischen der Grund, warum ich sie mag: Ich weiß, wo alles steht.

Welche Kachel wandert bei dir als Erstes aufs Dashboard, und welches Widget fehlt dir in der Liste der Integrationen? Schreib es in die Kommentare; ich sammle die Wünsche für den Beitrag zum Socket-Proxy und den Live-Widgets.

Wenn Uptime Kuma bei dir noch fehlt und die Widget-Kachel deshalb leer blieb: Uptime Kuma per Docker steht in einer halben Stunde, und die Status-Seite dafür ist dort ein Klick.

Quellen