Ausufernde Docker-Logs: So verstehst & begrenzt du sie

Ausufernde Docker-Logs: So verstehst & begrenzt du sie

Docker speichert die Ausgabe (stdout/stderr) der Container dauerhaft. Standardmäßig gibt es kein Rotieren, wie man es auf klassischen Servern mit Logrotate kennt. Erzeugt eine Anwendung hohe Mengen an Ausgaben, kann dies Gigabytes an Logdateien auf dem Dateisystem erzeugen. Es ist daher wichtig zu verstehen, wie Docker mit Protokollen umgeht. Und vor allem, diese in der Größe zu beschränken. Dafür sind keine externen Werkzeuge notwendig. Mit ein wenig Konfiguration begrenzt Docker automatisch die Größe der Protokolle auf ein von dir festgelegtes Maß.

So funktionieren Logs in Docker

Klassische Dateien (unter GNU/Linux normal in /var/log abgelegt) kommen üblicherweise nicht zum Einsatz. Diese wären unter Docker sowieso nur bis zum nächsten Neustart des Containers verfügbar. Ausgenommen sind Logs in Volumes, welche persistentes Speichern ermöglichen. Eben so wird vom Einsatz des Systemd-Journal abgesehen, welches vor allem bei Systemd-Units (Diensten) genutzt wird.

Es ist bei Docker-Containern stattdessen üblich, sämtliche Ausgaben auf die Standardausgabe zu schreiben. Dies ist nicht vergleichbar mit dem manuellen Starten eines Prozesses im Vordergrund! Der Docker Daemon läuft als Systemd-Dienst und kümmert sich im Hintergrund u.a. um das Schreiben dieser Logs ins Dateisystem. Dafür legt er im Docker Lib Verzeichnis eine Ordnerstruktur an, in die er Protokolle ins JSON-Format schreibt.

Das bietet mehrere Vorteile: Wir können die Protokolle mit Docker-Befehlen wie docker logs oder docker compose logs bequem einsehen. Insbesondere mit Compose sieht man dadurch nicht nur die Logs eines Containers, sondern sämtlicher (z.B. Datenbank). Außerdem können sie zentral weitergeleitet werden, etwa an Elasticsearch. So hat man diese gebündelt mit anderen Protokollen, dazu filter- und durchsuchbar.

Praktischer Einblick in Docker Logs

Mit docker inspect lassen sich viele tiefgehende Dinge erkunden, die Docker im Hintergrund für uns erledigt. Wer dies auf einem Container aufruft, wird schnell hunderte Zeilen an Ausgabe enthalten: Netzwerkeinstellungen, Volumes, Limits usw. Mit einem Go-Template1 kann der Pfad ermittelt werden:

$ docker inspect --format='{{.LogPath}}' portal_nginx
/var/lib/docker/containers/f4883a451474c34445abb74028235c80811c0f6c2ca01e9fb8c83ea992ed99aa/f4883a451474c34445abb74028235c80811c0f6c2ca01e9fb8c83ea992ed99aa-json.log

f4883a451474c34445abb74028235c80811c0f6c2ca01e9fb8c83ea992ed99aa ist hierbei die einzigartige ID, welche Docker jedem Container zuweist. Da sie im Ordner & Dateiname steht, werden die Pfade sperrig lang. Wer mit tail in die Datei schaut, sieht jeweils den originalen Logeintrag zusammen mit Zeitstempel & dem Ausgabestream (stdout/stderr) als Meta-Info:

{"log":"- - 1.2.3.4 - - [19/Dec/2025:15:06:56 +0100] \"POST /portal/wp-admin/admin-ajax.php HTTP/1.1\" 200 109 \"https://u-labs.de/portal/wp-admin/post.php?post=16048\u0026action=edit\" \"Mozilla/5.0 (X11; Linux x86_64; rv:145.0) Gecko/20100101 Firefox/145.0\"\n","stream":"stdout","time":"2025-12-19T14:06:56.05847318Z"}

Abgesehen vom JSON-Format mit den Metadaten verhält sich diese Datei wie jede andere Datei: Sobald eine Ausgabe im Container erfolgt, schreibt der Docker-Daemon sie in diese Datei. Man könnte ihr auch mit tail -f folgen. Wobei ich die Werkzeuge von Docker bevorzugen würde, sie sind komfortabler. Mit Werkzeugen wie ls kann die Größe im Dateisystem geprüft werden. Da der Docker-Damon als root läuft, sind dafür entsprechende rechte nötig.

Das Problem: Es gibt kein Limit

Naturgemäß werden Protokolle mit der Zeit immer größer. Zur Grundausstattung jedes Servers gehört daher ein Mechanismus, der sie nach einer bestimmten Zeitspanne archiviert und irgendwann löscht. Schließlich braucht in aller Regel niemand mehr die Webserver-Logs von vor 3 Jahren. Handelt es sich um einen öffentlich erreichbaren Dienst, kann das sogar kritisch werden: Gemäß dem Grundsatz der Datensparsamkeit müssen zumindest personenbezogene Daten nach (notwendigem) Gebrauch gelöscht werden.

Auf klassischen GNU/Linux-Systemen kommt dafür Logrotate zum Einsatz. Meist komprimiert man die Dateien damit nach einigen Tagen, weil sie so deutlich weniger Speicherplatz belegen. Wochen oder Monate später können sie gelöscht werden. Fehlt ein solcher Mechanismus, werden die Dateien immer größer. Je nach Datenmenge & Aktivität können auch Textdateien auf mehrere GB wachsen. Insbesondere Webserver. Selbst wenn die Webseite nicht all zu stark besucht ist, gibt es immer mehr Bots im WWW. Insbesondere durch den KI Hype hat das stark zugenommen. Im U-Labs Portal wächst das Zugriffsprotokoll von Nginx trotz regelmäßiger Leerung schnell auf über 2GB an:

