Semaphore UI auf dem Synology NAS installieren: Web-Oberfläche für Ansible
Semaphore UI bringt Ansible in den Browser: Playbooks per Klick starten, SSH-Keys sicher in der Datenbank speichern, Inventory verwalten – alles ohne Kommandozeile. Diese Anleitung zeigt, wie du Semaphore UI als Compose-Projekt mit PostgreSQL auf dem Synology NAS einrichtest.

Wer regelmäßig mehrere Linux-Server verwaltet, kennt das Szenario: Ansible-Playbooks liegen irgendwo in einem Git-Repository, die Ausführung erfolgt per Kommandozeile auf dem eigenen Laptop – und sobald ein Kollege einen Update-Lauf starten soll, beginnt die Erklärung von vorn. Semaphore UI löst genau dieses Problem. Die browserbasierte Web-Oberfläche macht Ansible bedienbar, ohne dass jeder Mitarbeiter die CLI kennen muss: Playbooks starten, Ausführungshistorie einsehen, SSH-Keys sicher hinterlegen und Zugriffsrechte per Benutzer-/Team-Modell vergeben – alles über den Browser. Das Synology NAS bietet sich als dauerläufiger Host an: Es steht im Heimnetz oder Büro, zieht wenig Strom und Docker läuft dank Container Manager out of the box. So wird das NAS zur stillen Automatisierungszentrale, die Server-Updates, Compliance-Checks und Konfigurationsmanagement ohne zusätzlichen Cloud-Dienst übernimmt. Semaphore UI unterstützt neben Ansible auch Terraform, OpenTofu und PowerShell – der Einstieg lohnt sich also weit über Ansible hinaus.
Voraussetzungen
- Synology NAS mit DSM 7.2 oder neuer, x86_64-Architektur (z. B. DS920+, DS923+, DS1522+) oder ARM64 (z. B. DS423+)
- Container Manager aus dem Package Center installiert
- Mindestens 2 GB freier RAM (empfohlen: 4 GB, damit NAS-Betrieb, PostgreSQL und Semaphore parallel laufen)
- Mindestens 5 GB freier Speicher auf
/volume1für Container-Images und Datenbankdaten - SSH-Zugang zum NAS aktiviert (Schritt für Schritt: SSH auf dem Synology NAS aktivieren und verbinden)
- Einen Linux-Zielhost mit aktiviertem SSH-Zugang für den ersten Ansible-Test
- Einen SSH-Keypair (ed25519 empfohlen) für Ansible-Verbindungen zum Zielhost
Schritt 1: Ordnerstruktur anlegen und Verschlüsselungsschlüssel generieren
Semaphore UI benötigt vier Host-Verzeichnisse, die vor dem ersten Container-Start existieren müssen. Fehlen sie, kann PostgreSQL sein Datenverzeichnis nicht initialisieren und der Start schlägt still fehl. Öffne eine SSH-Verbindung zum NAS (Details: SSH auf dem Synology NAS aktivieren und verbinden) und lege die Ordner an:
mkdir -p /volume1/docker/semaphore/db
mkdir -p /volume1/docker/semaphore/data
mkdir -p /volume1/docker/semaphore/config
mkdir -p /volume1/docker/semaphore/temp
chmod 755 /volume1/docker/semaphore/db
chmod 755 /volume1/docker/semaphore/data
chmod 755 /volume1/docker/semaphore/config
chmod 755 /volume1/docker/semaphore/temp
Generiere jetzt den Verschlüsselungsschlüssel für SSH-Keys und Passwörter in der Datenbank. Dieser Wert darf nach dem ersten Start nie mehr geändert werden, da sonst alle gespeicherten Credentials unlesbar werden:
head -c32 /dev/urandom | base64
Der Befehl gibt einen 44-stelligen Base64-String aus, zum Beispiel:
k7Xv2pN1mQwRzA8uLsTjHcYdFbGnEoKi4e5f6g7h8=
Kopiere diesen Wert in die Zwischenablage – du benötigst ihn im nächsten Schritt. Speichere ihn zusätzlich an einem sicheren Ort (z. B. Passwortmanager), da er für die Wiederherstellung aus einem Backup unbedingt benötigt wird.
Lege außerdem die requirements.txt an, damit Ansible im Container verfügbar ist. Das offizielle :latest-Image enthält Ansible nicht vorinstalliert – ohne diese Datei schlägt der erste Playbook-Run mit ansible: command not found fehl:
echo "ansible" > /volume1/docker/semaphore/config/requirements.txt
Verifizieren: Prüfe, ob alle vier Ordner angelegt wurden und die Datei existiert:
ls -la /volume1/docker/semaphore/
cat /volume1/docker/semaphore/config/requirements.txt
Erwartete Ausgabe: Vier Unterordner (db, data, config, temp) mit Berechtigung drwxr-xr-x sowie ansible als Inhalt der requirements.txt.
Schritt 2: compose.yaml erstellen
Erstelle die Compose-Datei unter /volume1/docker/semaphore/compose.yaml. Ersetze die Platzhalter-Passwörter durch eigene sichere Werte und füge den generierten Schlüssel aus Schritt 1 ein. Passwörter mit Sonderzeichen wie ! oder @ müssen in Anführungszeichen stehen, sonst entsteht ein YAML-Parse-Fehler.
services:
semaphore-db:
image: postgres:14
container_name: Semaphore-DB
hostname: semaphore-db
restart: unless-stopped
volumes:
- /volume1/docker/semaphore/db:/var/lib/postgresql/data
environment:
POSTGRES_USER: semaphore
POSTGRES_PASSWORD: "SicheresDBPasswort123!"
POSTGRES_DB: semaphore
healthcheck:
test: ["CMD-SHELL", "pg_isready -U semaphore"]
interval: 10s
timeout: 5s
retries: 5
semaphore:
image: semaphoreui/semaphore:latest
container_name: Semaphore-UI
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- /volume1/docker/semaphore/data:/var/lib/semaphore
- /volume1/docker/semaphore/config:/etc/semaphore
- /volume1/docker/semaphore/temp:/tmp/semaphore
environment:
SEMAPHORE_DB_USER: semaphore
SEMAPHORE_DB_PASS: "SicheresDBPasswort123!"
SEMAPHORE_DB_HOST: semaphore-db
SEMAPHORE_DB_PORT: "5432"
SEMAPHORE_DB_DIALECT: postgres
SEMAPHORE_DB: semaphore
SEMAPHORE_PLAYBOOK_PATH: /tmp/semaphore/
SEMAPHORE_ADMIN: admin
SEMAPHORE_ADMIN_PASSWORD: "SicheresAdminPasswort456!"
SEMAPHORE_ADMIN_NAME: Admin
SEMAPHORE_ADMIN_EMAIL: admin@localhost
SEMAPHORE_ACCESS_KEY_ENCRYPTION: "HIER_DEN_BASE64_SCHLUESSEL_EINTRAGEN"
SEMAPHORE_LDAP_ACTIVATED: "no"
TZ: Europe/Berlin
depends_on:
semaphore-db:
condition: service_healthy
Drei Designentscheidungen dieser Konfiguration verdienen eine kurze Erklärung: Erstens verwendet die Anleitung postgres:14 statt neuerer Tags – das ist das von der offiziellen Semaphore-Dokumentation referenzierte Image und damit die am besten getestete Kombination. Zweitens bewirkt depends_on: condition: service_healthy zusammen mit dem healthcheck auf dem Datenbank-Container, dass Semaphore erst startet, wenn PostgreSQL wirklich bereit ist – Race-Condition-Fehler beim ersten Hochfahren werden so zuverlässig verhindert. Drittens stellt restart: unless-stopped sicher, dass beide Container nach einem NAS-Neustart automatisch wieder starten.
Eine Übersicht aller relevanten Eckdaten:
| Parameter | Wert | Hinweis |
|---|---|---|
| Image | semaphoreui/semaphore:latest | Stabile Alternative: v2.18.12 pinnen |
| DB-Image | postgres:14 | Offiziell empfohlen |
| Host-Port | 3000 | Alternativ 4448 bei Kollision |
| Container-Port | 3000 | Intern fest verdrahtet |
| Image-Größe | ca. 463 MB | amd64 und arm64 unterstützt |
| Datenbank | PostgreSQL (Pflicht für Produktion) | BoltDB/SQLite ab v2.16 für Tests |
| Volume (Host-Pfad) | Container-Pfad | Inhalt |
|---|---|---|
| /volume1/docker/semaphore/db | /var/lib/postgresql/data | PostgreSQL-Datenbankdateien |
| /volume1/docker/semaphore/data | /var/lib/semaphore | Semaphore-Laufzeitdaten |
| /volume1/docker/semaphore/config | /etc/semaphore | Konfiguration und requirements.txt |
| /volume1/docker/semaphore/temp | /tmp/semaphore | Temporäre Playbook-Arbeitsdateien |
Verifizieren: Überprüfe die YAML-Syntax, bevor du den Stack startest:
cat /volume1/docker/semaphore/compose.yaml
Die Datei sollte vollständig und ohne Tabulatoren als Einrückung ausgegeben werden. Besonders wichtig: Der Wert von SEMAPHORE_ACCESS_KEY_ENCRYPTION darf nicht mehr der Platzhaltertext sein.
Schritt 3: Stack im Container Manager starten
Öffne den Container Manager in DSM. So legst du das Compose-Projekt an (ausführliche Grundlagen: Container Manager: Docker-Compose-Projekt anlegen, starten und aktualisieren):
- Wähle im linken Menü Projekt und klicke auf Erstellen.
- Vergib den Projektnamen
semaphore(Kleinschreibung). - Wähle als Pfad
/volume1/docker/semaphore– Container Manager erkennt diecompose.yamlautomatisch. - Klicke auf Weiter und dann auf Ausführen. DSM lädt die Images und startet beide Container.
Der erste Start dauert länger, da postgres:14 (ca. 90 MB) und semaphoreui/semaphore:latest (ca. 463 MB) heruntergeladen werden müssen. Beim allerersten Hochfahren installiert Semaphore außerdem Ansible aus der requirements.txt – das kann weitere ein bis zwei Minuten in Anspruch nehmen.
Wichtig: Die Bootstrap-Variablen SEMAPHORE_ADMIN* werden nur beim ersten Start ausgewertet. Nach der Erstinitialisierung lässt sich das Admin-Passwort ausschließlich über die Web-UI ändern.
Verifizieren: Prüfe den Status beider Container im Container Manager – beide müssen den Status Aktiv (grüner Punkt) zeigen. Zusätzlich kannst du per SSH die Logs einsehen:
docker logs Semaphore-UI --tail 30
Erwartete Ausgabe: Zeilen wie Loading config, Postgres connected und Server is running. Fehlermeldungen wie connection refused oder encryption key is not set deuten auf Konfigurationsprobleme hin (siehe Troubleshooting).
Schritt 4: Erster Login und Passwort ändern
Öffne deinen Browser und rufe http://NAS-IP:3000 auf. Melde dich mit dem Bootstrap-Admin an:
- Benutzername:
admin(Wert ausSEMAPHORE_ADMIN) - Passwort: der Wert aus
SEMAPHORE_ADMIN_PASSWORD
Nach dem ersten Login solltest du das Admin-Passwort sofort in der Web-UI ändern: Klicke oben rechts auf den Benutzernamen → Edit Profile → Change Password. Setze außerdem deine echte E-Mail-Adresse ein, damit Benachrichtigungen korrekt zugestellt werden können.
Für den dauerhaften Zugang empfiehlt sich ein Reverse Proxy über das DSM-Anwendungsportal. Das macht HTTPS möglich und erlaubt einen sauberen Hostnamen statt einer IP. Aktiviere dabei unbedingt die WebSocket-Unterstützung (Upgrade- und Connection-Header), da das Live-Log-Streaming im Browser WebSocket-Verbindungen verwendet. Eine detaillierte Anleitung findest du unter Nginx Proxy Manager auf der Synology mit Container Manager einrichten.
Verifizieren: Du siehst nach dem Login das Semaphore-Dashboard mit den Menüpunkten Projects, Key Store, Team Members und Activity. Die Versionsnummer wird unten links angezeigt und sollte v2.18.x lauten.
Schritt 5: SSH-Key hinterlegen
Ansible benötigt einen SSH-Schlüssel, um sich bei Zielhosts anzumelden. Semaphore speichert private Keys verschlüsselt in der PostgreSQL-Datenbank – kein Plaintext auf dem Dateisystem. Navigiere im Web-UI zu Key Store → New Key:
- Key Name: z. B.
ansible-homelab - Type:
SSH Key - Füge deinen privaten SSH-Schlüssel (ed25519 empfohlen) in das Textfeld ein – der komplette Inhalt deiner
~/.ssh/id_ed25519-Datei inklusive der-----BEGIN OPENSSH PRIVATE KEY------Zeilen. - Optional: Passphrase, falls der Key damit gesichert ist.
- Klicke auf Save.
Du hast noch kein SSH-Keypair? Erstelle eines auf dem NAS per SSH:
ssh-keygen -t ed25519 -C "semaphore-ansible" -f ~/.ssh/id_ed25519_semaphore
cat ~/.ssh/id_ed25519_semaphore.pub
Kopiere den Public Key auf den Zielhost:
ssh-copy-id -i ~/.ssh/id_ed25519_semaphore.pub benutzer@ZIELHOST-IP
Weitere Details zu SSH-Keys und sicherer Konfiguration findest du in SSH-Hardening Deep-Dive: sshd_config, Match-Blöcke und ssh-audit für KMU-Server.
Verifizieren: Im Key Store erscheint der neue Eintrag mit dem Typ SSH Key und einem grünen Häkchen. Der private Schlüssel ist nicht mehr im Klartext lesbar – das zeigt, dass die Verschlüsselung per SEMAPHORE_ACCESS_KEY_ENCRYPTION funktioniert.
Schritt 6: Inventory, Repository und erstes Playbook einrichten
Bevor du einen Task starten kannst, müssen drei weitere Objekte in Semaphore angelegt werden: ein Projekt, ein Inventory und ein Repository. Navigiere zunächst zu Projects → New Project und vergib einen Namen, z. B. Homelab.
Innerhalb des Projekts:
- Inventory anlegen: Wähle Inventory → New Inventory. Wähle den Typ
Staticund trage die IP-Adresse deines Zielhosts ein:
[linux]
192.168.1.100 ansible_user=ubuntu
- Repository anlegen: Wähle Repositories → New Repository. Trage eine Git-URL ein oder nutze für einen schnellen Test den Pfad
/tmp/semaphoreals Local Path. Für ein lokales Test-Playbook legst du es direkt unter/volume1/docker/semaphore/temp/ab – dieses Verzeichnis ist als Volume in/tmp/semaphoregemountet. - Task Template erstellen: Wähle Task Templates → New Template. Wähle Inventory, Repository und SSH-Key aus den Dropdowns. Trage den Dateinamen des Playbooks ein, z. B.
ping.yml.
Ein einfaches Test-Playbook zum Ablegen unter /volume1/docker/semaphore/temp/ping.yml:
---
- name: Ping-Test
hosts: linux
gather_facts: false
tasks:
- name: Ping
ansible.builtin.ping:
Starte das Template über Run und beobachte das Live-Log. Das Ergebnis zeigt dir in Echtzeit, ob Ansible den Zielhost erreicht und der SSH-Key akzeptiert wurde.
Semaphore kann auch als Automatisierungszentrale für regelmäßige Server-Updates dienen – kombiniert mit einem einfachen Update-Playbook und dem eingebauten Scheduler ersetzt das aufwändige manuelle SSH-Runden. Für die Grundlagen der Skript-Automatisierung empfiehlt sich ein Blick auf Bash-Skripting für Admins – die Grundlagen. Wer Playbooks direkt auf dem NAS entwickeln und testen möchte, findet in code-server auf dem Synology NAS eine komfortable VS-Code-Oberfläche im Browser.
Verifizieren: Im Live-Log erscheinen grüne ok-Zeilen für den Ping-Task und am Ende PLAY RECAP mit failed=0. Ein roter UNREACHABLE-Eintrag deutet auf ein SSH- oder Netzwerkproblem hin – prüfe dann den SSH-Key und die Inventory-IP.
Schritt 7: DSM-Firewall freigeben (optional, aber empfohlen)
Wenn die DSM-Firewall aktiv ist, muss Port 3000 TCP für das lokale Netz freigegeben werden. Die ausführliche Anleitung dazu: DSM-Firewall auf dem Synology NAS: Ports für Docker-Container freigeben. Kurz zusammengefasst: Systemsteuerung → Sicherheit → Firewall → Regel hinzufügen → Port 3000, Protokoll TCP, Quelle: dein lokales Subnetz (z. B. 192.168.1.0/24).
Nutzt du statt Port 3000 den alternativen Port 4448 (sinnvoll, falls Port 3000 durch ein anderes DSM-Paket belegt ist), passe das Port-Mapping in der compose.yaml entsprechend an: "4448:3000".
Verifizieren: Öffne Semaphore von einem anderen Gerät im gleichen Netz aus dem Browser. Ist der Login erreichbar, ist die Firewall-Regel korrekt gesetzt.
Troubleshooting / Typische Fehler
- „encryption key is not set" / SSH-Keys lassen sich nicht speichern:
SEMAPHORE_ACCESS_KEY_ENCRYPTIONist leer oder enthält noch den Platzhaltertext. Generiere den Schlüssel mithead -c32 /dev/urandom | base64und trage ihn in dercompose.yamlein. Achtung: Wird der Schlüssel nach dem ersten Start geändert, sind alle bereits gespeicherten Keys unlesbar. - „connection refused" beim Start von Semaphore-UI: PostgreSQL war noch nicht bereit. Überprüfe, ob der
healthcheck-Block imsemaphore-db-Service korrekt eingetragen ist und obdepends_on: condition: service_healthybeimsemaphore-Service gesetzt ist. - „ansible: command not found" beim ersten Playbook-Run: Das offizielle
:latest-Image enthält kein Ansible. Stelle sicher, dass/volume1/docker/semaphore/config/requirements.txtdie Zeileansibleenthält und das Verzeichnis korrekt als Volume gemountet ist. Starte den Container danach neu. - PostgreSQL-Container startet nicht: Das Host-Verzeichnis
/volume1/docker/semaphore/dbexistiert nicht oder hat falsche Rechte. Lege es mitmkdir -pundchmod 755neu an und starte den Stack erneut. - „no such host" beim DB-Verbindungsversuch:
SEMAPHORE_DB_HOSTmuss exakt mit demhostname-Wert des Datenbank-Containers übereinstimmen. In dieser Anleitung ist dassemaphore-db– ein Tippfehler hier führt zu DNS-Auflösungsfehlern innerhalb des Docker-Netzwerks. - Live-Log-Streaming bricht nach wenigen Sekunden ab: Der Reverse Proxy überträgt die WebSocket-Verbindung nicht weiter. Im DSM-Anwendungsportal müssen die Header
Upgrade: $http_upgradeundConnection: upgradegesetzt sein – dafür gibt es einen expliziten WebSocket-Schalter. - YAML-Parse-Fehler beim Stack-Start: Passwörter mit Sonderzeichen wie
!,@oder#müssen in dercompose.yamlin doppelte Anführungszeichen eingeschlossen sein, sonst interpretiert der YAML-Parser sie als Sonderzeichen. - Port 3000 bereits belegt: Manche DSM-Versionen nutzen Port 3000 für Node.js-Pakete. Ändere das Port-Mapping auf
"4448:3000"und passe die Firewall-Regel entsprechend an.
Häufige Fragen
Kann ich Semaphore ohne PostgreSQL betreiben?
Ja – für lokale Tests reicht BoltDB (eingebettete Datenbank, kein separater Container nötig). Setze dazu SEMAPHORE_DB_DIALECT: bolt und entferne den semaphore-db-Service aus der Compose-Datei. Ab Semaphore v2.16 ist außerdem SQLite als dritte Option verfügbar. Für dauerhaften Produktivbetrieb auf dem NAS ist PostgreSQL jedoch die richtige Wahl: Die Daten lassen sich mit pg_dump sichern, parallele Schreibzugriffe werden korrekt behandelt, und das Datenbankverzeichnis lässt sich mit Hyper Backup einschließen.
Wie sichere ich die Semaphore-Daten?
Drei Bereiche müssen gesichert werden: Erstens das Verzeichnis /volume1/docker/semaphore/db/ für die PostgreSQL-Daten (am besten per Hyper Backup auf eine externe Quelle). Zweitens die Verzeichnisse /volume1/docker/semaphore/config/ und /volume1/docker/semaphore/data/ für die App-Konfiguration. Drittens – und das ist kritisch – den Wert von SEMAPHORE_ACCESS_KEY_ENCRYPTION separat und sicher aufbewahren, zum Beispiel in einem Passwortmanager. Ohne diesen Schlüssel sind Datenbank-Backups zwar vorhanden, aber alle gespeicherten SSH-Keys und Passwörter sind nicht wiederherstellbar.
Wie richte ich HTTPS mit eigenem Hostnamen ein?
Öffne in DSM die Systemsteuerung → Anwendungsportal → Reverse-Proxy und lege eine neue Regel an: Quelle HTTPS auf Port 443 (oder deiner Synology-DDNS-Adresse), Ziel HTTP localhost:3000. Aktiviere den WebSocket-Schalter. Setze anschließend in der compose.yaml die Variable SEMAPHORE_WEB_ROOT: "https://dein-hostname.de", damit Links in E-Mail-Benachrichtigungen korrekt sind.
Welche Ansible-Version wird installiert?
Die requirements.txt mit dem Inhalt ansible installiert die jeweils aktuelle Version aus PyPI. Willst du eine bestimmte Version pinnen, schreibe z. B. ansible==10.7.0. Ansible-Collections und weitere Python-Pakete kannst du zusätzlich eintragen, z. B. netaddr für IP-Berechnungen in Playbooks.
Wie aktualisiere ich Semaphore auf eine neue Version?
Ändere in der compose.yaml den Image-Tag auf die gewünschte Version (oder behalte :latest) und führe im Container Manager einen Pull durch: Projekt auswählen → Aktion → Neuerstellen. Semaphore führt beim Neustart automatisch Datenbankmigrationen durch. Erstelle vor dem Update ein Backup des db-Verzeichnisses.
Kann Semaphore auch Terraform-Pläne ausführen?
Ja – Semaphore unterstützt neben Ansible auch Terraform, OpenTofu, Terragrunt und PowerShell als Task-Runner. Dazu muss das jeweilige Binary im Container vorhanden sein, entweder über ein angepasstes Image oder über eine erweiterte requirements.txt. In der Task-Template-Konfiguration wählst du dann den entsprechenden Runner statt Ansible aus.
Fazit
Semaphore UI macht das Synology NAS zur ernstgemeinten Automatisierungszentrale für kleine IT-Abteilungen und Heimlab-Admins. Das Compose-Projekt mit PostgreSQL ist in unter 45 Minuten aufgesetzt, der Admin-Bootstrap über Umgebungsvariablen spart manuelle Erstkonfiguration, und der healthcheck auf dem Datenbank-Container verhindert den häufigsten Anfängerfehler. Der einzige echte Stolperstein ist die fehlende Ansible-Installation im offiziellen Image – wer die requirements.txt anlegt, hat dieses Problem aber schnell gelöst. Wer darüber hinausgehende Infrastruktur-Automatisierung plant, findet in Semaphore ein solides Fundament: SSH-Keys verschlüsselt in der DB, Git-basierte Playbook-Verwaltung, Benutzer- und Team-Rechte, Benachrichtigungen und ein Scheduler für regelmäßige Jobs – alles ohne externe CI/CD-Plattform.
Weiterführende Anleitungen und Quellen
- SSH-Hardening Deep-Dive: sshd_config, Match-Blöcke und ssh-audit für KMU-Server – SSH-Verbindungen zwischen Semaphore und Zielhosts absichern
- Bash-Skripting für Admins – die Grundlagen – Ergänzend für Inline-Skripte und Wrapper-Playbooks
- code-server auf dem Synology NAS installieren: VS Code im Browser – Playbooks direkt auf dem NAS im Browser entwickeln und testen
- Container Manager auf dem Synology NAS: Docker-Compose-Projekt anlegen, starten und aktualisieren
- Nginx Proxy Manager auf der Synology mit Container Manager einrichten: Reverse Proxy und SSL
Offizielle Quellen: Semaphore UI Docker-Installationsdokumentation · Semaphore UI GitHub-Repository · semaphoreui/semaphore auf Docker Hub