Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Monitoring 03.06.2026 · 11 min Lesezeit

Logs zentralisieren mit Grafana Loki

So sammelst du alle Logs deiner Linux-Server und Docker-Container an einem Ort: Diese Anleitung zeigt den self-hosted Loki-Stack mit Docker Compose, Grafana Alloy als Collector und Grafana als Oberfläche – inklusive LogQL-Grundlagen, Loki-Datenquelle, Explore-Abfragen und aktivierter Retention.

Schematische Darstellung zentralisierter Logs: Datenströme von Servern fließen in eine zentrale Datenbank und ein Analyse-Dashboard

Wer mehrere Linux-Server und Docker-Container betreibt, kennt das Problem: Bei einem Fehler musst du dich per SSH auf jede Maschine einloggen und einzeln durch journalctl und Logdateien wühlen. Logs zentralisieren mit Grafana Loki löst das, indem alle Logs an einem Ort landen und sich dort durchsuchen lassen – self-hosted, in Docker, ohne teure SaaS-Lösung. Diese Anleitung baut einen kompakten Loki-Stack aus Loki, Grafana Alloy und Grafana auf, bindet ihn an dein Grafana an und zeigt die wichtigsten LogQL-Abfragen.

Voraussetzungen

Bevor du startest, sollte folgendes vorhanden sein:

  • Ein Linux-Server mit installiertem Docker und Docker Compose (Plugin docker compose v2).
  • Ein Benutzer mit Rechten, den Docker-Daemon zu nutzen, und Lesezugriff auf den Docker-Socket /var/run/docker.sock (für Container-Logs).
  • Etwas Plattenplatz für die Log-Chunks unter dem Loki-Volume – wie viel, hängt von Log-Volumen und Retention ab.
  • Die Ports 3000 (Grafana), 3100 (Loki) und optional 12345 (Alloy-UI) frei. Diese Ports gehören nicht ungeschützt ins Internet.
  • Grundkenntnisse in YAML und der Kommandozeile. Für reine Host-Logs ohne Loki bleibt journalctl die einfachste Wahl (Link am Ende).

Diese Anleitung baut bewusst einen monolithischen Single-Node-Stack (Loki mit -target=all und Filesystem-Storage). Der offizielle Getting-Started-Stack ist die skalierbare Variante mit read/write/backend, MinIO und nginx-Gateway – für einen einzelnen Host meist überdimensioniert.

Schritt 1: Architektur verstehen

Der Stack besteht aus drei klar getrennten Rollen. Wer diese Trennung versteht, debuggt später deutlich schneller:

KomponenteRolleAufgabePort
Grafana LokiLog-Datenbank / AggregationSpeichert Logs als komprimierte Chunks, indexiert nur die Labels (nicht den Volltext), stellt die Push- und Query-API bereit3100
Grafana AlloyCollector / AgentSammelt Logs (Docker, journald, Logdateien) und pusht sie an die Loki-API12345 (UI)
GrafanaOberflächeFragt Loki via LogQL ab, zeigt Logs in Explore und in Dashboards3000

Der Datenfluss ist einfach: Alloy liest Logs ein und schickt sie an den Push-Endpunkt von Loki. Aus Sicht der Container im selben Compose-Netz ist das http://loki:3100/loki/api/v1/push, von außen http://localhost:3100/.... Loki legt die Daten ab, Grafana liest sie wieder aus.

Der entscheidende Punkt: Loki indexiert nur Labels (z. B. job, container, level). Den eigentlichen Log-Text durchsuchst du erst zur Abfragezeit mit Zeilenfiltern. Deshalb gilt die wichtigste Regel von Loki: halte die Label-Kardinalität niedrig – niemals Request-IDs, IP-Adressen oder Timestamps als Labels verwenden.

Schritt 2: Docker-Compose-Stack anlegen

