Когда контейнеров на сервере становится больше десятка, привычка лазить по терминалу с командой docker logs быстро превращается в утомительный ритуал. Постоянное переключение между окнами, поиск нужного идентификатора, фильтрация строк через grep, попытки уловить ошибку в потоке вывода - всё это съедает время и нервы. Стоит контейнерам начать капризничать одновременно, как становится понятно: нужен инструмент, который покажет картину целиком, а не по кусочкам.

Здесь и появляется Dozzle. Это лёгкий веб-интерфейс для просмотра логов Docker-контейнеров в реальном времени. Никаких баз данных, никакого сбора метрик, никаких сложных настроек. Запустил контейнер, открыл браузер, увидел все логи всех контейнеров на хосте. Поиск с поддержкой регулярных выражений, разделение экрана для одновременного наблюдения за несколькими контейнерами, выгрузка логов на локальную машину - всё это работает из коробки. Ниже разобран процесс развёртывания Dozzle на Ubuntu 22.04 от установки движка Docker до проверки веб-интерфейса.

Предварительные требования для разворачивания Dozzle и подготовка чистого сервера

Перед началом работы понадобится сервер с установленной Ubuntu 22.04 LTS. Подойдёт как физическая машина, так и виртуальный сервер у любого облачного провайдера. Минимальные требования к ресурсам у Dozzle почти смешные - сам контейнер весит около 10 мегабайт и потребляет крошечный объём оперативной памяти. Так что разворачивать его можно даже на самой младшей виртуалке.

Для выполнения команд потребуется доступ к серверу с правами суперпользователя. На свежем сервере root-пароль обычно задаётся при создании, либо доступ оформляется через sudo для обычного пользователя. Все команды ниже выполняются от имени root, так что если работа идёт под обычным пользователем, имеет смысл либо предварить каждую команду через sudo, либо переключиться в root-сессию через sudo -i.

Желательно перед стартом обновить список пакетов и сами пакеты до актуальных версий. Это снимает массу потенциальных проблем со старыми зависимостями. Также стоит убедиться, что сервер имеет доступ к внешней сети, поскольку и Docker, и образ Dozzle тянутся из удалённых репозиториев.

Установка движка Docker Engine из официального репозитория для последующего запуска контейнеров

Dozzle живёт внутри Docker-контейнера и общается с другими контейнерами через сокет демона Docker. Без установленного движка дальше двигаться бессмысленно. В репозиториях Ubuntu есть пакет docker.io, но это упрощённая версия, отстающая по релизам. Для production-сценариев правильнее ставить Docker Engine из официального репозитория компании - там всегда свежие сборки и быстрая реакция на уязвимости.

Сначала ставятся вспомогательные пакеты, без которых не получится корректно подключить внешний репозиторий через HTTPS. Сюда входят корневые сертификаты для проверки подлинности соединения, утилита curl для скачивания GPG-ключа, набор gnupg для работы с этими ключами и lsb-release, который выдаёт информацию о текущем релизе Ubuntu.

apt install ca-certificates curl gnupg lsb-release -y

Флаг -y подтверждает все запросы автоматически, что удобно для скриптов и быстрых развёртываний. Если хочется контролировать процесс вручную, флаг можно убрать.

После установки зависимостей нужно добавить GPG-ключ Docker. Этот ключ проверяет подлинность пакетов из репозитория компании. Без него apt будет ругаться на неподписанные пакеты и откажется их ставить. Заодно прописывается сам адрес репозитория в список источников.

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor > /etc/apt/trusted.gpg.d/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list

В первой команде curl скачивает текстовый GPG-ключ, утилита gpg --dearmor конвертирует его в бинарный формат, и результат сохраняется в каталог доверенных ключей APT. Флаги curl стоит разобрать отдельно. Параметр -f заставляет curl возвращать код ошибки при HTTP-неудачах вместо тихого сохранения страницы с ошибкой. Флаг -s включает тихий режим без прогресс-бара. Опция -S всё-таки показывает сообщения об ошибках, чтобы не остаться в полной темноте. И -L разрешает следовать редиректам, что критично, поскольку сервера часто перенаправляют запросы.

