Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Docker 11.10.2026 · 10 min Lesezeit

OliveTin mit Docker Compose sicher einrichten: Login, ACL, Socket-Proxy

OliveTin stellt vordefinierte Shell-Befehle als Buttons in einer Weboberfläche bereit. Diese Anleitung richtet die Version 3000.20.0 mit Docker Compose ein: Login mit Argon2id, Gruppen mit Zugriffslisten, eingeschränkte Argumente, Socket-Proxy statt Rohsocket, nginx davor sowie Backup und Restore.

Geprüft am 11.10.2026 · für OliveTin 3000.20.0

Mit KI erstellt – redaktionelle Prüfung ausstehend

WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Titelbild: OliveTin sicher betreiben mit Dashboard aus Aktionsbuttons, Schloss und Schild sowie drei Stichpunkten zu Buttons, Login mit Zugriffsregeln und Docker ohne Rohsocket

OliveTin macht aus vordefinierten Shell-Befehlen Schaltflächen in einer schlanken Weboberfläche. Ein Teammitglied klickt auf „Container neu starten“, statt sich per SSH anzumelden und den Befehl selbst zu tippen. Das spart Zeit, bringt aber ein Risiko mit: Ein Dienst, der Befehle auf Ihrem Server ausführt, gehört zu den sensibelsten Anwendungen im Netz. Diese Anleitung richtet OliveTin deshalb von Anfang an abgesichert ein.

Sie bauen einen Compose-Stack mit festgelegter Version, Login, zwei Benutzergruppen, validierten Argumenten, Docker-Socket-Proxy und Reverse Proxy, dazu Backup und Wiederherstellung. Alles wurde auf einem Docker-Testhost mit OliveTin 3000.20.0 durchgespielt.

Voraussetzungen

Im Leerlauf belegte OliveTin auf dem Testhost etwa 4 MiB Arbeitsspeicher, der nginx knapp 5 MiB und der Socket-Proxy rund 24 MiB. Das Image ist entpackt rund 626 MB groß, weil es auf Fedora basiert und die Docker-CLI mitbringt.

  • Linux-Server mit Docker Engine und Compose v2 (getestet mit Docker 29.8 auf x86-64; das Image gibt es laut Docker Hub auch für ARM64)
  • mindestens 1 CPU-Kern, 512 MB RAM und 1 GB freien Speicher; empfohlen sind 2 Kerne und 4 GB RAM, wenn der Server weitere Dienste trägt
  • für den Zugriff aus dem Team ein Hostname und ein TLS-Zertifikat auf einem vorgeschalteten Reverse Proxy
EckdatumWert
Imageghcr.io/olivetin/olivetin:3000.20.0 (Docker Hub: jamesread/olivetin)
Port1337 im Container, nur im Compose-Netz erreichbar; veröffentlicht wird 8080 des nginx
Volume./config nach /config (config.yaml, sessions.yaml)
Wichtige VariablenTZ, DOCKER_HOST (zeigt auf den Socket-Proxy)
Container-Benutzerolivetin, UID 1000, GID 999
LizenzAGPL-3.0

Stand 11.10.2026: Das Repository OliveTin/OliveTin hat 3.832 Sterne, ist nicht archiviert und erhielt den letzten Push am 05.10.2026. Die Version 3000.20.0 erschien am 10.09.2026. Die AGPL-3.0 betrifft vor allem Sie, wenn Sie den Code ändern und als Netzdienst anbieten. Lizenzschlüssel oder Bezahlstufen sind nicht aufgetaucht.

Schritt 1: Projektordner, Compose-Datei und Start

Der Container-Benutzer hat die UID 1000 und muss in config lesen und schreiben können, denn OliveTin legt dort die Datei sessions.yaml an.

mkdir -p ~/olivetin/config ~/olivetin/proxy && cd ~/olivetin
sudo chown -R 1000:1000 config

Die .env enthält nur Versionen und Ports, keine Geheimnisse.

OLIVETIN_VERSION=3000.20.0
SOCKET_PROXY_VERSION=3.4.6
NGINX_VERSION=1.29-alpine
TZ=Europe/Berlin
PROXY_BIND=127.0.0.1
PROXY_PORT=8080

Die compose.yaml startet OliveTin, den Socket-Proxy, nginx und einen Demo-Container, den Sie später per Klick neu starten.

name: olivetin