Lege ein Projektverzeichnis an, z. B. /opt/loki-stack, und darin eine compose.yaml. Das folgende Gerüst ist ein kompakter Single-Node-Stack mit gepinnten Versionen, persistentem Loki-Volume und den drei Diensten:

services:
  loki:
    image: grafana/loki:3.7.0
    container_name: loki
    command: -config.file=/etc/loki/loki-config.yaml
    ports:
      - "127.0.0.1:3100:3100"   # nur lokal binden, nicht offen ins Netz
    volumes:
      - ./loki-config.yaml:/etc/loki/loki-config.yaml:ro
      - loki-data:/loki
    restart: unless-stopped

  alloy:
    image: grafana/alloy:v1.7.5
    container_name: alloy
    command:
      - run
      - --server.http.listen-addr=0.0.0.0:12345
      - /etc/alloy/config.alloy
    ports:
      - "127.0.0.1:12345:12345"  # Alloy-Web-UI, nur lokal
    volumes:
      - ./config.alloy:/etc/alloy/config.alloy:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
    depends_on:
      - loki
    restart: unless-stopped

  grafana:
    image: grafana/grafana:11.5.2
    container_name: grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=bitte-aendern
    volumes:
      - ./grafana-datasources.yaml:/etc/grafana/provisioning/datasources/ds.yaml:ro
      - grafana-data:/var/lib/grafana
    depends_on:
      - loki
    restart: unless-stopped

volumes:
  loki-data: {}
  grafana-data: {}

Wichtig sind hier drei Details: Die Image-Tags sind auf feste Versionen gepinnt statt :latest (sonst Reproduzierbarkeits- und Upgrade-Probleme), Loki und Alloy binden ihre Ports nur an 127.0.0.1, und Alloy bekommt den Docker-Socket read-only hineingemountet – ohne diesen Mount sieht Alloy keine Container-Logs.

Schritt 3: loki-config.yaml schreiben

Loki braucht eine Konfigurationsdatei. Diese Basis-Config läuft monolithisch (target: all) mit Filesystem-Storage und aktiviert bereits die Retention (Details dazu in Schritt 8):

auth_enabled: false   # kein Multi-Tenant; Loki bringt KEINE Auth mit

server:
  http_listen_port: 3100
  log_level: info

common:
  instance_addr: 127.0.0.1
  path_prefix: /loki
  storage:
    filesystem:
      chunks_directory: /loki/chunks
      rules_directory: /loki/rules
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory

schema_config:
  configs:
    - from: 2024-01-01
      store: tsdb
      object_store: filesystem
      schema: v13
      index:
        prefix: index_
        period: 24h        # Index-Periode MUSS 24h sein (Retention-Voraussetzung)

compactor:
  working_directory: /loki/retention
  compaction_interval: 10m
  retention_enabled: true
  retention_delete_delay: 2h
  retention_delete_worker_count: 150
  delete_request_store: filesystem   # Pflicht, sobald Retention aktiv ist

limits_config:
  retention_period: 744h   # 31 Tage; 0s (Default) = unbegrenzt
  max_query_lookback: 744h # sollte <= retention_period sein

Die wichtigsten Stellschrauben: auth_enabled: false bedeutet, dass Loki keinerlei Zugriffsschutz mitbringt (dazu Schritt 7). Die index.period muss exakt 24h betragen, sonst greift die Retention nicht. Und retention_period legt fest, wie lange Logs aufbewahrt werden – ohne diesen Wert (Default 0s) wachsen deine Logs unbegrenzt.

Schritt 4: Alloy als Collector konfigurieren

Alloy nutzt eine eigene Konfigurationssprache (River-Syntax, Datei config.alloy). Die folgende Pipeline entdeckt alle laufenden Docker-Container, nimmt den Container-Namen als Label und schickt deren Logs an Loki:

discovery.docker "linux" {
  host = "unix:///var/run/docker.sock"
}

discovery.relabel "docker" {
  targets = []
  rule {
    source_labels = ["__meta_docker_container_name"]
    regex         = "/(.*)"
    target_label  = "container"
  }
}

