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

Logs zentralisieren mit Grafana Loki

So sammeln Sie alle Logs Ihrer 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.

Geprüft am 09.10.2026 · für loki 3.7.8, alloy 1.20.1, grafana 13.2.3

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

Zentrale Datenbank, von der bunte Lichtströme zu mehreren Servergehäusen auf einer glänzenden weißen Fläche fließen

Wer mehrere Linux-Server und Docker-Container betreibt, meldet sich bei einem Fehler per SSH auf jeder Maschine an und sucht einzeln in journalctl und Logdateien. Logs zentralisieren mit Grafana Loki bündelt alle Logs an einem Ort, self-hosted in Docker. Diese Anleitung baut einen kompakten Stack aus Loki, Grafana Alloy und Grafana auf und zeigt die wichtigsten LogQL-Abfragen.

Voraussetzungen

  • Ein Linux-Server (x86-64 oder ARM64) mit Docker und Docker Compose v2. Für einen einzelnen Host reichen 2 CPU-Kerne und 4 GB RAM.
  • Ein Benutzer mit Rechten, den Docker-Daemon zu nutzen, und Lesezugriff auf den Docker-Socket /var/run/docker.sock (für Container-Logs).
  • Plattenplatz für die Log-Chunks im Loki-Volume, abhängig von Log-Menge und Retention.
  • 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.

Die Anleitung baut einen monolithischen Single-Node-Stack (Loki mit target: all und Filesystem-Storage). Im Test lief der Stack mit Loki 3.7.8, Alloy v1.20.1 und Grafana 13.2.3: Loki meldete „ready“, Alloy lieferte die Container-Logs mit den Labels job und container, und Grafana verband sich über die provisionierte Datenquelle.

Schritt 1: Architektur verstehen

Der Stack besteht aus drei getrennten Rollen:

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

Alloy schickt die Logs an den Push-Endpunkt von Loki, im Compose-Netz http://loki:3100/loki/api/v1/push. Loki legt die Daten ab, Grafana liest sie aus.

Loki indexiert nur Labels (z. B. job, container, level), den Log-Text durchsuchen Sie zur Abfragezeit mit Zeilenfiltern. Halten Sie deshalb die Label-Kardinalität (Zahl der unterschiedlichen Label-Werte) niedrig: Request-IDs, IP-Adressen oder Zeitstempel gehören nicht in Labels. Der frühere Collector Promtail ist End-of-Life (siehe Häufige Fragen).

Verifizieren: Sie können für jede der drei Komponenten Rolle und Port benennen und wissen, dass Alloy an http://loki:3100/loki/api/v1/push sendet.

Schritt 2: Docker-Compose-Stack anlegen

Legen Sie ein Projektverzeichnis an, z. B. /opt/loki-stack, und darin eine compose.yaml:

services:
  loki:
    image: grafana/loki:3.7.8
    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.20.1
    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:13.2.3
    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: {}

Die Image-Tags sind auf feste Versionen gesetzt statt :latest, Loki und Alloy binden ihre Ports nur an 127.0.0.1, und Alloy erhält den Docker-Socket read-only. Ohne diesen Mount sieht Alloy keine Container-Logs. Ersetzen Sie bitte-aendern durch ein eigenes Passwort.

Verifizieren: docker compose config --quiet im Projektverzeichnis endet ohne Ausgabe. Gestartet wird der Stack in Schritt 5, wenn alle Konfigurationsdateien vorliegen.

Schritt 3: loki-config.yaml schreiben

Diese loki-config.yaml läuft monolithisch mit Filesystem-Storage und aktiviert bereits die Retention (Details 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

auth_enabled: false bedeutet: Loki hat keinen Zugriffsschutz (siehe Schritt 7). Die index.period muss 24h betragen, sonst greift die Retention nicht. retention_period legt die Aufbewahrung fest, ohne diesen Wert (Default 0s) wachsen die Logs unbegrenzt.

Verifizieren: Nach dem Start in Schritt 5 enthält docker compose logs loki die Zeile msg=starting module=compactor und keine Konfigurationsfehler.

Schritt 4: Alloy als Collector konfigurieren

Die Datei config.alloy nutzt die Alloy-eigene Syntax. Die Pipeline entdeckt alle Docker-Container, setzt den Container-Namen als Label und schickt die 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"
  }
}

Nach dem Start liegen alle Container-Logs unter {job="docker"} und je Container unter {container="..."}. Die Alloy-Web-UI unter http://localhost:12345 zeigt den Zustand der Pipeline-Komponenten.

Für Host-Logs ergänzen Sie eine loki.source.journal-Quelle bzw. local.file_match auf /var/log/syslog und mounten /var/log/journal bzw. /var/log read-only in den Alloy-Container. Ohne Mount kommen keine Host-Logs an.

Verifizieren: Nach dem Start liefert curl -s http://localhost:3100/loki/api/v1/label/container/values die Namen Ihrer Container, im Test alloy, grafana und loki.

Schritt 5: Loki als Datenquelle in Grafana

Richten Sie die Datenquelle per Provisioning ein. Legen Sie im Projektverzeichnis eine grafana-datasources.yaml an, die Compose nach /etc/grafana/provisioning/datasources/ mountet:

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

Starten Sie den Stack und prüfen Sie Loki:

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