services:
  olivetin:
    image: ghcr.io/olivetin/olivetin:${OLIVETIN_VERSION}
    container_name: olivetin
    restart: unless-stopped
    environment:
      TZ: ${TZ}
      DOCKER_HOST: tcp://socket-proxy:2375
    volumes:
      - ./config:/config
    expose:
      - "1337"
    healthcheck:
      test: ["CMD", "curl", "-fsS", "-o", "/dev/null", "http://localhost:1337/"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 15s
    depends_on:
      - socket-proxy
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL

  socket-proxy:
    image: lscr.io/linuxserver/socket-proxy:${SOCKET_PROXY_VERSION}
    container_name: olivetin-socket-proxy
    restart: unless-stopped
    environment:
      CONTAINERS: 1
      ALLOW_RESTARTS: 1
      POST: 0
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    read_only: true
    tmpfs:
      - /run
    security_opt:
      - no-new-privileges:true

  proxy:
    image: nginx:${NGINX_VERSION}
    container_name: olivetin-proxy
    restart: unless-stopped
    ports:
      - "${PROXY_BIND}:${PROXY_PORT}:80"
    volumes:
      - ./proxy/olivetin.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - olivetin

  demo-target:
    image: alpine:3.22
    container_name: demo-target
    command: ["sleep", "infinity"]
    restart: unless-stopped

Legen Sie proxy/olivetin.conf (Inhalt in Schritt 5) und eine erste config/config.yaml an:

logLevel: "INFO"
actions:
  - title: Speicherplatz anzeigen
    shell: df -h /config
    icon: disk
    onclick: execution-dialog

Starten Sie den Stack:

docker compose up -d
docker compose ps

Verifizieren: docker compose ps zeigt alle vier Container als Up, olivetin nach wenigen Sekunden als healthy. curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8080/ liefert 200. Im Log steht „OliveTin started“ mit configDir="/config".

Schritt 2: Passwort-Hashes erzeugen

Lokale Benutzer speichert OliveTin mit Argon2id-Hash in der config.yaml. Die laufende Instanz erzeugt den Hash über ihre API. Mit read -rs landet das Passwort nicht in der Shell-Historie. Solange kein Login aktiv ist, muss der Dienst lokal bleiben, deshalb der Bind auf 127.0.0.1.

read -rs PW; echo
curl -sS --json "{\"password\": \"$PW\"}" http://127.0.0.1:8080/api/PasswordHash | jq -r .hash

Die Antwort ist ein JSON-Objekt mit dem Feld hash. Die Dokumentation zeigt noch den Satz „Your password hash is“, Version 3000.20.0 liefert jedoch JSON. Wiederholen Sie den Aufruf für jeden Benutzer.

Verifizieren: Jeder Hash beginnt mit $argon2id$v=19$m=65536,t=4,p=4$. Der HTTP-Status des Aufrufs ist 200.

Schritt 3: Aktionen, Argumente und Zugriffsregeln

Drei Bausteine sind wichtig. Erstens setzt authRequireGuestsToLogin: true alle Standardrechte auf false, Gäste sehen nur die Anmeldeseite. Zweitens regeln Zugriffslisten (ACLs), wer welche Aktion ausführt: „admins“ hängt mit addToEveryAction an jeder Aktion, „operators“ nur dort, wo es eingetragen ist. Drittens begrenzen Argumenttypen die Eingabe.

Ersetzen Sie die Platzhalter durch Ihre Hashes. Der Typ dnsname lässt nur gültige Hostnamen zu, die Auswahlliste nur vorab definierte Werte.

logLevel: "INFO"
pageTitle: "Betrieb: Server-Aktionen"

authRequireGuestsToLogin: true

authLocalUsers:
  enabled: true
  users:
    - username: admin
      usergroup: admins
      password: HASH_ADMIN_HIER
    - username: operator
      usergroup: operators
      password: HASH_OPERATOR_HIER

accessControlLists:
  - name: admins
    matchUsergroups:
      - admins
    permissions:
      view: true
      exec: true
      logs: true
    addToEveryAction: true
  - name: operators
    matchUsergroups:
      - operators
    permissions:
      view: true
      exec: true
      logs: true

defaultPolicy:
  showDiagnostics: false
  showLogList: true

actions:
  - title: Speicherplatz anzeigen
    shell: df -h /config
    icon: disk
    onclick: execution-dialog
    acls:
      - operators

  - title: Host auflösen
    shell: getent hosts {{ host }}
    icon: ping
    onclick: execution-dialog
    acls:
      - operators
    arguments:
      - name: host
        title: Hostname
        type: dnsname

  - title: Container neu starten
    shell: docker restart {{ container }}
    timeout: 30
    icon: restart
    onclick: execution-dialog
    arguments:
      - name: container
        title: Container
        choices:
          - title: Demo-Ziel
            value: demo-target

  - title: Langer Lauf (Timeout 10 s)
    shell: sleep 6 && echo fertig
    timeout: 10
    onclick: execution-dialog

Übernehmen Sie die Änderung mit docker compose restart olivetin. Die Datei wird nur beim Start gelesen.

Verifizieren: Ein anonymer Aufruf von /api/GetDashboard endet mit „guests are not allowed to access the dashboard“. Nach der Anmeldung sieht der Benutzer „admin“ alle vier Aktionen, „operator“ nur Speicherplatz und Host auflösen.

OliveTin-Oberfläche nach der Anmeldung als admin mit den vier Aktionsbuttons Speicherplatz anzeigen, Host auflösen, Container neu starten und Langer Lauf
Der Benutzer admin sieht alle vier Aktionen.
OliveTin-Oberfläche nach der Anmeldung als operator mit nur zwei Buttons, Speicherplatz anzeigen und Host auflösen
Der Benutzer operator sieht nur die beiden freigegebenen Aktionen.

Gefährlich bleiben Argumente. Roh-Typen wie very_dangerous_raw_string, password, url und eigene regex-Typen lehnt OliveTin bei shell: ab, dort ist exec: nötig. Bevorzugen Sie Auswahllisten und den strengsten Typ. Der Hostname „x; id“ wird mit „invalid dnsname label“ abgewiesen, der Befehl läuft gar nicht erst.

Ausführungsergebnis der Aktion Host auflösen mit Statuscode minus 1337 und der Meldung invalid dnsname label x; id, die Aktion wurde nicht ausgeführt
Ungültige Eingabe: Die Aktion wird nicht ausgeführt.

Ruft operator per API die Admin-Aktion auf, antwortet OliveTin mit „permission denied“, im Log steht „ACL check failed. Blocked from executing.“ Eine Anmeldung mit falschem Passwort liefert HTTP 200 mit {"success":false}, Skripte müssen also den Inhalt prüfen.

Schritt 4: Docker-Zugriff über den Socket-Proxy

Der Zugriff auf /var/run/docker.sock ist faktisch Root-Zugriff auf den Host. Im Test genügte ein Container mit gemountetem Socket und der Docker-Gruppe, um mit einem zweiten Container das Wurzelverzeichnis des Hosts einzubinden und dessen /etc/os-release zu lesen. Ohne group_add scheiterte der Versuch an „permission denied“.

Sicherer ist der Socket-Proxy aus dem Stack: Nur er sieht den Socket, schreibgeschützt. CONTAINERS=1 und ALLOW_RESTARTS=1 erlauben Abfragen und Neustarts, POST=0 sperrt alles Übrige. OliveTin nutzt ihn über DOCKER_HOST. Veröffentlichen Sie den Port 2375 niemals.

docker compose exec olivetin docker ps --format '{{.Names}}'
docker compose exec olivetin docker restart demo-target
docker compose exec olivetin docker rm -f demo-target
docker compose exec olivetin docker run --rm alpine id

Verifizieren: Die ersten beiden Befehle funktionieren. Die letzten beiden enden mit „403 Forbidden, Request forbidden by administrative rules“, ebenso docker exec in andere Container. In der Weboberfläche startet die Aktion „Container neu starten“ den Demo-Container neu.

Ausführungsergebnis der Aktion Container neu starten mit Status Completed, Dauer zehn Sekunden und der Ausgabe demo-target
Neustart über den Socket-Proxy, Dauer 10 Sekunden.

Beachten Sie die Dauer: Docker beendet einen Container, der SIGTERM ignoriert, erst nach 10 Sekunden. OliveTin bricht Aktionen standardmäßig nach 3 Sekunden ab, weshalb hier timeout: 30 steht. Ohne diese Zeile lief der Neustart zwar zu Ende, die Anzeige meldete aber „timed out after 3 seconds“.

Schritt 5: Reverse Proxy und Freigabe

Der Port 1337 bleibt im Compose-Netz, von außen erreichbar ist nur der nginx. Auf dem Testhost scheiterte curl gegen Port 1337 mit Fehler 7, gegen Port 8080 kam Status 200. Den Websocket-Block aus der Dokumentation brauchte Version 3000.20.0 nicht: Der Pfad /websocket lieferte nur die Startseite.

server {
    listen 80;
    server_name _;

    # Nur Weboberfläche und API von OliveTin weiterreichen
    location / {
        proxy_pass http://olivetin:1337/;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 600s;
        proxy_send_timeout 600s;
    }
}

Für das Team schalten Sie einen TLS-terminierenden Proxy auf dem Host davor, der auf 127.0.0.1:8080 weiterleitet, oder setzen PROXY_BIND auf die interne Adresse, jeweils erst nach aktivem Login. Das Anmeldecookie ist HttpOnly mit SameSite=Lax. Ins offene Internet gehört der Dienst nicht, die Dokumentation rät ausdrücklich davon ab. Besser sind VPN oder eine Zugangsschicht davor.

Verifizieren: curl -sI http://127.0.0.1:8080/ liefert 200 und die Header X-Frame-Options: DENY sowie eine Content-Security-Policy. Der Aufruf von http://127.0.0.1:1337/ schlägt fehl.

Schritt 6: Healthcheck, Backup und Wiederherstellung

Das Image bringt keinen Healthcheck mit, der Stack ergänzt einen mit curl. Persistent ist nur der Ordner config: Konfiguration und sessions.yaml mit den angemeldeten Sitzungen. Die Sitzungen liegen dort im Klartext, die Datei hat den Modus 600. Behandeln Sie das Backup deshalb wie ein Geheimnis.

docker compose stop olivetin
tar czf olivetin-config-$(date +%F).tgz config
docker compose start olivetin

Geprüft wurde die Wiederherstellung mit einer Marker-Aktion: ergänzen, sichern, config leeren, zurückspielen, Besitzer setzen, starten. Danach war der Marker wieder da, und die alte admin-Sitzung galt weiter.

docker compose stop olivetin
rm -rf config && mkdir config
tar xzf olivetin-config-JJJJ-MM-TT.tgz
sudo chown -R 1000:1000 config
docker compose start olivetin

Verifizieren: Der Dashboard-Aufruf mit der alten Sitzung listet die Aktion „Marker Restore-Test“. Der Status im docker compose ps ist nach wenigen Sekunden healthy.

Schritt 7: Update und Deinstallation

Ändern Sie für ein Update OLIVETIN_VERSION in der .env und sichern Sie vorher config. Eine Datenbank gibt es nicht, ein Rollback besteht im Eintragen des alten Tags. Für den Sprung zwischen den Reihen 2k und 3000 hat das Projekt eine eigene Migrationsanleitung, lesen Sie die Release-Notes vorher.

docker compose pull olivetin
docker compose up -d olivetin
docker compose logs olivetin | grep -i 'initializing'

docker compose down entfernt Container und Netz, der Ordner config bleibt liegen. Löschen Sie ihn erst mit Backup: Danach sind config.yaml und alle Sitzungen unwiederbringlich verloren.

Verifizieren: Die Log-Zeile zeigt die neue Version. Nach docker compose down listet docker ps -a keine Container des Stacks mehr.

Troubleshooting

SymptomUrsache und Lösung
Container startet ständig neu, Log: „mapping key \"timeout\" already defined at line 59“Doppelter Schlüssel in der config.yaml. Die Datei ist striktes YAML, entfernen Sie den doppelten Eintrag.
Restart-Schleife, Log: „open /config/config.yaml: permission denied“Der Container-Benutzer (UID 1000) darf die Datei nicht lesen. Besitzer oder Modus anpassen, zum Beispiel chown 1000:1000 und chmod 644.
docker pull mit Tag v3000.20.0 scheitert mit „not found“Der Tag heißt 3000.20.0 ohne v. Die Release-Bezeichnung auf GitHub weicht vom Registry-Tag ab.
Aktion endet mit „timed out after 3 seconds“Standardzeitlimit. Setzen Sie bei langen Aktionen timeout pro Aktion.
Docker-Befehle melden „403 Forbidden“Der Socket-Proxy sperrt die Operation. Öffnen Sie nur das, was die Aktion wirklich braucht, etwa ALLOW_RESTARTS.
Mit gemountetem Rohsocket „permission denied while trying to connect to the docker API“Der Benutzer olivetin gehört nicht zur Gruppe des Sockets. Mit group_add und der GID aus getent group docker lösbar, sicherer ist der Proxy.

Häufige Fragen

Darf ich OliveTin im Internet erreichbar machen?

Das Projekt rät ausdrücklich davon ab. Wählen Sie ein VPN oder eine Zugangsschicht wie Warpgate.

Warum nicht einfach den Docker-Socket mounten?

Weil ein Fehler dann Root-Rechte auf dem Host bedeutet. Der Proxy begrenzt den Schaden auf die erlaubten Operationen.

Gibt es Alternativen?

Für geplante Jobs statt Klick-Aktionen eignet sich Cronicle. OliveTin passt, wenn Menschen gezielt einzelne Befehle auslösen.

Fazit

OliveTin ist ein schlankes Werkzeug mit kleiner Angriffsfläche, solange Sie Login, Zugriffslisten und strenge Argumente konsequent nutzen. Der Stack ließ die erwarteten Aktionen zu und blockierte Gäste, den Operator bei Admin-Aktionen, ungültige Eingaben und gefährliche Docker-Befehle. Für KMU ist das eine sichere Brücke zwischen Admins und weniger erfahrenen Kollegen.

Weiterführende Anleitungen und Quellen

OliveTinDockerDocker ComposeSelfhostingShell-BefehleZugriffsschutzDocker-Socket-ProxyReverse ProxyAutomatisierung