Вторая команда формирует строку с адресом репозитория. Здесь происходит небольшая магия с подстановкой переменных. Вызов dpkg --print-architecture возвращает архитектуру системы - обычно amd64 для типичных серверов, либо arm64 для машин на чипах ARM. Команда lsb_release -cs выдаёт кодовое имя релиза Ubuntu, в случае 22.04 это jammy. Подстановки делают команду универсальной для разных систем.

Дальше нужно обновить локальный кэш пакетов, чтобы apt узнал о существовании новых источников, и установить сам Docker.

apt update -y
apt install docker-ce docker-ce-cli containerd.io -y

Здесь устанавливаются три пакета. Первый, docker-ce, это собственно движок community edition - демон, который рулит всеми контейнерами. Второй пакет, docker-ce-cli, добавляет утилиту командной строки docker, через которую происходит общение с демоном. Третий компонент, containerd.io, это низкоуровневый рантайм, на котором фактически крутятся контейнеры. Docker давно делегирует часть работы containerd, разделение обязанностей повышает гибкость и безопасность.

После завершения установки демон Docker запускается автоматически и регистрируется как системный сервис systemd. Проверить, что всё прошло гладко, можно через запрос версии.

docker --version

Ожидаемый вывод выглядит примерно так.

Docker version 20.10.18, build b40c2f6

Конкретная версия может отличаться в зависимости от того, когда выполняется установка. Главное, чтобы команда не ругалась на отсутствие бинарника и возвращала осмысленный номер релиза.

Запуск контейнера Dozzle и проброс сокета Docker для доступа к логам других контейнеров

Теперь, когда движок Docker работает, можно поднимать сам Dozzle. Образ публикуется в Docker Hub под именем amir20/dozzle. Запуск выполняется одной командой, но в ней зашито несколько важных нюансов, которые стоит разобрать подробно.

docker run --name dozzle -d --volume=/var/run/docker.sock:/var/run/docker.sock -p 8888:8080 amir20/dozzle:latest

Параметр --name dozzle задаёт человекочитаемое имя контейнера. Без этого флага Docker сгенерировал бы случайное имя вроде frosty_curie, что неудобно при последующих манипуляциях. Имя позволяет обращаться к контейнеру через docker stop dozzle вместо запоминания идентификатора.

Флаг -d переводит контейнер в режим демона - он запускается в фоне, не блокируя терминал. Если бы флага не было, текущая сессия оказалась бы привязанной к выводу контейнера и закрытие терминала остановило бы его.

Самая интересная часть это монтирование сокета. Параметр --volume=/var/run/docker.sock:/var/run/docker.sock пробрасывает unix-сокет демона Docker с хоста внутрь контейнера. Через этот сокет Dozzle общается с движком, запрашивает список запущенных контейнеров и читает их логи. По сути контейнер получает почти полный контроль над демоном на хосте, что является серьёзной привилегией. На production-серверах это часто закрывают через дополнительные прослойки вроде socket-proxy, но для домашних серверов и тестовых стендов прямой проброс работает прекрасно.

Опция -p 8888:8080 отвечает за публикацию портов. Слева указан порт хоста, справа порт внутри контейнера. Внутри Dozzle слушает 8080, наружу отдаётся через 8888. Если 8888 на сервере занят другим сервисом, можно подставить любой свободный, например 9000 или 8090. Главное, чтобы порт не был занят и был открыт в файрволе.

Последний аргумент это имя образа с тегом - amir20/dozzle:latest. Тег latest всегда указывает на самую свежую стабильную сборку. Для production иногда предпочитают фиксировать конкретную версию, чтобы случайное обновление образа не сломало работающую систему.

При первом запуске Docker не найдёт образ локально и начнёт его тянуть из Docker Hub. Вывод будет напоминать следующее.

Unable to find image 'amir20/dozzle:latest' locally
latest: Pulling from amir20/dozzle
d1cdc3d3f75b: Pull complete 
9208c7e48f51: Pull complete 
Digest: sha256:6c60292275a512749e4429ee82acfa473ae64e9692ddaa440110bf8cdcba1b9f
Status: Downloaded newer image for amir20/dozzle:latest
c23860a90ab760ca3102be73f50404ab61edf1e868078991b4533b5f09d1cd98