loki.source.docker "default" {
  host          = "unix:///var/run/docker.sock"
  targets       = discovery.docker.linux.targets
  labels        = {"job" = "docker"}
  relabel_rules = discovery.relabel.docker.rules
  forward_to    = [loki.write.local.receiver]
}

loki.write "local" {
  endpoint {
    url = "http://loki:3100/loki/api/v1/push"
  }
}

Damit hast du nach dem Start jeden Container-Log unter dem Label {job="docker"} und zusätzlich pro Container unter {container="..."}. Die Alloy-Web-UI unter http://localhost:12345 zeigt dir, ob die Pipeline-Komponenten gesund sind und Daten fließen.

Für Host-Logs (journald/systemd, klassische Logdateien) ergänzt du in Alloy eine loki.source.journal-Quelle bzw. local.file_match auf /var/log/syslog oder /var/log/messages. Dafür muss der jeweilige Pfad (z. B. /var/log/journal oder /var/log) als read-only Volume in den Alloy-Container gemountet sein – fehlt der Mount, kommen keine Host-Logs an.

Schritt 5: Loki als Datenquelle in Grafana

Am saubersten richtest du die Datenquelle per Provisioning ein. Lege im Projektverzeichnis eine grafana-datasources.yaml an (sie wird im Compose-File nach /etc/grafana/provisioning/datasources/ gemountet):

apiVersion: 1
datasources:
  - name: Loki
    type: loki
    access: proxy
    url: http://loki:3100
    isDefault: false

Beim nächsten Start steht Loki dann automatisch als Datenquelle bereit. Jetzt den Stack starten und Loki auf Bereitschaft prüfen:

cd /opt/loki-stack
docker compose up -d

# Health-Check: muss "ready" liefern
curl http://localhost:3100/ready

# Laufen alle drei Container?
docker compose ps

Alternativ richtest du die Datenquelle manuell ein. Öffne Grafana unter http://localhost:3000 (Erst-Login admin / dein gesetztes Passwort) und folge dem Klickpfad:

  1. Connections → Data sources → Add data source
  2. Den Typ Loki auswählen
  3. Bei URL http://loki:3100 eintragen
  4. Unten auf Save & test klicken – es muss eine grüne Erfolgsmeldung erscheinen

Schritt 6: LogQL-Grundlagen und Explore

LogQL ist an PromQL angelehnt. Pflicht ist immer ein Label-Selektor in geschweiften Klammern, danach folgt optional eine Pipeline. Du testest Abfragen am besten in Explore (linkes Menü, Datenquelle Loki auswählen). Die Selektor-Operatoren:

  • = exakt, != ungleich, =~ Regex-Match, !~ Regex kein Match – z. B. {job="docker", container=~"web.+"}
  • Zeilenfilter (schnellste Methode nach dem Selektor): |= enthält, != enthält nicht, |~ Regex enthält, !~ Regex fehlt
  • Parser: | json und | logfmt extrahieren Felder als Labels

Ein paar copy-paste-fertige Beispiele:

{job="docker"}                                    # alle Container-Logs
{container="nginx"} |= "error"                     # Zeilen, die 'error' enthalten
{job="docker"} |= "error" != "timeout"            # verkettete Zeilenfilter
{job="docker"} | json | level="error"             # JSON parsen, dann nach Feld filtern

Für Graphen brauchst du Metrik-Queries. Sie wandeln Logzeilen in Zahlenreihen um – etwa Einträge pro Sekunde oder Fehler je Container:

rate({job="docker"}[5m])                           # Eintraege pro Sekunde
count_over_time({job="docker"} |= "error" [5m])    # Fehler-Anzahl im 5m-Fenster
sum by (container) (
  count_over_time({job="docker"} |= "error" [5m])
)                                                  # Fehler je Container

