Dozzle: When You Don't Need an Observability Stack

TL;DR: Dozzle is a small Go log viewer that mounts your Docker socket and gives you live, colored, searchable logs in ten seconds - no pipeline, no retention, no storage bill. After two posts about shaping telemetry with OTTL and continuous profiling, this one argues the other direction: most “we need observability” moments are really “what is breaking right now” moments, and for those a viewer beats a stack. The skill is not picking a side - it’s knowing which question you’re asking.
The confession first
I gave a talk in September about what observability costs. I run an OpenTelemetry Collector with OTTL statements that shape telemetry before it’s stored, and I wrote four thousand words about profiling as the fourth signal. And when a container in my homelab misbehaves, I open none of it. I open Dozzle - a log viewer that starts faster than Grafana renders its login page.
That’s not hypocrisy. That’s two different problems wearing the same word.
What Dozzle actually is
One container, one volume mount, one port:
# the whole deployment, verbatim from my homelab compose
services:
dozzle:
image: amir20/dozzle:latest
restart: unless-stopped
environment:
- DOZZLE_NO_ANALYTICS=true # see the telemetry note below
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
ports:
- 8080:8080 # never exposed publicly - UI reachable only over my tailnet
The numbers behind the project say something interesting about the gap between visibility and use: about 12,000 GitHub stars, but on the order of 300 million Docker Hub pulls, and Docker sponsors the project. That ratio - modest stars, enormous pulls - is the signature of a tool people run rather than talk about.
What you get: live streams of every container, log-level colors you can filter by eye instead of writing level=error queries, a scrollbar that shows both position and the age of the line under your cursor, stdout/stderr separation, and a merged view of all containers with the name labelled on every line. The side-by-side panes - each with independent search and scroll - are the feature I’d actually pay for: the service that’s dying in one pane, the service that’s killing it in the other.
The feature nobody expects: SQL over your logs
If your logs are structured JSON, Dozzle indexes them into a table and lets you query them with plain SQL - aggregation by level, counts, limits. The trick is where it runs: DuckDB, client-side, in your browser. No backend call. The honest limit is in Dozzle’s own phrasing - results are truncated “to prevent total annihilation of your browser.” At real volume, client-side analytics stops being an alternative to a backend and becomes a preview of a sample. For a homelab, you will not hit that wall.
Alerts, and the trap that comes with them
Three alert types: log content (container + level + substring, with live preview of matching lines), metrics (CPU/memory with a sampling window and cooldown), and container events (start, stop, restart, health). Targets are webhooks - Slack, Discord, or ntfy straight to your phone.
The source material for this post - a DevOps Toolbox video - demonstrates the trap live: the author sets a content alert, gets a flood of notifications, and coins the best definition of alert fatigue I’ve heard: “a professional DevOps term for: I muted the critical alerts channel because it went off constantly - so nobody saw the leaked production keys, even though we had alerts.” Note the asymmetry in Dozzle itself: metric alerts have a cooldown, content alerts don’t. The tool names the disease and ships a default that can cause it.
My own usage is deliberately narrow: one alert type, container events - a restart or an unhealthy status pushes to my phone via ntfy. That’s the alert that matches the tool’s philosophy: it fires on “something is breaking right now,” which is the only question Dozzle answers anyway. Content alerts I leave off - no cooldown means the first noisy error loop would train me to ignore the channel, and the quote above explains exactly where that road ends.
The anecdote that argues for something else
The video’s centerpiece: the author plugs Dozzle into a fresh K3s cluster “just to see what’s going on” and immediately finds an AWS access key and a second hardcoded secret in plaintext logs. His first reflex - alert on the phrase access key - is symptom detection. The right conclusion arrives a beat later: if the application prints keys, it has keys it shouldn’t be holding that way. The fix isn’t a better alert; it’s that the secret should never reach stdout at all. I’ve written about the laptop end of that problem in pass and the production end in GitOps secrets.
But notice what the anecdote proves about viewers: nobody finds plaintext secrets in a log pipeline they never look at. Cheap, frictionless log reading is a security control in itself - not because the tool does anything, but because it makes looking free.
Where the honest line runs
Here’s the framework the video doesn’t state, and the entire reason this post exists:
| The question you’re asking | What answers it |
|---|---|
| “What is breaking right now?” | a viewer - Dozzle, kubectl logs, docker logs |
| “What happened last Tuesday?” | retention - a collector shipping to storage |
| “Is p95 degrading across deploys?” | metrics and traces - the actual stack |
| “Why is it slow when nothing errors?” | profiling - the fourth signal |
Dozzle reads live streams from the runtime. When the container goes away, the logs go with it. That’s not a missing feature - it’s the boundary of the problem class. The mistake I see (and made) isn’t choosing the wrong tool; it’s answering a first-row question with a fourth-row deployment. A weekend of Loki configuration to find out why a container is crash-looping today is over-engineering with extra steps.
The reverse mistake is subtler: growing until you have last-Tuesday questions and still only having a viewer. If you’re SSH-ing into hosts to grep rotated files, you’ve outgrown the first row - that’s when the collector post applies, and OTTL is how you keep the bill sane.
Two caveats the hype skips
The Docker socket mount is root-equivalent access to the host. Every Dozzle tutorial treats it as a setup detail. It’s the single most consequential security decision in the deployment - especially if you expose the UI beyond localhost. The Kubernetes mode is cleaner: DOZZLE_MODE=k8s with RBAC scoped to get, list, watch on pods. Still worth saying: cluster-wide watch pods means reading every log line in the cluster, and logs are where PII and tokens go to hide. In a single-person homelab, fine. In a shared cluster, that’s a decision someone should have to defend.
Telemetry exists. Anonymized config data, sent in the first minute, disabled with an environment variable. The video’s author sniffed the traffic and left it on. I turn it off - that’s the DOZZLE_NO_ANALYTICS=true line in the compose above. Not out of suspicion (the author verified the traffic is harmless); it’s the same default-deny reflex as the tailnet-only port, and a tool this good deserves support in the form of a star and a recommendation rather than a data point.
Where it lands in my setup
The split in my homelab follows the table above, almost mechanically. Dozzle runs on the compose hosts - the layer where the questions are first-row questions: is the container up, what did it just print, why is it restarting. The UI is reachable only over the tailnet, the socket is mounted read-only, and the one alert wired to my phone is the container-event one. The cluster is different territory: that’s where deploys, trends and “what happened last Tuesday” live, so that’s where the OTel collector and the metrics stack earn their keep - the OTTL post is the bill of materials for that side of the line. The boundary rule, stated once: if losing the logs when the container dies would hurt, that workload has outgrown the viewer. Nothing on my compose hosts passes that test; most things in the cluster do.
The pattern I’d defend in a review: viewer first, stack when a question forces it. Every component in the observability pipeline should exist because a question you actually asked couldn’t be answered without it. Start with the tool that starts in ten seconds. When you catch yourself wishing the logs survived the container - that wish, written down, is your requirements document for the stack.
Attribution: the walkthrough that prompted this post is DevOps Toolbox’s Dozzle video - the AWS-key anecdote and the alert-fatigue definition are his. The framework for when a viewer is the wrong answer is mine, and so is the bill for learning it.