# Health-Check: liefert nach etwa 15 Sekunden "ready"
curl http://localhost:3100/ready

# Laufen alle drei Container?
docker compose ps

Alternativ richten Sie die Datenquelle manuell ein: Grafana unter http://localhost:3000 öffnen (Login admin mit Ihrem Passwort), dann:

  1. Connections → Add new connection
  2. Den Typ Loki auswählen und Add new data source klicken
  3. Bei URL http://loki:3100 eintragen
  4. Unten auf Save & test klicken

Verifizieren: docker compose ps zeigt loki, alloy und grafana als „Up“, curl http://localhost:3100/ready antwortet ready, und Save & test meldet „Data source successfully connected.“

Schritt 6: LogQL-Grundlagen und Explore

LogQL ist an PromQL angelehnt. Pflicht ist ein Label-Selektor in geschweiften Klammern, danach optional eine Pipeline. Abfragen testen Sie in Explore (linkes Menü, Datenquelle Loki). Die 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

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 brauchen Sie Metrik-Queries, die Logzeilen in Zahlenreihen umwandeln:

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

Ein Selektor mit Zeilenfilter liefert Logzeilen (Logs-Panel, Explore), rate(), count_over_time() oder sum by() liefern eine Metrik für ein Graph-Panel.

Verifizieren: {job="docker"} zeigt in Explore Logzeilen, sum by (container) (count_over_time({job="docker"} |= "error" [5m])) eine Zahl je Container.

Schritt 7: Log-Dashboard bauen und Loki absichern

Aus Explore heraus bauen Sie 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

Zur Sicherheit: Loki bringt keine Authentifizierung mit. Port 3100 darf nie ungeschützt ins Netz. Setzen Sie 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 sind beide Ports nur an 127.0.0.1 gebunden.
  • Ein starkes Grafana-Admin-Passwort statt des Defaults admin.

Verifizieren: Das Dashboard zeigt beide Panels nach dem Neuladen, docker compose ps listet 3100 und 12345 nur mit 127.0.0.1, und von einem anderen Rechner ist Port 3100 nicht erreichbar.

Schritt 8: Retention und Limits richtig setzen

Ohne Retention wachsen die Logs unbegrenzt. Die nötigen Einstellungen sind in der loki-config.yaml aus Schritt 3 schon gesetzt:

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

Für unterschiedliche Aufbewahrung je Quelle definieren Sie eine Stream-Retention:

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

Gelöschte Chunks geben den Platz erst nach retention_delete_delay frei. max_query_lookback sollte höchstens so groß wie retention_period sein.

Verifizieren: Nach docker compose restart loki enthält docker compose logs loki keine Fehler zu retention oder delete_request_store, sondern wieder msg=starting module=compactor.

Troubleshooting / Typische Fehler

  • Keine Container-Logs in Loki: Mount /var/run/docker.sock im Alloy-Container prüfen, ebenso 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 oder Retention greift nicht: Einstellungen aus der Tabelle in Schritt 8 prüfen, insbesondere index.period: 24h.
  • Loki langsam oder überlastet: Meist zu hohe Label-Kardinalität. Details wie Request-IDs über Zeilenfilter und | json/| logfmt abfragen.
  • Explore zeigt nichts an: Log- und Metrik-Query verwechselt (siehe Schritt 6)?
  • Alloy meldet timestamp too old: Beim ersten Start liest Alloy auch ältere Logs bestehender Container. Loki verwirft Einträge, die älter als eine Woche sind (reject_old_samples_max_age). Die Meldung verschwindet danach, neue Logs kommen an.
  • Upgrade kaputt: Feste Versionen wie grafana/loki:3.7.8 statt :latest verwenden.

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 erhält keine Updates oder Sicherheitskorrekturen mehr. Bestehende Promtail-Konfigurationen wandelt alloy convert --source-format=promtail in Alloy-Syntax um.

Indexiert Loki den gesamten Log-Text?

Nein. Loki indexiert nur die Labels und speichert den Text als komprimierte Chunks. Den Volltext durchsuchen Sie zur Abfragezeit über Zeilenfilter und Parser. Deshalb braucht Loki vergleichsweise wenig Speicher.

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

Wie in Schritt 4 beschrieben, über loki.source.journal bzw. local.file_match plus read-only-Mount. Für schnelles Lesen direkt auf dem Host bleibt journalctl die einfachste Wahl.

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). Ein monolithisches Loki mit Filesystem-Storage wie in dieser Anleitung reicht.

Ist mein Loki ohne weitere Maßnahmen sicher erreichbar?

Nein. Loki hat keine Authentifizierung. Schützen Sie Port 3100 wie in Schritt 7 beschrieben.

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

Gelöschte Chunks werden erst nach retention_delete_delay (Default 2 Stunden) entfernt, und der Compactor läuft nur alle compaction_interval (hier 10 Minuten).

Fazit

Loki, Grafana Alloy und Grafana bilden eine schlanke, self-hosted Log-Zentrale: Alloy sammelt, Loki speichert und indexiert nur Labels, Grafana fragt per LogQL ab. Halten Sie die Label-Kardinalität niedrig, stellen Sie Loki nie offen ins Netz und aktivieren Sie die Retention. Dann reicht der Stack vom einzelnen Server bis zur kleinen KMU-Umgebung.

Weiterführende Anleitungen und Quellen

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