Merke dir die Unterscheidung: Ein nackter Selektor mit Zeilenfilter liefert Logzeilen (Logs-Panel / Explore), während rate(), count_over_time() oder sum by() eine Metrik für ein Graph-Panel ergeben. Verwechselst du beides, bleibt die Darstellung leer oder unerwartet.

Schritt 7: Log-Dashboard bauen und Loki absichern

Aus Explore heraus baust du schnell ein einfaches Dashboard. Der Klickpfad für ein Logs-Panel:

  1. Abfrage in Explore testen, z. B. {job="docker"} |= "error"
  2. Oben rechts auf Add to dashboard → New dashboard
  3. Den Panel-Typ auf Logs stellen, Panel benennen und speichern
  4. Ein zweites Panel vom Typ Time series mit einer Metrik-Query wie sum by (container) (count_over_time({job="docker"} |= "error" [5m])) hinzufügen

Damit erweiterst du dein bestehendes Grafana-Metriken-Dashboard um eine Log-Sicht: Metriken zeigen dass etwas klemmt, die Logs zeigen warum. Wie du Panels und Datenquellen grundsätzlich aufbaust, ist im verlinkten Grafana-Dashboard-Guide weiter unten beschrieben.

Zur Sicherheit ein klares Wort: Loki bringt keine Authentifizierung mit. Der offene Push-Endpunkt auf Port 3100 darf niemals ungeschützt ins Netz oder Internet. Setze davor:

  • Einen authentifizierenden Reverse-Proxy (nginx oder Traefik) mit Basic-Auth oder OAuth, falls Loki über das Netz erreichbar sein muss.
  • Eine Firewall-Regel, die 3100 (und 12345) nur für vertrauenswürdige Hosts öffnet – im Beispiel oben binden wir die Ports bereits nur an 127.0.0.1.
  • Ein starkes Grafana-Admin-Passwort statt des Default admin.

Schritt 8: Retention und Limits richtig setzen

Ohne Retention wachsen deine Logs unbegrenzt – der häufigste Stolperstein im Betrieb. Die Aktivierung hängt an drei Bedingungen, die in der loki-config.yaml aus Schritt 3 schon erfüllt sind:

EinstellungWert / Bedeutung
compactor.retention_enabledtrue – schaltet das Löschen alter Daten überhaupt erst ein
compactor.delete_request_storefilesystem – Pflicht, sobald Retention aktiv ist
limits_config.retention_periodz. B. 744h (31 Tage); Minimum 24h; 0s = unbegrenzt
schema_config ... index.periodmuss exakt 24h sein (single-store TSDB)
retention_delete_delayDefault 2h – so lange bleiben gelöschte Chunks noch liegen

Brauchst du unterschiedliche Aufbewahrung je Log-Quelle, definierst du eine Stream-Retention mit Selektor, Priorität und Periode:

limits_config:
  retention_period: 744h            # globaler Default: 31 Tage
  retention_stream:
    - selector: '{job="docker", container="nginx"}'
      priority: 1
      period: 168h                  # Access-Logs nur 7 Tage

Zwei Dinge sorgen oft für Verwirrung: Gelöschte Chunks geben den Plattenplatz erst nach retention_delete_delay (Default 2h) frei – der Speicher sinkt also nicht sofort. Und max_query_lookback sollte höchstens so groß wie retention_period sein, sonst fragst du Zeiträume ab, in denen ohnehin keine Daten mehr liegen.

Troubleshooting / Typische Fehler

  • Keine Container-Logs in Loki: Fehlt der Mount /var/run/docker.sock im Alloy-Container? Prüfe die Alloy-UI auf 12345 und docker compose logs alloy.
  • Push schlägt fehl / Connection refused: Die Alloy-URL muss http://loki:3100/loki/api/v1/push lauten (Service-Name im Compose-Netz), nicht localhost.
  • Logs wachsen unbegrenzt: Retention ist standardmäßig aus. retention_enabled: true, delete_request_store und retention_period setzen.
  • Retention greift nicht: Die index.period muss exakt 24h sein und die Retention mindestens 24h betragen.
  • Loki langsam oder überlastet: Meist zu hohe Label-Kardinalität. Niemals Request-IDs, IPs oder Timestamps als Labels – solche Details über Zeilenfilter und | json/| logfmt abfragen.
  • Explore zeigt nichts an: Log- und Metrik-Query verwechselt? Nackter Selektor = Logs, rate()/count_over_time() = Metrik/Graph.
  • Upgrade kaputt: :latest in den Images vermeiden und auf feste Versionen pinnen (z. B. grafana/loki:3.7.0).