Каждая строка с Pull complete означает успешно загруженный слой образа. Контейнерные образы устроены как набор слоёв, что экономит место при наличии общих базовых слоёв с другими образами. Длинная строка с Digest - это криптографическая подпись образа, по которой можно убедиться в его целостности. Последняя длинная строка снизу это идентификатор только что созданного контейнера.

Проверка скачанного образа Dozzle и убедительность его веса всего в 10 мегабайт

После запуска полезно убедиться, что образ действительно появился в локальном хранилище Docker. Команда docker images выводит таблицу всех образов, сохранённых на хосте.

docker images

Результат должен показать строку с Dozzle.

REPOSITORY            TAG       IMAGE ID       CREATED        SIZE
amir20/dozzle         latest    8ecfe772f6ef   3 days ago     10MB

Обратите внимание на размер - всего 10 мегабайт. Для сравнения, типичный образ с Ubuntu или Debian внутри тянет на 70-100 мегабайт, а полноценные образы с PostgreSQL или Node.js разрастаются до сотен мегабайт. Такой компактный размер Dozzle достигается за счёт сборки на базе Alpine Linux или даже scratch-образа с минимальным набором библиотек. Программа написана на Go и компилируется в статический бинарник без зависимостей, что позволяет выкинуть из образа практически всё лишнее.

Лёгкость образа означает быстрый старт, минимальный расход памяти и крошечный расход трафика при первой загрузке. На слабом сервере с ограниченным каналом эти мелочи складываются в ощутимую разницу.

Проверка статуса запущенного контейнера Dozzle и понимание вывода команды docker ps

Образ скачан, контейнер создан, но работает ли он сейчас? Команда docker ps выводит список запущенных контейнеров.

docker ps

Если всё прошло без сбоев, в выводе будет видна строка с Dozzle.

CONTAINER ID   IMAGE                  COMMAND                  CREATED          STATUS                    PORTS                                       NAMES
c23860a90ab7   amir20/dozzle:latest   "/dozzle"                47 seconds ago   Up 46 seconds             0.0.0.0:8888->8080/tcp, :::8888->8080/tcp   dozzle

В колонке STATUS видно Up 46 seconds - значит контейнер бодро работает почти минуту. Колонка COMMAND показывает /dozzle, это бинарник, который запускается внутри контейнера в качестве основного процесса. Когда этот процесс завершается, контейнер останавливается.

Особый интерес представляет колонка PORTS. Запись 0.0.0.0:8888->8080/tcp означает, что любой входящий трафик на порт 8888 на любой сетевой интерфейс хоста пробрасывается на порт 8080 внутри контейнера. Дублирование с [::]:8888 относится к IPv6, Docker по умолчанию слушает оба стека протоколов.

Если в выводе docker ps контейнера Dozzle нет, значит он по какой-то причине остановился сразу после запуска. В этом случае имеет смысл выполнить docker ps -a с флагом для показа всех контейнеров включая остановленные, и затем docker logs dozzle для просмотра последних строк его вывода. Чаще всего проблема либо в занятом порту, либо в правах доступа к сокету Docker.

Проверка прослушивания порта 8888 на уровне сетевого стека через утилиту ss

Можно подойти к проверке с другой стороны и посмотреть, какие порты вообще слушаются на сервере. Утилита ss показывает активные сокеты на машине.

ss -altnp

Флаги расшифровываются так. Буква -a выводит все сокеты, и слушающие, и установленные соединения. Флаг -l ограничивает вывод только теми, что находятся в режиме прослушивания. Опция -t показывает только TCP-сокеты, отсекая UDP. Параметр -n выводит порты в числовом виде без попыток разрешить их в имена сервисов. И наконец -p добавляет информацию о процессе, который держит сокет открытым.

