Aller au contenu
Conteneurs & Orchestration medium

Dozzle : lire les logs de vos conteneurs Docker dans le navigateur

16 min de lecture

Dozzle affiche les logs de vos conteneurs Docker dans un navigateur, en temps réel, sans rien installer sur l'hôte au-delà d'un conteneur de 15 Mo de mémoire. Là où docker logs -f vous condamne à un terminal par conteneur, Dozzle regroupe tout dans une interface unique, colore les niveaux de log, découpe l'écran et cherche par expression régulière.

C'est un visualiseur, pas une solution de journalisation. Il ne stocke rien, ne conserve rien, et ne cherche pas dans le passé. Ce guide installe la version 10.6.9 avec Docker Compose, puis traite le sujet que la plupart des tutoriels escamotent : Dozzle demande le socket Docker, et ce socket vaut un accès root sur votre machine.

  • Installer Dozzle avec Compose et suivre les logs de plusieurs conteneurs
  • Restreindre ce que Dozzle expose, par label et par authentification
  • Comprendre pourquoi monter le socket en :ro ne protège de rien, preuve à l'appui
  • Isoler Dozzle du socket avec un proxy, et vérifier que ça marche
  • Décider quand Dozzle suffit, et quand il faut une vraie stack de logs
  • Docker Engine 19.03 ou plus récent (API 1.40 minimum) et le plugin Compose. Voir le guide Docker.
  • Un port libre pour l'interface. Ce guide utilise 8091.
  • Le pilote de logs json-file, local ou journald (le défaut de Docker convient).

La distinction est fondamentale, et l'auteur du projet est le premier à la poser : Dozzle « ne stocke aucun fichier de log, il est conçu uniquement pour la consultation en direct ».

DozzleUne stack de logs (Loki, ELK)
Rétentionaucune, il lit le flux de Dockerdes semaines ou des mois
Recherche passéeimpossiblec'est sa raison d'être
Alertingnonoui, avec corrélation
Coûtun conteneur de 15 Moplusieurs services, du stockage
Mise en routedeux minutesune journée

Autrement dit, Dozzle remplace docker logs -f sur plusieurs conteneurs à la fois. Il ne remplace pas Grafana Alloy et Loki, qui collectent, stockent et indexent. Sur un homelab, un poste de développement ou une petite infrastructure, c'est exactement ce qu'il faut. Dès qu'il faut retrouver ce qui s'est passé la semaine dernière, prouver une conformité ou déclencher une alerte, Dozzle n'est pas l'outil.

Un fichier Compose suffit. Voici la version que nous allons construire pas à pas, avec deux conteneurs qui produisent de vrais logs pour avoir quelque chose à regarder.

compose.yaml
services:
dozzle:
image: amir20/dozzle:v10.6.9
container_name: dozzle
command: ["--no-analytics", "--filter", "label=lab=dozzle"]
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- dozzle_data:/data
ports:
- "8091:8080"
api:
image: nginx:1.27-alpine
container_name: lab-api
labels:
lab: dozzle
ports:
- "8092:80"
worker:
image: alpine:3.21
container_name: lab-worker
labels:
lab: dozzle
command: >
sh -c "i=0; while true; do
i=$$((i+1));
if [ $$((i % 5)) -eq 0 ]; then echo \"level=error msg=\\\"paiement refuse\\\" order=$$i\";
else echo \"level=info msg=\\\"commande traitee\\\" order=$$i\"; fi;
sleep 2; done"
volumes:
dozzle_data:
Fenêtre de terminal
docker compose up -d

Ouvrez http://localhost:8091/. Les logs défilent immédiatement.

Interface de Dozzle affichant les logs d'un conteneur en temps réel

Trois choses se voient sur cette capture. Dozzle colore les niveaux de log, en rouge les error et en vert les info, sans configuration. Il découpe les paires clé/valeur (level=, msg=, order=) et les met en évidence. Et il affiche en haut la consommation CPU et mémoire du conteneur, en direct.

--no-analytics coupe la télémétrie. Dozzle envoie par défaut des données anonymes à chaque démarrage : version, mode de déploiement, nombre de conteneurs, plus un identifiant aléatoire par installation. Aucun contenu de log n'est transmis, l'auteur est clair là-dessus. Reste que c'est activé par défaut, et que le README parle encore de Google Analytics alors que le binaire ne contient plus aucune trace de Google : la destination réelle est b.dozzle.dev/event. La documentation est en retard sur le code, et cela vaut d'être su.