Häufige Fragen

Soll ich Promtail oder Grafana Alloy nehmen?

Für jede Neuinstallation Grafana Alloy. Promtail ist seit dem 2. März 2026 End-of-Life und bekommt keine Updates oder Security-Fixes mehr; seine Funktionen sind mit Loki 3.4 in Alloy aufgegangen. Bestehende Promtail-Configs lassen sich mit einem Migrationstool in Alloy-Config umwandeln.

Indexiert Loki den gesamten Log-Text?

Nein. Loki indexiert ausschließlich die Labels und speichert den eigentlichen Text als komprimierte Chunks. Den Volltext durchsuchst du erst zur Abfragezeit über Zeilenfilter (|=, |~) und Parser (| json). Das ist der Grund, warum Loki vergleichsweise sparsam mit Speicher und Ressourcen umgeht.

Wie sammle ich Host-Logs wie journald oder /var/log/syslog ein?

In Alloy ergänzt du eine loki.source.journal-Quelle bzw. einen Datei-Match auf /var/log/syslog und mountest den jeweiligen Pfad read-only in den Alloy-Container. Für schnelles Lesen direkt auf dem Host ohne Loki bleibt journalctl die einfachste Wahl – siehe den verlinkten Leitfaden unten.

Brauche ich den großen Getting-Started-Stack mit MinIO und nginx?

Für einen einzelnen Server nicht. Der offizielle Getting-Started-Stack ist die skalierbare Variante (read/write/backend + MinIO + nginx-Gateway) und für kleine Setups überdimensioniert. Ein monolithisches Loki (target: all) mit Filesystem-Storage – wie in dieser Anleitung – reicht völlig.

Ist mein Loki ohne weitere Maßnahmen sicher erreichbar?

Nein. Loki bringt keine Authentifizierung mit. Stelle Port 3100 niemals offen ins Netz, sondern binde ihn nur lokal, setze einen authentifizierenden Reverse-Proxy davor und schütze ihn per Firewall.

Warum sinkt mein Plattenplatz nach dem Setzen der Retention nicht sofort?

Weil gelöschte Chunks erst nach retention_delete_delay (Default 2 Stunden) tatsächlich entfernt werden. Zudem läuft der Compactor nur alle compaction_interval (Default 10 Minuten). Etwas Geduld – der Platz wird mit Verzögerung frei.

Fazit

Mit Loki, Grafana Alloy und Grafana hast du eine schlanke, self-hosted Log-Zentrale, die ohne SaaS-Kosten auskommt. Der Kern ist die saubere Rollentrennung: Alloy sammelt und pusht, Loki speichert und indexiert nur Labels, Grafana fragt via LogQL ab. Halte dich an die drei wichtigsten Regeln – Label-Kardinalität niedrig halten, Loki niemals offen ins Netz stellen und die Retention aktiv setzen – dann skaliert der Stack zuverlässig vom einzelnen Server bis zum kleinen KMU-Cluster. Zusammen mit deinem bestehenden Metriken-Dashboard hast du damit Metriken und Logs an einem Ort.

Weiterführende Anleitungen und Quellen

Quellen: Grafana-Loki-Doku (Install with Docker / Docker Compose), Grafana-Alloy-Doku (Monitor Docker containers), Grafana-Loki-Doku (Log queries / LogQL) sowie Grafana-Loki-Doku (Log retention).