LISTEN      0           4096                   0.0.0.0:8888                 0.0.0.0:*          users:(("docker-proxy",pid=51708,fd=4))        
LISTEN      0           4096                      [::]:8888                    [::]:*          users:(("docker-proxy",pid=51714,fd=4))        

В колонке с именем процесса виден docker-proxy - это специальный вспомогательный процесс, который Docker запускает для проброса портов между хостовой сетью и внутренней сетью контейнеров. Сам Dozzle про docker-proxy ничего не знает, для него всё выглядит так, будто он слушает свой внутренний порт 8080. Магия трансляции происходит ниже, на уровне сетевого стека хоста.

Если на сервере включён файрвол UFW или iptables, нужно убедиться, что порт 8888 открыт для входящих соединений. Без этого браузер клиента просто не достучится до сервера, даже если контейнер слушает порт. Открытие порта в UFW делается через ufw allow 8888/tcp.

Доступ к веб-интерфейсу Dozzle через браузер и первичная навигация по списку контейнеров

Самый приятный момент - подключение через браузер. На любой машине в той же сети, что и сервер, нужно открыть веб-обозреватель и перейти по адресу http://адрес-сервера:8888. Если сервер находится в локальной сети с IP вроде 192.168.1.50, адрес будет выглядеть как http://192.168.1.50:8888. Для облачного сервера используется его публичный IP.

Главная страница Dozzle открывается мгновенно благодаря лёгкости интерфейса. В левой колонке отображается список всех контейнеров на хосте, включая сам контейнер Dozzle, который рекурсивно показывает свои собственные логи - забавная особенность, иногда полезная при отладке.

Клик по любому контейнеру в левой панели открывает поток его логов в основной области экрана. Логи прокручиваются в реальном времени, новые строки появляются по мере поступления. Поиск с поддержкой регулярных выражений позволяет быстро находить нужное среди потока. Например, выражение error|fail|warn выловит сразу все проблемные строки независимо от регистра.

Функция разделения экрана даёт возможность вывести логи нескольких контейнеров одновременно. Это незаменимо при отладке связки сервисов, где база, бэкенд и фронтенд должны работать вместе. Видя их логи бок о бок, проще понять, на каком этапе цепочки что-то ломается. Выгрузка логов на локальную машину пригождается, когда нужно отправить их коллеге или сохранить для последующего анализа.

Типичные сложности при работе с Dozzle и практические советы для повседневной эксплуатации

Несколько моментов, которые стоит держать в голове при ежедневной работе с инструментом.

  1. Dozzle не хранит историю логов в собственной базе. Если контейнер удалить, его логи исчезнут вместе с ним, даже если их видно в интерфейсе прямо сейчас. Для долгосрочного хранения нужны другие решения вроде Loki или ELK-стека.
  2. Просмотр логов через сокет требует, чтобы Dozzle имел доступ к /var/run/docker.sock с правами на чтение. На некоторых дистрибутивах с включённым AppArmor или SELinux могут потребоваться дополнительные разрешения.
  3. Если контейнеров на хосте много, имеет смысл повесить за Dozzle обратный прокси с базовой аутентификацией. Сам по себе Dozzle не имеет встроенной защиты доступа в базовой конфигурации, и оставлять его открытым в публичной сети без обвязки рискованно.
  4. При обновлении версий Dozzle через docker pull amir20/dozzle:latest и последующий перезапуск контейнера, старый контейнер нужно сначала остановить и удалить через docker stop dozzle и docker rm dozzle. Иначе имя останется занятым.

Освоение такого инструмента меняет привычный сценарий отладки контейнеризованных приложений. Вместо хаотичного жонглирования терминальными окнами и попыток вычитать нужное через docker logs --tail, появляется единая точка обзора, где видна жизнь всех сервисов на хосте. Это не просто удобство, а реальная экономия времени на расследовании инцидентов. Когда логи видны как живой поток, аномалии цепляют взгляд быстрее, чем при пролистывании статичных файлов. А значит, проблемы находятся раньше, чем успевают перерасти в полноценные сбои. Для всех, кто работает с Docker всерьёз, такой инструмент превращается из приятного дополнения в часть рабочего инструментария наравне с самим демоном Docker.