--filter label=lab=dozzle restreint Dozzle aux conteneurs qui portent ce label. Sans ce filtre, Dozzle affiche les logs de tous les conteneurs de la machine. Sur un serveur qui héberge autre chose, cela signifie exposer les journaux de votre base de données, de votre reverse proxy et de tout le reste dans la même interface.

Étape 2 - Le socket Docker, et le mensonge du :ro

Section intitulée « Étape 2 - Le socket Docker, et le mensonge du :ro »

Vous avez remarqué le :ro à la fin du montage du socket. Des dizaines de tutoriels l'ajoutent en expliquant que Dozzle n'a besoin que de lire, et que le mode lecture seule garantit qu'il ne peut rien casser.

C'est faux, et la démonstration tient en une commande. Depuis un conteneur qui monte le socket en lecture seule, créons un conteneur via l'API Docker :

Fenêtre de terminal
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock:ro alpine:3.21 sh -c '
apk add --no-cache curl >/dev/null
curl -s -o /dev/null -w "%{http_code}\n" -X POST --unix-socket /var/run/docker.sock \
-H "Content-Type: application/json" \
-d "{\"Image\":\"alpine:3.21\"}" \
http://localhost/containers/create'
201

HTTP 201 : le conteneur a été créé. En lecture seule.

L'explication est dans la nature d'un socket. Un fichier ordinaire s'écrit avec l'appel système write(), que le montage :ro bloque effectivement. Un socket, lui, communique avec send() et recv(), que le système de fichiers ne filtre pas. Le :ro empêche de supprimer le socket ou d'en changer les métadonnées, rien de plus. Les requêtes passent.

Étape 3 - Isoler Dozzle derrière un proxy de socket

Section intitulée « Étape 3 - Isoler Dozzle derrière un proxy de socket »

La vraie parade est de ne jamais donner le socket à Dozzle. Un proxy de socket s'intercale, expose l'API Docker sur le réseau interne, et n'autorise que les points d'entrée dont Dozzle a besoin.

compose.secure.yaml
services:
docker-socket-proxy:
image: tecnativa/docker-socket-proxy:0.3.0
container_name: dozzle-socket-proxy
environment:
CONTAINERS: 1 # lister et lire les conteneurs
INFO: 1 # nom d'hôte, version, CPU et RAM
POST: 0 # aucune écriture
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- dozzle-net
restart: unless-stopped
dozzle:
image: amir20/dozzle:v10.6.9
container_name: dozzle
command: ["--no-analytics"]
environment:
DOZZLE_REMOTE_HOST: tcp://docker-socket-proxy:2375|lab
ports:
- "8091:8080"
networks:
- dozzle-net
depends_on:
- docker-socket-proxy
restart: unless-stopped
networks:
dozzle-net:

Dozzle n'a plus aucun volume, plus aucun accès au socket. Il parle au proxy en TCP, sur un réseau interne. Vérifions que la barrière tient, avec la même requête que tout à l'heure :

Fenêtre de terminal
docker run --rm --network lab-dozzle_dozzle-net alpine:3.21 sh -c '
apk add --no-cache curl >/dev/null
echo -n "POST /containers/create : "
curl -s -o /dev/null -w "%{http_code}\n" -X POST -H "Content-Type: application/json" \
-d "{}" http://docker-socket-proxy:2375/containers/create
echo -n "GET /containers/json : "
curl -s -o /dev/null -w "%{http_code}\n" http://docker-socket-proxy:2375/containers/json'
POST /containers/create : 403
GET /containers/json : 200

403 sur la création, 200 sur la lecture. Là où le :ro renvoyait 201, le proxy refuse. La différence n'est pas cosmétique : le filtrage se fait au niveau de l'API, c'est-à-dire au seul endroit où il a un sens.

Un détail qui fait perdre du temps : Docker Compose préfixe le nom du réseau par celui du dossier, d'où le lab-dozzle_dozzle-net ci-dessus. Si votre projet s'appelle autrement, lisez le nom réel avec docker network ls.

Par défaut, Dozzle n'a aucune authentification. La configuration exposée par l'interface le dit sans détour :

Fenêtre de terminal
curl -s http://localhost:8091/ | grep -o '"authProvider":"[a-z-]*"'
"authProvider":"none"

N'importe qui atteignant le port lit tous vos journaux. Sur localhost, c'est sans conséquence. Dès que Dozzle sort de la machine, c'est inacceptable.