$ sudo ls -lh $(docker inspect --format='{{.LogPath}}' portal_nginx)
-rw-r----- 1 root root 2.2G Dez 19 15:22 /var/lib/docker/containers/f4883a451474c34445abb74028235c80811c0f6c2ca01e9fb8c83ea992ed99aa/f4883a451474c34445abb74028235c80811c0f6c2ca01e9fb8c83ea992ed99aa-json.log

Systemweit kannst du dir mit folgendem Einzeiler eine Übersicht über sämtliche derzeit vorhandenen Logdateien mit ihrer Größe verschaffen:

$ sudo ls -lh $(docker inspect --format='{{.LogPath}}' $(docker ps --format='{{.Names}}'))
-rw-r----- 1 root root  18M Dez 19 14:44 /var/lib/docker/containers/01e91183a5e1762adade346dd8017ac3e8679a94c55313a44d0368fda67d5526/01e91183a5e1762adade346dd8017ac3e8679a94c55313a44d0368fda67d5526-json.log
-rw-r----- 1 root root  24K Dez 16 01:18 /var/lib/docker/containers/063c2f8bb0218cbe3ea00da826b7209413c8a7a78d0eaffccd53ea378dbae7f4/063c2f8bb0218cbe3ea00da826b7209413c8a7a78d0eaffccd53ea378dbae7f4-json.log
-rw-r----- 1 root root 1.5M Dez 19 02:32 /var/lib/docker/containers/100da33621b9125322d2362f21edb6f9d06291e4e27732306df0536b3e277794/100da33621b9125322d2362f21edb6f9d06291e4e27732306df0536b3e277794-json.log
-rw-r----- 1 root root  75M Dez 19 15:28 /var/lib/docker/containers/1828edd3e9e1af73750f3382f942a505210711eb68a3dc2ad965511fd251782b/1828edd3e9e1af73750f3382f942a505210711eb68a3dc2ad965511fd251782b-json.log
[...]

Insbesondere, wenn du hier bereits große Dateien siehst, besteht Handlungsbedarf. Auch wenn nicht würde ich es jedoch nicht darauf ankommen lassen: Logdateien können das Dateisystem mit der Zeit beachtlich füllen! Jeden Tag ein paar MB summieren sich mit der Zeit. Außerdem kann ein derzeit langsames Wachstum durch z.B. (vergessene) Debug-Logs oder exzessive Fehlermeldungen in Zukunft noch steigen. Docker legt seine Dateien standardmäßig in vielen Distributionen nach /var/lib/docker ab. Für gewöhnlich ist das keine eigene Partition, sondern liegt im Wurzel-Dateisystem. Das möchte man nicht unbedingt voll laufen lassen. Daher: Besser vorzeitig darum kümmern.

$ docker info | grep "Docker Root"
 Docker Root Dir: /var/lib/docker

So begrenzt du Dockers Protokolle systemweit

Unter Docker greift Logrotate nicht ohne weiteres. Doch die Container Laufzeitumgebung besitzt bereits zwei eingebaute Wege, um Logdateien ein Limit zu verpassen. Methode 1 ist global, d.H. sie gilt standardmäßig für alle Container. Ich bevorzuge sie und setze damit eine sinnvolle Standard-Begrenzung. So muss dies nicht explizit für jeden einzelnen Container angegeben werden, obwohl dies bei vielen ähnlich ist.

Das globale Limit wird in der Konfigurationsdatei des Docker Daemons unter /etc/docker/daemon.json festgelegt. Sollte sie nicht existieren, die Datei mit Root-Rechten anlegen:

{
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  }
}

Im Kern bietet Docker ähnliche Funktionalität wie Logrotate: Die erste Anweisung setzt ein Limit pro Logdatei, in diesem Beispiel 100MB. Über die zweite wird begrenzt, wie viele Logdateien es geben darf (hier 3). Docker würde also die erste Datei füllen, bis sie 100MB erreicht hat. Dann eine zweite & dritte, ebenfalls bis jeweils 100MB. Nachdem die dritte Datei das 100MB Limit erreicht hat, löscht es die älteste der Dateien. Komprimieren wird von Docker nicht unterstützt. Wer das möchte, kann zusätzlich Logrotate verwenden.

Durch die Client-Server Struktur von Docker werden die Änderungen erst nach dem Neustart des Daemons wirksam. Zu beachten: Dies stoppt sämtliche laufende Container & startet sie ebenfalls neu!

sudo systemctl restart docker

Limits pro Container

Es kann sinnvoll sein, diese globalen Grenzwerte für einzelne Container höher oder niedriger zu setzen. Ein Beispiel: Du betreibst Webserver mit hoher Last. Hier sollen die Limits höher sein, um mehr als nur die letzten Stunden nachvollziehen zu können. Bei anderen Anwendungen, die weniger Ausgaben erzeugen, ist das nicht nötig. In solchen Fällen nutzt du das Attribut logging in der docker-compose.yml, um Abweichungen von den systemweiten Begrenzungen anzugeben:

services:
  nginx:
    image: nginx:1.27-alpine
    restart: always
    # ...
    logging:
      driver: "json-file"
      options:
        max-file: "3"
        max-size: "10m"

Den Daemon neu zu starten, ist hierbei nicht nötig. Es genügt, die Container mit Docker-Compose neu zu starten, damit die Änderungen wirksam werden:

docker compose up -d

Quellen

  1. https://docs.docker.com/engine/cli/formatting/ ↩︎

Leave a Reply