Dockerfile кажется простым файлом из десятка строк, но именно в нём появляются незакреплённые версии пакетов, лишние слои, запуск от root и команды, которые ломаются при первой смене базового образа. Hadolint — статический анализатор Dockerfile: он не собирает образ, а проверяет инструкции по набору правил и встроенному ShellCheck. Разберём установку, типовые замечания, настройку исключений и встраивание проверки в CI.
Установка и первый запуск
Hadolint распространяется одним бинарником. Скачайте релиз для своей архитектуры и положите в /usr/local/bin. Установка выглядит так:
curl -fsSL -o /tmp/hadolint https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64
chmod +x /tmp/hadolint
sudo install -m 0755 /tmp/hadolint /usr/local/bin/hadolint
hadolint --version
Тот же анализ в контейнере: docker run --rm -i hadolint/hadolint < Dockerfile.
Запуск предельно прост: hadolint Dockerfile. Код возврата ненулевой, если найдены замечания уровня выше порога. Для CI это означает, что сборку можно остановить до создания образа. Полезные флаги: --format json для машинной обработки, --failure-threshold warning, чтобы не падать из-за информационных сообщений, и --no-color для логов.
hadolint --failure-threshold warning Dockerfile; echo "exit=$?"
hadolint --format json Dockerfile | jq '.[].code'
Как читать правила
Коды DL1xxx–DL4xxx описывают правила Dockerfile, а SCxxxx — замечания ShellCheck для команд внутри RUN. Сообщение содержит строку, код, уровень и краткое объяснение. Не стоит включать все правила без разбора: часть из них избыточна для учебных примеров, часть важна только в продакшене.
- DL3008 — версии пакетов apt не закреплены. Образ может измениться без изменения Dockerfile.
- DL3015 — нет
--no-install-recommends, в образ попадают лишние пакеты. - DL3009 — не удалён кэш apt после установки, слой раздувается.
- DL3018 — аналогично для apk: версии не закреплены.
- DL3020 —
ADDвместоCOPYтам, где не нужна распаковка архивов. - DL3025 —
CMDилиENTRYPOINTзаписаны в shell-форме, сигналы не доходят до процесса. - DL3042 — pip без
--no-cache-dir, кэш остаётся в образе. - DL4006 — в shell-конвейере не включён
set -o pipefail. - DL3059 — несколько подряд идущих
RUN, которые можно объединить. - DL3007 — тег
latest, сборка невоспроизводима.
У правил есть уровни важности: error, warning, info и style. Для блокировки сборки обычно достаточно error и warning, а стилистические замечания можно разбирать постепенно. Не путайте уровень правила с порогом вывода: --failure-threshold управляет только кодом возврата, но не скрывает сообщения.
Пример: до и после
Возьмём типичный Dockerfile небольшого Python-приложения:
FROM python:latest
RUN apt-get update
RUN apt-get install -y curl git
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
CMD python app.py
Hadolint выдаст замечания о теге, раздельных RUN, отсутствии очистки кэша, незакреплённых версиях, кэше pip и shell-форме CMD. Улучшенный вариант выглядит так:
FROM python:3.12-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
Одно замечание DL3008 в этом варианте останется: версию curl нужно закрепить под свой репозиторий. Жёсткое закрепление без проверки приводит к ошибкам сборки. Для apt разумно фиксировать версии через lock-файл или переменные, а не копировать числа из чужой статьи. Hadolint лишь указывает на риск, решение остаётся за вами.
Настройка .hadolint.yaml
Правила и порог можно зафиксировать в файле рядом с Dockerfile. Это делает поведение одинаковым на ноутбуке и в CI:
failure-threshold: warning
ignored:
- DL3008
trustedRegistries:
- registry.example.com
- docker.io/library
override:
DL3018:
severity: error
Точечное исключение удобно ставить комментарием прямо в Dockerfile: # hadolint ignore=DL3008 перед инструкцией. Каждое исключение стоит сопровождать причиной в комментарии, иначе через полгода никто не вспомнит, зачем оно нужно. Не отключайте правила массово ради зелёного CI: линтер тогда не приносит пользы.
Проверка в CI
Проще всего запускать тот же контейнер, что и локально, закрепив тег или digest. Пример для GitLab CI:
lint:dockerfile:
image: hadolint/hadolint:latest
script:
- hadolint --failure-threshold warning Dockerfile
В примере указан latest для краткости; в рабочем пайплайне замените его на конкретный тег или digest. Это же правило действует для GitHub Actions: версию action и образ фиксируйте, чтобы обновление не меняло результат неожиданно. Проверку стоит запускать на каждый merge request и хранить отчёт как артефакт. Если Dockerfile несколько, передавайте их списком или используйте find в скрипте.
Проверка всех Dockerfile в репозитории
В проекте может быть несколько файлов: основной, отладочный и образы для тестов. Пройти по ним одним запуском поможет find, но путь к файлу в отчёте придётся сопоставлять вручную. Если файлы лежат в предсказуемых каталогах, перечислите их явно в скрипте — так проще читать вывод и настраивать исключения.
find . -name Dockerfile -not -path './.git/*' -exec hadolint {} \;
find . -name 'Dockerfile.*' -print0 | xargs -0 -n1 hadolint
Отдельно проверьте порядок слоёв. Если COPY . . стоит перед установкой зависимостей, любое изменение исходников сбрасывает кэш и заставляет скачивать пакеты заново. Сначала копируйте файлы зависимостей, устанавливайте их, и только потом добавляйте остальной код. Hadolint подскажет часть проблем, но порядок инструкций и содержимое .dockerignore остаются на совести автора.
Чего Hadolint не делает
- Не собирает образ и не проверяет, что команды действительно выполняются.
- Не ищет уязвимости в базовом образе и пакетах — для этого нужны Trivy, Grype или сканер реестра.
- Не заменяет ревью: архитектурные проблемы, лишние права и секреты в слоях он не увидит.
- Не гарантирует воспроизводимость: закреплённые версии пакетов всё равно могут исчезнуть из репозитория.
Дополнительно полезно включить проверки BuildKit: современный docker build умеет выводить предупреждения о качестве Dockerfile. Они пересекаются с Hadolint, но иногда ловят то, чего нет в правилах линтера.
Итог
Hadolint — быстрый способ поймать типовые ошибки Dockerfile до сборки: незакреплённые версии, лишние слои, неверную форму CMD и проблемы цитирования в shell. Начните с порога warning, исправьте критичные замечания, затем добавьте файл конфигурации и запуск в CI. Не превращайте исключения в привычку и дополняйте линтер сканером уязвимостей. Чек-лист: бинарник закреплён, конфиг в репозитории, проверка блокирует merge при ошибках, отчёты сохраняются.