Dozzle sait générer un fichier d'utilisateurs, avec des mots de passe hachés en bcrypt :

  1. Créez le compte et rangez le fichier dans un dossier monté :

    Fenêtre de terminal
    mkdir -p data
    docker run --rm amir20/dozzle:v10.6.9 generate \
    --password "UnMotDePasseSolide" --name "Stephane" --email "s@lab.local" admin > data/users.yml

    Le fichier obtenu contient bien un hachage bcrypt, reconnaissable à son préfixe $2a$ :

    users:
    admin:
    email: s@lab.local
    name: Stephane
    password: $2a$11$JqB9QJC77f91I0AMFq9.ke...
  2. Activez le fournisseur d'authentification dans le service Dozzle :

    environment:
    DOZZLE_AUTH_PROVIDER: simple
    volumes:
    - ./data:/data
  3. Vérifiez que l'accès anonyme est bien refusé :

    Fenêtre de terminal
    curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" http://localhost:8091/
    307 -> http://localhost:8091/login?redirectUrl=/

Dozzle sait aussi déléguer l'authentification à un proxy d'authentification (Authelia, oauth2-proxy, Cloudflare Access), via l'en-tête Remote-User. C'est la bonne approche si vous avez déjà un SSO, et cela permet d'attribuer à chaque utilisateur un filtre de conteneurs différent.

Dozzle n'est plus le simple visualiseur en lecture seule d'il y a deux ans. Il sait, sur option, ouvrir un shell dans un conteneur (DOZZLE_ENABLE_SHELL) et piloter son cycle de vie (DOZZLE_ENABLE_ACTIONS). Ces surfaces ont un prix, et il a été payé.

VulnérabilitéGravitéNatureCorrigée en
CVE-2026-452988.6SSRF avant authentification sur le test de webhook10.5.2
CVE-2026-449858.7 à 9.6Détournement de WebSocket sur /exec et /attach10.5.2
CVE-2026-247408.7Contournement du filtrage par label en mode agent9.0.3

La deuxième mérite un mot, parce qu'elle frappait même avec l'authentification correctement configurée : le contrôle d'origine des WebSockets acceptait tout, et le cookie de session était en SameSite: Lax. Une page malveillante sur un sous-domaine voisin pouvait ouvrir un shell dans vos conteneurs.

La 10.6.9 installée ici corrige les trois. Deux réflexes en découlent. Laissez DOZZLE_ENABLE_SHELL et DOZZLE_ENABLE_ACTIONS désactivés : ce sont eux qui transforment un visualiseur en porte d'entrée. Et surveillez les versions, car un déploiement oublié sur une version d'il y a un an est aujourd'hui vulnérable.

La plupart des problèmes de Dozzle ne viennent pas de Dozzle, mais de ce qu'il lit : le pilote de logs de Docker et le socket. Le tableau ci-dessous part du symptôme visible pour remonter à sa cause réelle.

SymptômeCauseCorrectif
Aucun log ne s'affichepilote de logs distant (splunk, fluentd, awslogs) avec cache-disabled: trueDozzle lit l'API docker logs : réactiver le cache de double journalisation
CPU et mémoire à 0 %runtime non standard ou API distantelimitation connue selon le runtime, sans correctif
L'interface rameplusieurs centaines de conteneursle goulot est le navigateur, pas le serveur ; filtrer par label
host not found sous PodmanPodman ne génère pas d'engine-idcréer /var/lib/docker/engine-id avec uuidgen
L'interface s'ouvre sans mot de passeauthProvider: none par défautDOZZLE_AUTH_PROVIDER: simple et un users.yml
Dozzle voit des conteneurs qui ne le regardent pasaucun filtre--filter label=...
  • Dozzle est un visualiseur de logs en direct, pas une solution de journalisation : aucune rétention, aucune recherche dans le passé.
  • Il remplace docker logs -f sur plusieurs conteneurs ; il ne remplace pas Loki ou ELK.
  • Le montage du socket en :ro ne protège de rien : un socket répond à send(), pas à write(). La preuve tient en une requête qui renvoie HTTP 201.
  • Le socket Docker vaut un accès root sur l'hôte. La vraie parade est un proxy de socket (403 en écriture, 200 en lecture) ou le mode agent.
  • Aucune authentification par défaut : authProvider: none. Un users.yml en bcrypt ou un proxy d'authentification s'impose dès que Dozzle sort de localhost.
  • Sans --filter, Dozzle expose les logs de tous les conteneurs de la machine.
  • La télémétrie est active par défaut : --no-analytics la coupe. Le README parle de Google Analytics, le binaire n'en contient plus trace.
  • Trois vulnérabilités sérieuses ont été corrigées en 2026, toutes sur les surfaces /exec, /attach et webhook. Laissez le shell et les actions désactivés.
  • Épinglez la version. latest sur un service exposé n'est pas une stratégie.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn