## File: README.md

# Dozzle - [dozzle.dev](https://dozzle.dev/)

Dozzle is a lightweight, web-based application for monitoring Docker logs in real time. It doesn't store any log files—it's designed purely for live log viewing.

https://github.com/user-attachments/assets/66a7b4b2-d6c9-4fca-ab04-aef6cd7c0c31

[](https://hub.docker.com/r/amir20/dozzle/)
[](https://hub.docker.com/r/amir20/dozzle/)
[](https://hub.docker.com/r/amir20/dozzle/)


> [!NOTE]
> If you like Dozzle, check out [`dtop`](https://github.com/amir20/dtop), a top-like application for monitoring Docker containers. It integrates with Dozzle to link directly to container logs.

## Features

- Intelligent fuzzy search for container names
- Search logs using regex
- Search logs using [SQL queries](https://dozzle.dev/guide/sql-engine)
- Small memory footprint
- Split screen for viewing multiple logs
- Live stats with memory and CPU usage
- Multi-user [authentication](https://dozzle.dev/guide/authentication) with support for forward proxy authorization
- [Swarm mode](https://dozzle.dev/guide/swarm-mode) support
- [Agent mode](https://dozzle.dev/guide/agent) for monitoring multiple Docker hosts
- Dark mode

Dozzle has been tested with hundreds of containers. However, it doesn't support offline searching. Products like [Loggly](https://www.loggly.com), [Papertrail](https://papertrailapp.com), or [Kibana](https://www.elastic.co/products/kibana) are better suited for full search capabilities.

## Getting Started

Dozzle is a small container (7 MB compressed). Pull the latest release with:

    $ docker pull amir20/dozzle:latest

### Running Dozzle

The simplest way to use Dozzle is to run the Docker container. Mount the Docker Unix socket with `--volume` to `/var/run/docker.sock`:

    $ docker run --name dozzle -d --volume=/var/run/docker.sock:/var/run/docker.sock -v dozzle_data:/data -p 8080:8080 amir20/dozzle:latest

Dozzle will be available at [http://localhost:8080/](http://localhost:8080/).

Here is a Docker Compose example:

    services:
      dozzle:
        container_name: dozzle
        image: amir20/dozzle:latest
        volumes:
          - /var/run/docker.sock:/var/run/docker.sock
          - dozzle_data:/data
        ports:
          - 8080:8080
    volumes:
      dozzle_data:

For advanced options like [authentication](https://dozzle.dev/guide/authentication), [remote hosts](https://dozzle.dev/guide/remote-hosts), or common [questions](https://dozzle.dev/guide/faq), see the documentation at [dozzle.dev](https://dozzle.dev/guide/getting-started).

### Image Tags

Images are published to both [Docker Hub](https://hub.docker.com/r/amir20/dozzle) and [ghcr.io](https://github.com/amir20/dozzle/pkgs/container/dozzle) with identical tags.

| Tag                                             | Description                                                            |
| ----------------------------------------------- | ---------------------------------------------------------------------- |
| `latest`                                        | Most recent release. Recommended for most users.                       |
| `v10.6.15`                                      | An exact release. Pin this for reproducible deployments.               |
| `v10.6`                                         | Latest patch within that minor version. Picks up bug fixes only.       |
| `v10`                                           | Latest release within that major version. Picks up new features too.   |
| `alpine`                                        | Same as `latest`, but on an Alpine base instead of `scratch`.          |
| `v10.6.15-alpine`, `v10.6-alpine`, `v10-alpine` | Alpine variants of the version tags above.                             |
| `master`                                        | Built from the `master` branch on every push. Unreleased and unstable. |
| `pr-1234`, `pr-1234-alpine`                     | Built from pull request #1234, for testing a fix before it ships.      |

The default images are built `FROM scratch` and contain only the Dozzle binary, which is why they are around 7 MB compressed. That also means there is no shell inside them. Use the `alpine` variants only if something in your setup needs one, most commonly platforms that bind-mount a `#!/bin/sh` wrapper over the container entrypoint such as Unraid's per-container Tailscale toggle. See the [FAQ](https://dozzle.dev/guide/faq) for details.

Avoid `latest` and `master` in production. `latest` moves on every release and `master` is unreleased code.

## Swarm Mode

Dozzle works with Docker Swarm. You can run Dozzle as a global service:

    $ docker service create --name dozzle --env DOZZLE_MODE=swarm --mode global --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock -p 8080:8080 amir20/dozzle:latest

See the [Swarm Mode](https://dozzle.dev/guide/swarm-mode) documentation for more details.

## Agent Mode

Dozzle can monitor multiple Docker hosts. Run Dozzle in agent mode with:

    $ docker run -v /var/run/docker.sock:/var/run/docker.sock -p 7007:7007 amir20/dozzle:latest agent

See the [Agent Mode](https://dozzle.dev/guide/agent) documentation for more details.

## Technical Details

Dozzle uses automatic API negotiation, which works with most Docker configurations. Dozzle also works with [Colima](https://github.com/abiosoft/colima) and [Podman](https://podman.io/).

Dozzle requires Docker Engine 19.03 or newer (API version 1.40+). Older daemons are not supported by the underlying Docker SDK.

### Installation on Podman

By default, Podman doesn't have a background process, but you can enable the remote socket for Dozzle to work.

First, verify if your Podman installation has the remote socket enabled:

```
podman info
```

If you see output like this under the remote socket key, it's already enabled:

```
  remoteSocket:
    exists: true
    path: /run/user/1000/podman/podman.sock
```

If it's not enabled, follow [this tutorial](https://github.com/containers/podman/blob/main/docs/tutorials/socket_activation.md) to enable it.

Once the Podman remote socket is enabled, you can run Dozzle:

```
podman run --volume=/run/user/1000/podman/podman.sock:/var/run/docker.sock -d -p 8080:8080 docker.io/amir20/dozzle:latest
```

Nothing else is needed. Dozzle works out a stable host id for Podman on its own, since Podman is daemonless and has no engine identity to report.

> [!NOTE]
> Older versions of this page told you to create a `/var/lib/docker/engine-id` file to prevent `host not found` errors. That never worked. Podman's Docker-compatible `/info` endpoint does not read any file, it returns a new random UUID on every call, so Dozzle now derives the id instead. You can delete the file.

For more details, see [Podman Info](docs/guide/podman.md) or the [FAQ](docs/guide/faq.md#i-am-seeing-host-not-found-error-in-the-logs-how-do-i-fix-it).

## Security

Dozzle supports file-based authentication and forward proxy authentication with tools like [Authelia](https://www.authelia.com/). See the documentation at https://dozzle.dev/guide/authentication.

## Analytics

Dozzle collects anonymous user configurations using Google Analytics. Why? Dozzle is an open source project with no funding, so there's no time for formal user studies. Analytics help prioritize features and fixes based on how people use Dozzle. This data is completely public and can be viewed live on the [Data Studio dashboard](https://datastudio.google.com/s/naeIu0MiWsY).

To disable analytics, use the `--no-analytics` flag.

## Environment Variables and Configuration

Dozzle follows the [12-factor](https://12factor.net/) model. Configuration can be done via CLI flags or environment variables. See the documentation at [dozzle.dev/guide/supported-env-vars](https://dozzle.dev/guide/supported-env-vars) for more details.

## Support

There are many ways to support Dozzle:

- Use it! Write about it! Star it! If you love Dozzle, drop me a line and tell me what you love.
- Blog about Dozzle to spread the word. If you're good at writing, send PRs to improve the documentation at [dozzle.dev](https://dozzle.dev/).
- Sponsor my work at https://www.buymeacoffee.com/amirraminfar

[](https://www.buymeacoffee.com/amirraminfar)

## License

[MIT](LICENSE)

## Building

Want to contribute? Great! Dozzle has two parts: a **Go backend** that talks to Docker, and a **Vue frontend** that runs in the browser. You don't need to know both — pick the side that matches what you want to change. For documentation fixes, no setup is needed at all; just edit the file on GitHub.

### 1. Install the prerequisites

You'll need [Go](https://go.dev/doc/install) (1.25+), [Node.js](https://nodejs.org/en/download/), [Bun](https://bun.com/docs/installation), and [protoc](https://grpc.io/docs/protoc-installation/).

On macOS, you can install everything in one go:

```bash
brew install go node oven-sh/bun/bun protobuf
```

On Linux (Debian/Ubuntu):

```bash
sudo apt install golang nodejs protobuf-compiler
curl -fsSL https://bun.com/install | bash
```

On Windows, we recommend using [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install) and following the Linux instructions.

### 2. Clone and set up

```bash
git clone https://github.com/amir20/dozzle.git
cd dozzle
bun install                 # installs frontend dependencies
go install tool             # installs Go build tools listed in go.mod (air, protoc-gen-go, etc.)
make generate               # generates TLS certificates and protobuf code (only needed once)
```

### 3. Start the dev server

```bash
make dev
```

Open [http://localhost:3100](http://localhost:3100) — you should see the Dozzle UI connected to your local Docker. Both the frontend and backend reload automatically when you save a file.

### Making your first change

Try editing `assets/pages/index.vue` and saving — the browser updates instantly. For backend changes, edit any `.go` file and the server will restart on its own.

### Troubleshooting

- **Nothing shows up at localhost:3100** — make sure Docker is running and the socket is accessible at `/var/run/docker.sock`.
- **`make generate` fails** — confirm `protoc` is on your PATH (`protoc --version`).
- **Still stuck?** Open a question in [GitHub Discussions](https://github.com/amir20/dozzle/discussions) — we're happy to help.

## Adding App Icons

Dozzle shows a logo next to containers whose image it recognizes. Icons are vendored from [homarr-labs/dashboard-icons](https://github.com/homarr-labs/dashboard-icons) into [`assets/icons/apps/`](assets/icons/apps/), and matching happens in `assets/utils/appIcons.ts`.

The repo ships an `add-app-icon` skill in `.claude/skills/`. To add icons with an AI coding agent, paste this prompt and fill in the images:

```
Use the add-app-icon skill in .claude/skills/add-app-icon/SKILL.md to add app icons
for these container images: , 
```

---

## File: docs/guide/actions.md

---
title: Container Actions
---

# Container Actions



Dozzle supports container actions, which allows you to `start`, `stop`, `restart`, `remove`, and `update` containers from the dropdown menu on the right next to the container stats. This feature is **disabled** by default and can be enabled by setting the environment variable `DOZZLE_ENABLE_ACTIONS` to `true`.

The `update` action pulls the latest image for the container and recreates it with the same configuration — useful for upgrading a container in place without editing its compose file. `update` only has a meaningful effect when the image uses a moving tag (e.g. `latest`, `stable`); a pinned tag will simply re-pull the same image.

The old container is kept, renamed, until the new one has run for 10 seconds without restarting, and has reported healthy if its image has a healthcheck. If the new container fails to start, exits, restarts or turns unhealthy, Dozzle removes it and puts the old one back, and the update reports **rolled back** with the reason. The new container is labelled `dev.dozzle.previous-image` with the image id it replaced, and `dev.dozzle.previous-ref` with that image's `repo@sha256:…` digest (absent for images built locally). A stopped container is never updated, since it may be stopped on purpose. It still shows when a newer image is available, but **Update** is not offered for it, the schedule skips it, and an update asked for anyway, from Dozzle Cloud for one, is refused with "Start the container first". Start it, and it can be updated.

> [!WARNING]
> `remove` deletes the container: data in its writable layer is lost, and its anonymous volumes are left behind, detached. `update` recreates the container and keeps every volume, anonymous ones included, and every bind mount. Only data written to the container's writable layer is lost.

::: code-group

```sh
docker run --volume=/var/run/docker.sock:/var/run/docker.sock -p 8080:8080 amir20/dozzle --enable-actions
```

```yaml [docker-compose.yml]
services:
  dozzle:
    image: amir20/dozzle:latest
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - 8080:8080
    environment:
      DOZZLE_ENABLE_ACTIONS: true
```

:::

## Update checking

Dozzle checks whether the image a container is running is still the one its registry serves. When they differ, a dot appears on the container's menu and the menu says an update is available.

The check asks the registry for the digest of the tag the container was created from, and compares it against the digest the container is actually running. It does this with a `HEAD` request for the image manifest, so no layers are downloaded and it does not count against Docker Hub pull rate limits. Answers are cached for six hours, and the same image is only ever looked up once no matter how many containers or hosts run it.

Because the comparison is against what the container is _running_, a container stays out of date until it is recreated, even if a newer image was already pulled onto the host.

Checking is separate from actions. Knowing a container is out of date is useful whether or not Dozzle is allowed to do anything about it, so the notice appears even when `DOZZLE_ENABLE_ACTIONS` is off. Only the `Update` button requires actions.

### Turning it off

`DOZZLE_IMAGE_CHECK_MODE` controls whether Dozzle contacts registries at all.

| Value       | Behavior                                                                 |
| ----------- | ------------------------------------------------------------------------ |
| `automatic` | Checks in the background when a container is viewed.                     |
| `manual`    | Never checks on its own. The menu offers a "Check for updates" action.   |
| `off`       | The feature is gone. No endpoint is registered and no requests are made. |

It defaults to whatever `DOZZLE_RELEASE_CHECK_MODE` is set to, so if you have already told Dozzle not to fetch releases automatically, it will not check images automatically either.

```yaml [docker-compose.yml]
services:
  dozzle:
    image: amir20/dozzle:latest
    environment:
      DOZZLE_IMAGE_CHECK_MODE: off
```

To stop checking a single container, such as one deliberately pinned to a version, label it. That also keeps it off the [auto-update schedule](#auto-updating-containers).

```yaml [docker-compose.yml]
services:
  database:
    image: postgres:18-alpine
    labels:
      dev.dozzle.update: off
```

A notification can also be shown when an update is found. It is off by default and lives under Settings.

### What cannot be checked

Some containers have nothing to compare, and Dozzle stays quiet rather than guessing:

- Images built locally, which carry no registry digest
- References pinned to a digest, which cannot drift
- Private registries, since Dozzle has no credentials of its own. In Kubernetes that includes images pulled with `imagePullSecrets`.

### Kubernetes

In Kubernetes mode the check compares the digest in the pod's status against the registry, so it works on containerd clusters such as k3s, EKS and GKE without extra permissions. The notice is informational and never comes with an `Update` button, because a pod's image belongs to its workload. For a moving tag like `:latest` with `imagePullPolicy: Always`, a [rollout restart](/guide/k8s#rollout-restart) picks up the new image. Anything else is a change to the workload's spec. The dashboard's updates button and drawer are not shown in Kubernetes mode.

### Updating Dozzle itself

The `Update` action on Dozzle's own container updates Dozzle in place. It pulls the new image and hands the swap to a short-lived helper container, so Dozzle goes away for a few seconds and comes back on the new version with the same configuration and volumes. It can also run on a schedule. See [How self-update works](/guide/setup-wizard#self-update) for what is kept and the setups it does not cover. Running Dozzle as a Swarm service updates through the orchestrator. Dozzle agents on other hosts are ordinary containers and update like anything else.

## Updating several containers at once

With actions on, the dashboard checks every container in one pass. Out-of-date containers get a small ring next to their name, and an **N updates** button appears above the container list. Both open the Updates drawer, which lists every container with a newer image, all selected. Untick anything you want to leave alone, then press **Update**.

Hosts update in parallel, and each host updates one container at a time, so no daemon has to pull a dozen images at once. The drawer shows each container moving through pulling, recreating and updated, and a failure on one container does not stop the rest. The update runs on the server, so closing the tab does not interrupt it. Opening the drawer again picks up where it is.

If Dozzle's own container is in the list, it always goes last, because updating it restarts Dozzle.

With `DOZZLE_IMAGE_CHECK_MODE=manual`, the button reads **Check for updates** until you press it. With actions off, the dashboard looks exactly as it does today, and each container's own menu still says when an update is available.

## Auto-updating containers {#auto-updating-containers}

Dozzle can update containers on a schedule. Set it up under **Settings → Updates** or in the [setup wizard](/guide/setup-wizard#auto-update):

- **When:** off, daily or weekly on Sunday, at a time of day. The same as `DOZZLE_AUTO_UPDATE` and `DOZZLE_AUTO_UPDATE_TIME`.
- **Which containers:** **Dozzle only**, **Labelled containers** (the default) or **Everything**. Dozzle itself follows the schedule in all three. The same as `DOZZLE_UPDATE_CONTAINERS` (`off`, `labelled` or `all`).

One label on a container decides the rest:

| `dev.dozzle.update` | What happens                                                                                            |
| ------------------- | ------------------------------------------------------------------------------------------------------- |
| `auto`              | Updated on the schedule, unless **Which containers** is **Dozzle only**                                 |
| _(no label)_        | Updated on the schedule under **Everything**. Otherwise checked and shown as an update, which you apply |
| `off`               | Never checked, never updated                                                                            |

```yaml
services:
  app:
    image: ghcr.io/example/app:latest
    labels:
      dev.dozzle.update: auto
```

Older labels still work: `dev.dozzle.auto-update=true` reads as `auto`, and `dev.dozzle.update-check=false` as `off`.

At the scheduled time Dozzle checks each container on the schedule against its registry and updates only the ones with a newer image, Dozzle itself last. Every update is the safe swap described above, so a new container that fails to stay up is replaced by the old one again. Containers that are stopped or unhealthy, that Dozzle [cannot check](#what-cannot-be-checked), or that were [rolled back](#rolling-back) from the image on offer are skipped. **Settings → Updates** lists the containers the next run will update.

Under **Everything**, a database on a floating tag like `postgres:latest` can move to a major version its data files cannot read. Picking **Everything** lists the containers that keep data in named volumes. Label those `dev.dozzle.update: off` to keep them out.

**Which containers** is saved in [`dozzle.yml`](/guide/setup-wizard#dozzle-yml) as `updateContainers`, so changing it from the UI needs `/data` on a volume. Auto-update runs in server mode, including containers on [remote agents](/guide/agent), and needs actions on. Coming from Watchtower? See [Moving from Watchtower](/guide/moving-from-watchtower).

## Cleaning up old images {#cleaning-up-old-images}

Every update leaves the image it replaced on the host, so Dozzle removes old images after an update, like Watchtower's `--cleanup`. It always runs, for every update: scheduled, from a container's `Update` action, or from the Updates drawer. There is nothing to turn on.

Dozzle keeps the image the container ran until now, so the container can still go back to it, and removes the one before that. An update from 1.4.1 to 1.4.2 removes 1.4.0 and keeps 1.4.1, so each container keeps at most one spare image. Dozzle reads which image to remove from the old container's `dev.dozzle.previous-image` label, so a container's first update removes nothing.

Cleanup only runs after the update has gone through and the old container is gone. A rolled back update removes nothing. Dozzle only removes an untagged image that no container uses: an image that still has a tag, such as one you pulled or built yourself, is kept, and the removal is not forced, so Docker refuses while any other container, running or stopped, still uses it. A refusal never fails the update.

Containers on [remote agents](/guide/agent) are cleaned up the same way, and so is Dozzle's own container: the [self-update](/guide/setup-wizard#self-update) helper removes the image before the previous one once the new Dozzle has stayed up. Swarm services, including Dozzle running as one, are not cleaned up, since each node keeps its own images and Swarm prunes its own task history.

## Rolling back {#rolling-back}

Rolling an update back is a [Dozzle Cloud](/guide/dozzle-cloud) feature. Dozzle Cloud watches each update the schedule makes and, when the new version starts failing, offers to roll it back. Dozzle then swaps the container back to the image it ran before, the one its `dev.dozzle.previous-image` label names, the same way an update swaps it: the current container is kept until the previous image has stayed up, and is put back if it does not. Settings and volumes stay as they are. Nothing is pulled, so if the previous image is no longer on the host the rollback fails and the container is left alone.

The rolled back container is labelled `dev.dozzle.rolled-back-from` with the image it left, and the auto-update schedule leaves it alone until its tag points to a newer image. Once the rollback has stayed up, the image rolled back from is [cleaned up](#cleaning-up-old-images) like any old image, so it is removed only when no tag points to it any more.

Rollback works for standalone containers, including containers on [remote agents](/guide/agent). It is not available for Swarm services, Kubernetes, or Dozzle's own container.

## Updates in the log view

When Dozzle updates a container, on the schedule, from the Dozzle UI or from Dozzle Cloud, the logs of the new container start with a marker that names the image it moved from and to and what started the update. A rollback and an update that was undone are marked too. With Dozzle Cloud linked, the marker also shows what Dozzle Cloud made of the update, with a link to it there. Dozzle keeps its recent updates in memory, so the marker is gone after Dozzle restarts.

---

## File: docs/guide/agent.md

---
title: Agent Mode
---

# Agent Mode



Dozzle can run in agent mode which can expose Docker hosts to other Dozzle instances. All communication is done over a secured connection using TLS. This means that you can deploy Dozzle on a remote host and connect to it from your local machine.

> [!WARNING] An agent is only as private as the network it listens on
> The certificate Dozzle ships with is the same in every copy of the image, so it encrypts the connection but it does not prove who is on the other end. Anyone who can reach port `7007` can connect their own Dozzle to your agent, read every log on that host and run commands inside its containers. The agent does not look at `DOZZLE_ENABLE_SHELL` or `DOZZLE_ENABLE_ACTIONS`, those flags only control what the UI offers.
>
> Keep port `7007` on a private network, and if the agent is reachable from anywhere else, [generate your own certificate](#custom-certificates) so agents only accept your hub.

> [!NOTE] Using Docker Swarm?
> If you are using Docker Swarm Mode, you don't need to use agents. Dozzle will automatically discover itself and create a cluster using swarm mode. See [Swarm Mode](/guide/swarm-mode) for more information.

##  How to Create an Agent

To create a Dozzle agent, you need to run Dozzle with the `agent` subcommand. Here is an example:

::: code-group

```sh
docker run -v /var/run/docker.sock:/var/run/docker.sock -p 7007:7007 amir20/dozzle:latest agent
```

```yaml [docker-compose.yml]
services:
  dozzle-agent:
    image: amir20/dozzle:latest
    command: agent
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    ports:
      - 7007:7007
```

:::

> [!NOTE] Docker Socket Proxy users
> If you are using a remote agent you **CANNOT** add a socket proxy on top of the agent. Dozzle agents **REPLACE** using a proxy, see [Remote Hosts](/guide/remote-hosts) for more info and how to use a socket proxy instead of an agent.

The agent will start and listen on port `7007`. You can connect to the agent using the Dozzle UI by providing the agent's IP address and port. The agent will only show the containers that are available on the host where the agent is running.

> [!TIP]
> You don't need to expose port 7007 if using Docker network. The agent will be available to other containers on the same network. This is the safest way to run an agent, since nothing outside that network can reach it.

##  How to Connect to an Agent

To connect to an agent, you need to provide the agent's IP address and port. Here is an example:

::: code-group

```sh
docker run -p 8080:8080 amir20/dozzle:latest --remote-agent agent:7007
```

```yaml [docker-compose.yml]
services:
  dozzle:
    image: amir20/dozzle:latest
    environment:
      - DOZZLE_REMOTE_AGENT=agent:7007
    ports:
      - 8080:8080 # Dozzle UI port
```

:::

Note that it is not necessary to mount the local Docker socket when connecting to agents, in which case the UI will only show the containers that are available on the agents.

> [!TIP]
> If you want to include the host containers in the UI as well, mount the `docker.sock` socket as shown in the [getting started](/guide/getting-started) example.

> [!TIP]
> You can connect to multiple agents by providing multiple `DOZZLE_REMOTE_AGENT` environment variables. For example, `DOZZLE_REMOTE_AGENT=agent1:7007,agent2:7007`.

> [!TIP]
> In server mode you can also add an agent from the UI, with **Add host** at the bottom of the host list, under **Settings → Hosts**, or in the Hosts step of the [setup wizard](/guide/setup-wizard). Dozzle connects to the agent before saving it, and the host shows up without a restart. Agents added this way are saved in `/data/dozzle.yml`, so `/data` has to be on a volume. Agents set with `DOZZLE_REMOTE_AGENT` stay as they are and can't be removed from the UI.

##  Host Groups

When managing many agents across different environments, you can assign each agent to a named group. Groups appear as collapsible sections in the sidebar, and each group has a "merge all" button to view combined logs from every host in the group.

The connection string format is `endpoint|name|group` — all three parts are optional:

| Format                          | Result                     |
| ------------------------------- | -------------------------- |
| `agent:7007`                    | No name override, no group |
| `agent:7007\|web-1`             | Name override, no group    |
| `agent:7007\|web-1\|Production` | Name override + group      |
| `agent:7007\|\|Production`      | Default hostname + group   |

::: code-group

```sh
docker run -p 8080:8080 amir20/dozzle:latest \
  --remote-agent agent1:7007|web-1|Production \
  --remote-agent agent2:7007|web-2|Production \
  --remote-agent agent3:7007|dev-1|Development
```

```yaml [docker-compose.yml]
services:
  dozzle:
    image: amir20/dozzle:latest
    environment:
      - DOZZLE_REMOTE_AGENT=agent1:7007|web-1|Production,agent2:7007|web-2|Production,agent3:7007|dev-1|Development
    ports:
      - 8080:8080
```

:::

The sidebar will display:

```
▾ Production
    web-1
    web-2
▾ Development
    dev-1
  ungrouped-host   ← agents without a group appear below
```

Clicking the merge icon next to a group name opens a combined log view streaming from all hosts in that group. The merged view is also available directly at `/host-group/<group-name>`.

Agents without a group continue to work exactly as before and appear below grouped sections.

##  Common Issues

### Agent Not Showing Up

If you are seeing `An agent with an existing ID was found. Removing the duplicate host.` then you have two hosts that use the same Server ID.

Dozzle utilizes the Docker API to collect information about hosts. Each agent requires a unique host ID that remains consistent across restarts to ensure proper identification. Currently, agents identify the host using either Docker's system ID or node ID.

If you are operating in a Swarm environment, the node ID will be employed for this purpose. However, if you notice that not all hosts are visible, it may be due to the presence of duplicate hosts configured with the same host ID.

To resolve this issue, you should remove `/var/lib/docker/engine-id` from your system and restart. This action will help eliminate any conflicts caused by duplicate host IDs. For additional information and troubleshooting tips, please refer to the [FAQ](/guide/faq#i-am-seeing-duplicate-hosts-error-in-the-logs-how-do-i-fix-it).

##  Advanced Options

### Setting Up Healthcheck

You can set a healthcheck for the agent, similar to the healthcheck for the main Dozzle instance. When running in agent mode, healthcheck checks agent connection to Docker. If Docker is not reachable, the agent will be marked as unhealthy and will not be shown in the UI.

To set up healthcheck, use the `healthcheck` subcommand. Here is an example:

```yml
services:
  dozzle-agent:
    image: amir20/dozzle:latest
    command: agent
    healthcheck:
      test: ["CMD", "/dozzle", "healthcheck"]
      interval: 5s
      retries: 5
      start_period: 5s
      start_interval: 5s
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    ports:
      - 7007:7007
```

### Changing Agent's Name

Similar to Dozzle instance, you can change the agent's name by providing the `DOZZLE_HOSTNAME` environment variable. Here is an example:

::: code-group

```sh
docker run -v /var/run/docker.sock:/var/run/docker.sock -p 7007:7007 amir20/dozzle:latest agent --hostname my-special-name
```

```yaml [docker-compose.yml]
services:
  dozzle-agent:
    image: amir20/dozzle:latest
    command: agent
    environment:
      - DOZZLE_HOSTNAME=my-special-name
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    ports:
      - 7007:7007
```

:::

This will change the agent's name to `my-special-name` and will be reflected on the UI when connecting to the agent.

### Setting Up Filters

You can set up filters for the agent to limit the containers it can access. These filters are passed directly to Docker, restricting what Dozzle can view.

```yaml
services:
  dozzle-agent:
    image: amir20/dozzle:latest
    command: agent
    environment:
      - DOZZLE_FILTER=label=color
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
```

This will restrict the agent to displaying only containers with the label `color`. Keep in mind that these filters are combined with the UI filters to narrow down the containers. To learn more about the different types of filters, read the [filters documentation](/guide/filters#ui-agents-and-user-filters).

### Custom Certificates

Dozzle ships with a self-signed certificate that both ends present to each other, and each end trusts that one certificate and nothing else. It is compiled into the binary, so it is identical in every Dozzle install and anyone can read it out of the public image.

That means it gives you encryption but no authentication. An agent cannot tell your hub apart from someone else's, so the only thing keeping strangers out of an agent started with the defaults is that they cannot reach port `7007`.

> [!WARNING] When you need your own certificate
> If port `7007` is reachable by anything you do not control, which includes publishing it on a host with a public IP, generate your own pair. Every deployment that does gets a credential nobody else holds.

Run `generate-certs` to write a unique pair:

```sh
docker run --rm -v "$PWD":/out amir20/dozzle:latest generate-certs --cert-out /out/dozzle_cert.pem --key-out /out/dozzle_key.pem
```

Copy both files to the hub and to every agent. Dozzle looks for them at `/dozzle_cert.pem` and `/dozzle_key.pem`, so mounting them there is enough. You can put them elsewhere with the `--cert` and `--key` flags or the `DOZZLE_CERT` and `DOZZLE_KEY` environment variables.

Treat the key like a password. Anyone holding it can connect to your agents, and an agent will reject a hub presenting anything else, so the hub and its agents have to be given the same pair and restarted together.

Here is an example using the default paths:

```yml
services:
  agent:
    image: amir20/dozzle:latest
    command: agent
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    secrets:
      - source: cert
        target: /dozzle_cert.pem
      - source: key
        target: /dozzle_key.pem
    ports:
      - 7007:7007
secrets:
  cert:
    file: ./cert.pem
  key:
    file: ./key.pem
```

Or using custom paths with environment variables:

```yml
services:
  agent:
    image: amir20/dozzle:latest
    command: agent
    environment:
      - DOZZLE_CERT=/certs/my-cert.pem
      - DOZZLE_KEY=/certs/my-key.pem
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./certs:/certs
    ports:
      - 7007:7007
```

Or using command-line flags:

::: code-group

```sh
docker run -v /var/run/docker.sock:/var/run/docker.sock -v ./certs:/certs -p 7007:7007 amir20/dozzle:latest agent --cert /certs/my-cert.pem --key /certs/my-key.pem
```

```yaml [docker-compose.yml]
services:
  agent:
    image: amir20/dozzle:latest
    command: agent --cert /certs/my-cert.pem --key /certs/my-key.pem
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./certs:/certs
    ports:
      - 7007:7007
```

:::

> [!TIP]
> Docker secrets are preferred for providing certificates. They can be created using `docker secret create` command or as the example above using `docker-compose.yml`. The same certificates should be provided to the Dozzle instance connecting to the agent.

This will mount the certificate and key files to the agent. The agent will use these certificates for communication. The same certificates should be provided to the Dozzle instance connecting to the agent.

If you would rather generate them with openssl than with `generate-certs`:

```sh
$ openssl genpkey -algorithm Ed25519 -out key.pem
$ openssl req -new -key key.pem -out request.csr -subj "/C=US/ST=California/L=San Francisco/O=My Company"
$ openssl x509 -req -in request.csr -signkey key.pem -out cert.pem -days 365
```

### Private certificate for agents added from the UI {#private-certificate}

When you add an agent with **Add host**, the dialog has a **Private certificate** toggle. It is on by default for new hosts. With it on, only this Dozzle can connect to the agent, even if port `7007` is reachable by others.

The first time it is used, Dozzle creates its own pair in `/data/agent_cert.pem` and `/data/agent_key.pem`. The pair is never replaced, so every private agent keeps working across restarts and updates. The compose snippet in the dialog passes the pair to the agent through `DOZZLE_CERT_PEM` and `DOZZLE_KEY_PEM`:

```yaml [docker-compose.yml]
services:
  dozzle-agent:
    image: amir20/dozzle:vX.Y.Z # same tag as your Dozzle
    command: agent
    environment:
      DOZZLE_CERT_PEM: |
        -----BEGIN CERTIFICATE-----
        ...
        -----END CERTIFICATE-----
      DOZZLE_KEY_PEM: |
        -----BEGIN PRIVATE KEY-----
        ...
        -----END PRIVATE KEY-----
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    ports:
      - 7007:7007
```

The snippet contains the private key, so treat it like a password. The toggle is per host: agents that already exist, and any agent added with the toggle off, keep using the certificate they have. Dozzle records which agents are private under `privateAgents` in `/data/dozzle.yml`. The toggle is not offered when Dozzle already runs a [custom certificate](#custom-certificates), because every agent needs that pair anyway. The snippet uses the same image as your Dozzle. An older agent ignores `DOZZLE_CERT_PEM` and `DOZZLE_KEY_PEM`, presents the built-in certificate, and is refused.

##  Comparing Agents with Remote Connection

Agents are similar to remote connections, but they have some advantages. Generally, agents are preferred over remote connections due to performance and security reasons. Here is a comparison:

| Feature     | Agent                          | Remote Connection               |
| ----------- | ------------------------------ | ------------------------------- |
| Performance | Better with distributed load   | Worse on the UI                 |
| Security    | Encrypted, bring your own cert | Insecure or Docker TLS          |
| Ease of use | Easy out of the box            | Requires exposing Docker socket |
| Permissions | Full access to Docker          | Can be controlled with a proxy  |
| Reconnect   | Automatically reconnects       | Requires UI restart             |
| Healthcheck | Built-in healthcheck           | No healthcheck                  |
| Filters     | Supports filters               | No support for filters          |

If you do plan to use remote connections, make sure to secure the connection using Docker TLS or a reverse proxy.

---

## File: docs/guide/analytics.md

---
title: Anonymous Analytics
---

# Data Collection of Analytics

Dozzle collects anonymous usage data via a lightweight beacon to help prioritize features and fixes. It is an open-source project with no funding, so this data is the primary signal for where to invest effort.

## What is Collected

At a high level, the beacon includes things like the Dozzle version, deployment mode (server, swarm, k8s, agent), which auth provider is enabled, a few feature flags, the Docker Engine version, and small counts (number of hosts, containers, filters). A random per-install ID is included for deduplication.

No log contents, container names, image names, IP addresses, or user identifiers are ever transmitted. The exact set of fields evolves over time — the authoritative source is [`types/beacon.go`](https://github.com/amir20/dozzle/blob/master/types/beacon.go), and the sender is [`internal/analytics/http_beacon.go`](https://github.com/amir20/dozzle/blob/master/internal/analytics/http_beacon.go).

## Where is Data Stored

Events are posted to `https://b.dozzle.dev/event`, a small Go service that writes events to a flat file on DigitalOcean for later processing.

## Opting Out

Pass `--no-analytics` or set `DOZZLE_NO_ANALYTICS=true`. No beacon requests will be made.

```yaml
services:
  dozzle:
    image: amir20/dozzle:latest
    environment:
      DOZZLE_NO_ANALYTICS: "true"
```

---

## File: docs/guide/authentication.md

---
title: Authentication
---

# Authentication

Dozzle supports two configurations for authentication. In the first configuration, you bring your own authentication method by protecting Dozzle through a proxy. Dozzle can read appropriate headers out of the box.

If you do not have an authentication solution, then Dozzle has a simple file-based user management solution. Authentication providers are set up using the `--auth-provider` flag. In both configurations, Dozzle will try to save user settings to disk. This data is written to `/data`.

##  Security Considerations

Dozzle has access to `docker.sock`, which — unless restricted — is equivalent to **root on the host**. Before exposing Dozzle beyond your private network, review the following:

- **Always put Dozzle behind authentication** if it is reachable from the public internet. Use `--auth-provider=simple` or a forward-proxy like Authelia / Authentik / Cloudflare Access.
- **Keep [actions](/guide/actions) and [shell access](/guide/shell) disabled** unless you need them. They allow starting, stopping, recreating, and executing arbitrary commands inside containers.
- **Restrict users with [roles](/guide/authentication/simple#setting-specific-roles-for-users) and [filters](/guide/authentication/simple#setting-specific-filters-for-users)** in multi-user mode. Without explicit roles, a user can see every container the Dozzle instance can.
- **Never expose Dozzle's port directly in forward-proxy mode.** Dozzle trusts `Remote-User` on every request, and when no roles header is present the user is granted all roles. Anyone who can reach the container without passing through the proxy authenticates as whoever they like by setting one header. Publish only the proxy, and keep Dozzle on an internal network with `expose` rather than `ports`.
- **Run TLS at the reverse proxy**. See [Reverse Proxy & Base Path](/guide/changing-base) for Nginx / Traefik / Caddy examples.
- **Restrict `docker.sock` access with a proxy** if you don't need actions. Note that a read-only mount (`/var/run/docker.sock:/var/run/docker.sock:ro`) does _not_ limit the API: the `:ro` flag only marks the socket file read-only on disk, while API calls still pass through the socket normally, so create/delete/update remain possible. To actually restrict operations, put a socket proxy like [`tecnativa/docker-socket-proxy`](https://github.com/Tecnativa/docker-socket-proxy) in front of the daemon.

##  Choosing a method

| Method                                               | Who owns the users     | Use it when                                                                                                                             |
| ---------------------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| [Simple](/guide/authentication/simple)               | Dozzle, in `users.yml` | You have no authentication solution and want Dozzle to handle logins.                                                                   |
| [GitHub & OIDC](/guide/authentication/oauth)         | Dozzle, in `users.yml` | You want the same `users.yml` users to sign in with GitHub, Google, Keycloak, Pocket ID, Zitadel or Authentik instead of a password.    |
| [OpenID Connect](/guide/authentication/oidc)         | Your identity provider | You want Keycloak, Authentik, Zitadel or Pocket ID to own the user list, with roles and filters read from the token and no `users.yml`. |
| [Forward Proxy](/guide/authentication/forward-proxy) | Your proxy             | You already run Authelia, Authentik, Cloudflare Access or similar, and want it to own authentication entirely.                          |

Simple and OAuth are the same provider: `users.yml` is the user list either way, and OAuth only adds a second way to prove you are one of the users in it. OpenID Connect and forward proxy are the separate ones, where something outside Dozzle owns the users. Pick `oidc` when your identity provider can put roles in the token, and forward proxy when you need org-wide or domain-wide access rules enforced in front of Dozzle, which `users.yml` deliberately does not do.

##  Generating users.yml

Dozzle has a built-in `generate` command to generate `users.yml`. Here is an example:

```sh
docker run -it --rm amir20/dozzle generate admin --password password --email test@email.net --name "John Doe" --user-filter name=foo --user-roles shell > users.yml
```

In this example, `admin` is the username. Email and name are optional but recommended to display accurate avatars. `docker run -it --rm amir20/dozzle generate --help` displays all options. The `--user-filter` flag is a comma-separated list of filters. The `--user-roles` flag is a comma-separated list of roles.

If you omit `--password`, Dozzle prompts for it on stdin so the password never lands in your shell history. This requires an interactive terminal, so keep the `-it` flags:

```sh
docker run -it --rm amir20/dozzle generate admin --email test@email.net --name "John Doe" > users.yml
```

The prompt is written to stderr, so redirecting stdout to `users.yml` still works. You can also pipe the password in, for example `echo "$PASSWORD" | docker run -i --rm amir20/dozzle generate admin > users.yml`.

---

## File: docs/guide/changing-base.md

---
title: Reverse Proxy & Base Path
---

# Reverse Proxy & Base Path

Dozzle is commonly placed behind a reverse proxy for TLS termination, authentication, or to share a hostname with other services. This page covers both mounting Dozzle at a sub-path and the proxy settings needed to make streaming work correctly.

## Changing the Base Path

Dozzle by default mounts to `/`. This can be changed with the `--base` flag or the `DOZZLE_BASE` environment variable. For example, to mount at `/foobar`:

::: code-group

```sh
docker run --volume=/var/run/docker.sock:/var/run/docker.sock -p 8080:8080 amir20/dozzle --base /foobar
```

```yaml [docker-compose.yml]
services:
  dozzle:
    image: amir20/dozzle:latest
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - 8080:8080
    environment:
      DOZZLE_BASE: /foobar
```

:::

Dozzle will be available at `http://localhost:8080/foobar/`. This option rewrites all assets to `/foobar/{file.path}` and automatically redirects `/foobar` to `/foobar/`.

## Proxy Requirements

Dozzle streams logs over **Server-Sent Events (SSE)** and uses **WebSocket** for container shells. Reverse proxies must:

1. **Disable response buffering** — SSE delivers events as they happen. Any buffering causes logs to arrive in bursts or never arrive at all. Dozzle sends `X-Accel-Buffering: no`, but some proxies ignore it.
2. **Forward WebSocket upgrade headers** — required for the shell and attach features.
3. **Avoid compressing `text/event-stream`** — compression middleware often breaks SSE.

## Nginx

```nginx
location ^~ /foobar/ {
    proxy_pass http://dozzle:8080;

    chunked_transfer_encoding off;
    proxy_buffering off;
    proxy_cache off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}
```

Drop the `^~ /foobar/` prefix if Dozzle is mounted at the root. See also the FAQ entry on [disabling buffering](/guide/faq#disabling-buffering-in-nginx).

## Traefik

Traefik handles WebSocket upgrades automatically, but the default `compress` middleware will break SSE. Exclude `text/event-stream`:

```yaml
http:
  middlewares:
    middlewares-compress:
      compress:
        excludedContentTypes:
          - text/event-stream
```

Then a typical labels block on the Dozzle service:

```yaml
services:
  dozzle:
    image: amir20/dozzle:latest
    labels:
      - traefik.enable=true
      - traefik.http.routers.dozzle.rule=Host(`dozzle.example.com`)
      - traefik.http.routers.dozzle.entrypoints=websecure
      - traefik.http.routers.dozzle.tls.certresolver=letsencrypt
      - traefik.http.services.dozzle.loadbalancer.server.port=8080
```

## Caddy

```caddyfile
dozzle.example.com {
    reverse_proxy dozzle:8080 {
        flush_interval -1
    }
}
```

`flush_interval -1` disables response buffering for streaming endpoints.

## Common Pitfalls

- **Blank page or assets 404 when using `--base`** — the proxy is stripping the path prefix before forwarding. Configure it to pass the full path through to Dozzle.
- **Logs stop after a few seconds** — connection timeouts on the proxy are too short. Increase read/send timeouts to at least a few minutes (e.g. Nginx `proxy_read_timeout 3600s`).
- **Shell disconnects immediately** — WebSocket upgrade headers are not being forwarded. Verify `Upgrade` and `Connection` headers.

---

## File: docs/guide/container-groups.md

---
title: Container Groups
---

# Container Groups

Dozzle performs automatic grouping of containers based on their stack name or service name. You can also create custom groups using labels.

## Default Groups

By default, containers are grouped by their stack name in host mode. If `com.docker.swarm.service.name` label is present, Dozzle will automatically enable a "swarm mode" where all containers with the same service name will be joined together.

## Custom Groups

Additionally, you can create custom groups by adding a label to your container. The label is `dev.dozzle.group` and the value is the name of the group. All containers with the same group name will be joined together in the UI. For example, if you have a group named `myapp`, all containers with the label `dev.dozzle.group=myapp` will be joined together.

Here is an example using Docker Compose or Docker CLI:

::: code-group

```sh
docker run --label dev.dozzle.group=myapp hello-world
```

```yaml [docker-compose.yml]
services:
  dozzle:
    image: hello-world
    labels:
      - dev.dozzle.group=myapp
```

:::

## Kubernetes

In Kubernetes mode, the sidebar groups pods by namespace and owner. A `dev.dozzle.group` label or annotation on the pod template adds every container in the pod to a custom group as well. A label and an annotation behave the same, so use an annotation if the group name has spaces. When both are set, the annotation wins.

```yaml [deployment.yaml]
spec:
  template:
    metadata:
      labels:
        dev.dozzle.group: myapp
```

---

## File: docs/guide/container-names.md

---
title: Container Names
---

# Container Names

By default, Dozzle retrieves container names directly from Docker. This is usually sufficient, as these names can be customized using the `--name` flag in `docker run` commands or through the `container_name` field in Docker Compose services.

## Custom Names

In cases where modifying the container name itself isn't possible, you can override it by adding a `dev.dozzle.name` label to your container.

Here is an example using Docker Compose or Docker CLI:

::: code-group

```sh
docker run --label dev.dozzle.name=hello hello-world
```

```yaml [docker-compose.yml]
services:
  dozzle:
    image: hello-world
    labels:
      - dev.dozzle.name=hello
```

:::

## Kubernetes

In Kubernetes mode, containers are named `/<container>` by default. Set `dev.dozzle.name` on the pod template to override it. An annotation is the better place for it, because label values cannot contain spaces and are limited to 63 characters. If both are set, the annotation wins. The same goes for every other `dev.dozzle.*` setting, such as `dev.dozzle.url` and `dev.dozzle.icon`.

```yaml [deployment.yaml]
spec:
  template:
    metadata:
      annotations:
        dev.dozzle.name: Public API
```

The name applies to the whole pod. Init containers always keep their own name as a suffix, for example `Public API/migrate`, and so does every container in a pod with more than one app container, for example `Public API/proxy`.

Replicas of the same deployment all get the same name, and Dozzle treats containers with the same name as one. Pinning one replica in the sidebar pins all of them, and the container title menu lists the others as earlier runs of the same container.

## Coolify Integration

If you're using [Coolify](https://coolify.io/), Dozzle automatically recognizes Coolify's labels as fallbacks:

- `coolify.serviceName` → Used as container name if `dev.dozzle.name` is not set
- `coolify.projectName` → Used for grouping if `dev.dozzle.group` is not set

No additional configuration is needed for Coolify deployments.

---

## File: docs/guide/debugging.md

---
title: Debugging
---

# Debugging with Logs

By default Dozzle logs at `info` level, which is intentionally quiet. When something isn't working, turn up the verbosity using the `--level` flag or the `DOZZLE_LEVEL` environment variable.

| Level   | When to use                                                                  |
| ------- | ---------------------------------------------------------------------------- |
| `info`  | Default. Startup details, errors, and warnings.                              |
| `debug` | Request-level diagnostics, auth decisions, agent connections, config dump.   |
| `trace` | Everything. Individual log events, beacon payloads, gRPC frames. Very noisy. |

Dozzle writes all logs to `stdout`, so `docker logs dozzle` is the right place to read them.

```yaml
services:
  dozzle:
    image: amir20/dozzle:latest
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - 8080:8080
    environment:
      DOZZLE_LEVEL: debug
```

## Reporting a Bug

If you think you've hit a bug, please open an issue at [github.com/amir20/dozzle/issues](https://github.com/amir20/dozzle/issues). Include:

- Dozzle version (visible in the UI footer or `dozzle --version`)
- Deployment mode: server, swarm, k8s, or agent
- Docker or Kubernetes version
- Relevant `debug`- or `trace`-level log output
- Steps to reproduce, ideally with a minimal `docker-compose.yml`

The more context in the initial report, the faster it can be triaged.

---

## File: docs/guide/faq.md

---
title: FAQ
---

# Frequently Asked Questions

## Dozzle fails to start with `client version 1.x is too new`. What does this mean?

Dozzle requires Docker Engine 19.03 or newer (API version 1.40+). Older daemons — for example Docker 18.06 (API 1.38) — are not supported by the underlying Docker SDK and will fail at startup with an error like `failed to create docker client: ... client version 1.54 is too new. Maximum supported API version is 1.38`.

Upgrade Docker Engine to a supported version. As a temporary workaround, pin Dozzle to `v10.5.2` or earlier, which used a Docker SDK that still negotiated down to older API versions.

## How do I upgrade Dozzle?

Dozzle follows standard Docker image practices. To upgrade, pull the new image and recreate the container:

```sh
docker pull amir20/dozzle:latest
docker compose up -d dozzle
```

User settings, notification rules, and other state are stored in `/data` (see below), so keep that volume mounted across upgrades. For production use, pin a specific tag (e.g. `amir20/dozzle:v10.9.2`) rather than `latest` so upgrades are deliberate. Release notes are published on the [GitHub releases page](https://github.com/amir20/dozzle/releases). Rolling back is as simple as redeploying an older tag.

## My platform wraps the container entrypoint and Dozzle fails with `no such file or directory`

The default image is built `FROM scratch`, so it contains the Dozzle binary and nothing else. No shell, no interpreter.

Some platforms add optional features by bind-mounting a `#!/bin/sh` wrapper over the container entrypoint and re-exec'ing the original one. Unraid's per-container Tailscale toggle works this way, as do some sidecar and init injectors. Without `/bin/sh` in the image the wrapper cannot be executed and the container exits with an error naming the wrapper rather than the missing shell:

```
exec /opt/unraid/tailscale: no such file or directory
```

For these cases use the `alpine` variant, which is the same binary on an Alpine base:

```sh
docker run \
  --volume=/var/run/docker.sock:/var/run/docker.sock \
  -p 8080:8080 \
  amir20/dozzle:alpine
```

Versioned tags follow the same pattern (`amir20/dozzle:v10.9.2-alpine`). The scratch-based `latest` stays the recommended image for everything else, since it has a much smaller footprint and no distro packages to patch.

## What is stored in `/data` and how do I back it up?

The `/data` directory is where Dozzle persists anything that needs to survive a container restart:

- `users.yml` / `users.yaml` — simple-auth user file (if you created one)
- Notification rules, destinations, and delivery state
- Per-user UI settings (only in multi-user mode; single-user settings live in browser localStorage)
- A small set of internal files like dismissed-announcement state

The directory is small (typically well under 10 MB) and can be backed up with a simple `tar` or `rsync` of the mounted volume. When upgrading or migrating to a new host, moving the `/data` volume carries all settings with it.

## I installed Dozzle, but logs are slow or they never load. What do I do?

Dozzle uses Server Sent Events (SSE) which connects to a server using a HTTP stream without closing the connection. If any proxy tries to buffer this connection, then Dozzle never receives the data and hangs forever waiting for the reverse proxy to flush the buffer. Since version `1.23.0`, Dozzle sends the `X-Accel-Buffering: no` header which should stop reverse proxies buffering. However, some proxies may ignore this header. In those cases, you need to explicitly disable any buffering.

### Disabling buffering in nginx

Below is an example with nginx and using `proxy_pass` to disable buffering:

```
server {
    ...

    location / {
        proxy_pass                  http://<dozzle.container.ip.address>:8080;
    }

    location /api {
        proxy_pass                  http://<dozzle.container.ip.address>:8080;

        proxy_buffering             off;
        proxy_cache                 off;
    }
}
```

### Disabling compression in traefik

Traefik reverse proxy can be configured via middlewares to support compression. If implemented, the usual configuration looks like this:

```
http:
  middlewares:
    middlewares-compress:
      compress: {}
```

With this setup, you may find that certain containers do not show logs in dozzle anymore if you open dozzle via traefik (e.g., dozzle.mydomain.com).
You will also note that the same dozzle instance does show the logs when accessed directly (e.g., localhost:8080).

Containers where this has been observed (non-exhaustive list) are: dozzle, homepage, glances, filebrowser.

To re-enable the logs to flow, exclude `text/event-stream` from the compression middleware:

```
http:
  middlewares:
    middlewares-compress:
      compress:
        excludedContentTypes:
          - text/event-stream
```

## We have tools that use Dozzle when a new container is created. How can I get a direct link to a container by name?

Dozzle has a special [route](https://github.com/amir20/dozzle/blob/master/assets/pages/show.vue) that can be used to search containers by name and then forward to that container. For example, if you have a container with name `"foo.bar"` and id `abc123`, you can send your users to `/show?name=foo.bar` which will be forwarded to `/container/abc123`.

To open several containers in one merged view, separate the names with commas: `/show?name=foo,bar` forwards to `/merged/`. When more than one container has the same name, the most recently started one wins. Add `host=<host id>` to only match containers on that host, e.g. `/show?name=foo,bar&host=abc`. Names that match nothing are skipped.

## I installed Dozzle but memory consumption doesn't show up!

_This is an issue specific to ARM devices._

Dozzle uses the Docker API to gather information about the containers' memory usage. If the memory usage is not showing up, then it is likely that the Docker API is not returning the memory usage.

You can verify this by running docker info, and you should see the following:

```
WARNING: No memory limit support
WARNING: No swap limit support
```

In this case, you'll need to add the following line to your `/boot/cmdline.txt` file and reboot your device:

```
cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1
```

## I am seeing duplicate hosts error in the logs. How do I fix it?

If you are seeing the following error in the logs, then you may have duplicate hosts configured with the same host ID:

```
time="2024-07-10T13:35:53Z" level=warning msg="duplicate host ID: *********, Endpoint: 1.1.1.1:7007 found, skipping"
```

Dozzle uses the Docker API to gather information about the hosts. Each host must have a unique ID. This ID is used to identify the host in the UI. In swarm mode, Dozzle uses the node ID from `docker system info` to identify the host. If you are not using swarm mode, then Dozzle will use the system ID from `docker system info` as the host ID.

Sometimes, VMs may be restored from backups with the same host ID. This can cause Dozzle to think that the host is already present and skip adding it to the list of hosts. To fix this, you need to remove `/var/lib/docker/engine-id` file. This file contains the host ID and is created when the Docker daemon starts.

## I am seeing host not found error in the logs. How do I fix it?

This is mainly a Podman error. Podman is daemonless and tracks no engine identity, so its Docker-compatible `/info` endpoint returns a brand new random UUID on every call. Dozzle reads that ID once when it connects and uses it to identify the host, so every restart used to produce a different host, and the main server kept routing to an ID that no longer answered.

Dozzle now derives a stable ID on Podman from the hostname and the container storage path, so this should fix itself once both the server and the agents are updated. If you are still seeing it, restart the main Dozzle server so it picks up the new IDs.

> [!WARNING]
> Earlier versions of this page told you to create `/var/lib/docker/engine-id`. That never had any effect on Podman, which reads no file for this. You can safely delete it.

If you are using Docker rather than Podman, check that `engine-id` exists in `/var/lib/docker` with a UUID inside and is readable by the Docker daemon.

Two Podman hosts that share both a hostname and a storage path will still collide, since that is all Podman gives Dozzle to work with. Set `DOZZLE_HOST_ID` on one of them to break the tie:

```sh
podman run -e DOZZLE_HOST_ID=web-01 ...
```

See the [Podman guide](/guide/podman) for the full setup.

## Why am I only seeing running containers? How do I see stopped containers?

By default, Dozzle only shows running containers. To see stopped containers, you need to enable the `Show Stopped Containers` option in the settings. This option is disabled by default to reduce the number of containers shown in the UI.

## Is there a way to sync my settings across multiple instances of Dozzle?

In single-user mode, Dozzle stores the settings in the browser's local storage. This means that the settings are only available on the browser where they were set. For Dozzle to enable syncing settings across multiple instances, it needs to know who the user is. In multi-user mode, Dozzle uses the user's username to store the settings on disk and sync them across multiple instances. This information is stored in `/data` directory. If you want to sync settings across multiple instances, you need to [enable](/guide/authentication) multi-user mode and provide a username.

## Why doesn't Dozzle support notifications for Slack, Discord, Telegram, email, etc. directly?

By design, Dozzle is unopinionated about where your alerts go. Instead of bundling integrations for specific notification platforms, Dozzle provides **webhooks** with customizable payload templates. This means you can send alerts to _any_ service that accepts HTTP requests — Slack, Discord, Telegram, ntfy, PagerDuty, Opsgenie, or your own internal tools — without waiting for Dozzle to add explicit support.

There are a few reasons for this approach:

- **Universality.** Webhooks work with virtually every notification platform. Adding provider-specific integrations would cover only a fraction of what users need, while webhooks cover all of them.
- **Maintenance.** Each provider integration brings its own API quirks, authentication flows, rate limits, and breaking changes. Supporting them means Dozzle maintainers become responsible for debugging issues with third-party services — which is outside the scope of a log viewer.
- **Simplicity.** Dozzle is a lightweight, focused tool for viewing Docker logs. Keeping the notification layer generic keeps the codebase small and the project sustainable.

If you need a more opinionated experience with richer provider integrations (e.g., web push notifications, ntfy action buttons), [Dozzle Cloud](/guide/dozzle-cloud) is designed for that.

For setting up webhooks with your preferred service, see the [Alerts & Webhooks](/guide/alerts-and-webhooks) guide — it includes built-in payload templates for Slack, Discord, and ntfy that you can use as-is or customize.

## Why does Dozzle keep dockerd and containerd at slightly elevated CPU when no browser is connected?

Dozzle keeps container stats streaming for up to 6 hours (2 hours on Kubernetes) after the last browser disconnects, then shuts the stats collector down on its own. This is intentional. Stats are streamed continuously so that when you reopen the UI you see historical CPU and memory history instead of a blank chart. If streaming stopped the moment you closed the tab, there would be no history to show.

The cost is a small, steady amount of CPU in dockerd and containerd, since Docker's stats API is polling based. Restarting the Dozzle container resets the timer immediately, which is why a restart drops the host back to idle. This is not configurable by design. A small timeout would break other functionality that assumes stats are still streaming, so lowering it defeats the purpose of the stats history.

## My Dozzle instances are timing out in Swarm Mode or I'm not seeing all my Swarm nodes when behind a load balancer. How do I fix it?

In Swarm Mode, Dozzle instances may require their own overlay network. If you see inconsistent behavior when connecting to different Dozzle nodes, consider adding a separate overlay network which only contains the Dozzle instances, as shown below:

```
services:
  logs:
    ...
    networks: [ traefik, dozzle ]
    ...

networks:
  dozzle:
    driver: overlay
  traefik:
    external: true
```

The external network `traefik` is the overlay network which is used for the load balancer service discovery, and we've created a new `dozzle` overlay network for the Dozzle nodes to talk to one another.