MAATRIX / Блог / Регламент обновления Docker-образов: как не жить на latest годами

Регламент обновления Docker-образов: как не жить на latest годами

MAATRIX

Ваша инфраструктура наверняка уже стоит на одном из двух полюсов: либо image: postgres:latest, который однажды ночью молча подтянет несовместимый мажор и уронит прод, либо image: nginx:1.24.0, зафиксированный три года назад на этапе первого деплоя и с тех пор ни разу не тронутый — вместе с десятком закрытых с тех пор уязвимостей внутри базового слоя. Обе ситуации выглядят как противоположности, но по сути это одна и та же ошибка: отсутствие процесса, который решает, когда и как версия образа меняется. Ниже — рабочий регламент, закрывающий оба случая: конкретное расписание проверки, критерии «обновлять сейчас или можно подождать» и инструменты, которые превращают это в рутину, а не в героизм одного инженера раз в год.

Почему latest — это не решение, а отложенная проблема

Тег latest не означает «последняя стабильная версия» — это просто указатель, который майнтейнеры образа двигают на новый релиз по своим правилам, а вы узнаёте об этом постфактум, в момент docker-compose pull. Полгода стек работает без изменений, потом плановое обслуживание — и postgres:latest тянет мажорный релиз с несовместимым форматом данных на диске. Контейнер не стартует, а разбираться приходится в разгар инцидента, а не спокойно за неделю до него.

Проблема глубже одного неудачного апгрейда. latest ломает воспроизводимость: один и тот же docker-compose.yml, развёрнутый сегодня и через полгода, тянет разные образы, хотя в git ничего не менялось. И ломает откат — когда после обновления что-то падает, узнать, какая версия работала вчера, часто невозможно, если контейнер уже пересоздан. Детальный разбор обеих проблем и живой пример инцидента — в статьях антипаттерн: latest вместо версии образа и как тег latest уронил API ночью. Здесь достаточно зафиксировать вывод: latest в проде — не про удобство, а про перенос ответственности за момент обновления с вас на реестр образов, который ничего не знает о вашей нагрузке, окне обслуживания и готовности отката.

Обратная крайность: пиннинг и забвение

Ровно противоположная ошибка встречается не реже. Команда один раз честно фиксирует версии — postgres:15.4, redis:7.0.11, node:18.16.0 — облегчённо выдыхает и больше к этим строчкам не возвращается. Формально требование «не жить на latest» выполнено. По факту образ застыл в моменте фиксации, а мир вокруг него — нет: в апстриме выходят патчи безопасности для той же мажорной ветки, обновляются пакеты в базовом слое ОС (Debian/Alpine внутри официальных образов), закрываются CVE в самой СУБД или рантайме — а в проде продолжает крутиться слепок годичной или трёхлетней давности.

Разница между «зафиксировано» и «безопасно» — ключевая вещь, которую регламент должен явно проговаривать. Зафиксированная версия защищает от случайных изменений при пересборке, но ничего не говорит о том, накопились ли за это время исправления критичных уязвимостей. postgres:15.4 образца 2023 года и текущий патч-релиз 15-й ветки — это разные образы с разным набором закрытых CVE в самой СУБД и в базовых системных пакетах, даже если мажорная и минорная версия PostgreSQL не менялись. Именно поэтому «зафиксировали и забыли» на горизонте пары лет по факту эквивалентно эксплуатации известных дыр — только без предупреждающего баннера, который есть у latest-подхода в виде постоянной непредсказуемости. Пиннинг без регулярного пересмотра — это управляемый риск, превращённый в неуправляемый техдолг; ему посвящён отдельный разбор — цена отложенного обновления как техдолг инфраструктуры.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Как измерить, что образ пора обновлять

Регламент нужен потому, что «пора обновлять» — не ощущение, а измеримая величина. На практике достаточно трёх сигналов.

Дистанция от актуального патч-релиза той же ветки. Если у вас postgres:15.4, а апстрим уже выпустил 15.9 — это минимум пять патч-релизов, в каждом из которых потенциально закрыты баги и уязвимости. Патч-релизы внутри одной минорной ветки СУБД и рантаймов, как правило, без breaking changes — их накопление обычно безопаснее пропустить одним прыжком, чем откладывать месяцами.

Известные уязвимости в самом образе. Проверяется сканером — docker scout cves postgres:15.4 (встроен в Docker Desktop и Docker CLI начиная с относительно свежих версий) или отдельным инструментом вроде Trivy:

trivy image --severity CRITICAL,HIGH postgres:15.4

Вывод покажет не только CVE в самой СУБД, но и в системных пакетах базового слоя — libssl, glibc, zlib и подобных, которые почти никогда не проверяют вручную, но именно через них чаще всего и находят реальные эксплойты.

Статус поддержки версии в апстриме (EOL). У PostgreSQL, Node.js, Python и большинства крупных проектов есть публичный график: сколько лет мажорная ветка получает патчи безопасности. Node.js 18, например, вышел из LTS-поддержки — значит, образ на его основе больше не получает исправлений от мейнтейнеров рантайма в принципе, и это не про «давно не обновляли», а про то, что обновлять уже некуда без смены мажорной версии.

Три сигнала складываются в простую таблицу, по которой удобно классифицировать состояние:

СигналКак проверитьЧто означает «плохо»
Отставание патч-версиисравнение с release notes апстримабольше 3-4 патч-релизов позади
CVE в образеtrivy image / docker scout cvesесть CRITICAL или HIGH без фикса
Статус веткистраница EOL проектаветка вне поддержки (EOL)

Регламент проверки: расписание и ответственность

Без закреплённого расписания проверка версий обычно скатывается к «когда вспомним» — то есть никогда. Рабочая схема, которая не требует отдельного человека на full-time, выглядит так:

  • Еженедельно (10-15 минут). Автоматический скан образов в проде на CVE уровня CRITICAL/HIGH — через CI-джобу или cron на самом сервере. Ответственный смотрит только отчёт, если он не пустой.
  • Раз в месяц (30-60 минут). Сверка версий всех образов в compose-файлах с актуальными патч-релизами апстрима. Здесь же — просмотр открытых Renovate-PR, если инструмент настроен (см. следующий раздел).
  • Раз в квартал. Пересмотр мажорных и минорных веток целиком: не вышла ли текущая ветка из EOL, не появился ли новый LTS-релиз, к которому стоит спланировать переход. Это отдельная задача с собственным окном тестирования, а не рутинный патч.
  • Немедленно, вне расписания. Публикация CVE уровня CRITICAL с публичным эксплойтом для используемого вами образа — повод сорвать плановое расписание и патчить в течение суток, а не ждать следующего понедельника.

Это же расписание закрывает и обратную сторону регламента — что откладывать можно, а что нельзя. Общий принцип триажа обновлений (не только образов, но и системных пакетов) разобран в статье регламент обновлений: что ставить сразу, что отложить — логика та же: критичность уязвимости и наличие breaking changes определяют срок, а не личное расписание того, кто отвечает за сервер.

Инструменты: от ручной проверки до автоматизации PR

Ручной регламент выше работает, но на практике его стоит подкрепить автоматизацией — иначе «раз в месяц» превращается в «когда наконец руки дошли».

Trivy или Grype — сканеры уязвимостей образов, которые можно встроить в CI как gate: если в образе находится CVE выше заданного уровня серьёзности, пайплайн не пускает деплой дальше.

trivy image --exit-code 1 --severity CRITICAL myregistry.local/myapp:2026.08.3

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

Renovate — инструмент, который сам мониторит реестры и открывает pull request с предложением обновить тег версии в compose-файле или Dockerfile. Ключевое отличие от latest — обновление видимо, обсуждаемо в PR и попадает в git-историю осознанным коммитом, а не молча подставляется при следующем pull. Базовая настройка под self-hosted Gitea, GitLab CE или Forgejo описана в статье Renovate в Docker Compose: готовый файл. Типичный конфиг для docker-compose:

{
  "packageRules": [
    {
      "matchDatasources": ["docker"],
      "matchUpdateTypes": ["patch"],
      "automerge": false,
      "labels": ["docker-patch"]
    },
    {
      "matchDatasources": ["docker"],
      "matchUpdateTypes": ["major"],
      "labels": ["docker-major", "needs-review"]
    }
  ]
}

Патч-обновления и мажорные помечаются разными лейблами — это позволяет мёржить рутинные патчи быстрее, а мажорные обязательно проводить через ревью и staging.

Watchtower стоит рядом, но решает другую задачу — он не открывает PR, а сам перезапускает контейнер на новом образе прямо на сервере. Это быстрее, но менее контролируемо: подходит для некритичных внутренних сервисов, но не для продакшен-СУБД, где вы хотите видеть diff версии до того, как она окажется в проде.

Общий обзор инструментов сканирования уязвимостей сервера, включая образы контейнеров, — в статье сканирование уязвимостей сервера: инструменты.

Процесс обновления: от алерта до прода без сюрпризов

Когда сигнал сработал — вручную по расписанию или через Renovate-PR, — дальше идёт одна и та же последовательность шагов вне зависимости от источника:

  1. Прочитать changelog апстрима, конкретно раздел «Breaking Changes» или «Upgrade Notes». Для патч-версий внутри минора это обычно две минуты; для мажорного перехода — может занять час, но это часы, потраченные до инцидента, а не во время него.
  2. Проверить staging-окружением с тем же compose-файлом, но отдельным .env и volume. Обновляете тег, поднимаете, гоняете smoke-тесты — базовые проверки, что сервис стартует, отвечает на health-check и держит основные сценарии.
  3. Обновлять по одному компоненту за раз. Если в компоузе одновременно нужно поднять версии базы, кэша и приложения — делайте это последовательно, а не одним коммитом. Так проще локализовать причину, если что-то сломалось.
  4. Сделать бэкап перед мажорным обновлением с изменением формата данных — PostgreSQL, MySQL, MongoDB и подобные системы это явно требуют. Зафиксированная версия сама по себе не отменяет необходимости плана отката.
  5. Коммитить изменение тега отдельно и с понятным сообщением, например postgres: 15.8 → 15.9, security patch CVE-2026-XXXXX. Так git-история версий образов становится читаемым журналом решений, а не набором случайных правок в конфиге.
  6. Держать staging-конфигурацию и regламент отката под рукой ещё день-два после выката на прод — большинство проблем всплывает в первые часы под реальной нагрузкой, а не на smoke-тестах.

Такой процесс занимает от пятнадцати минут для рутинного патча до пары часов для мажорного перехода — что всё равно на порядки дешевле разбора ночного инцидента, вызванного либо неожиданным latest, либо годами непропатченной уязвимостью.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

С чего начать, если сейчас в проде и latest, и версии, зафиксированные три года назад — одновременно?

Сначала закройте latest — это источник непредсказуемых поломок при любом пересоздании контейнера. Замените на текущие стабильные версии, зафиксировав их явно. Устаревший пиннинг — задача следующего этапа, с чтением changelog и тестированием на staging, но она не горит так, как случайный апгрейд без предупреждения.

Нужен ли отдельный инженер под этот регламент?

Нет, для большинства инфраструктур достаточно 10-15 минут в неделю на просмотр автоматического отчёта сканера плюс полчаса в месяц на сверку версий. Основная нагрузка приходится не на рутину, а на квартальный пересмотр мажорных веток — его стоит планировать заранее, а не втискивать между делом.

Что делать, если у образа нет чёткого semver и непонятно, насколько он отстал?

Ориентируйтесь на дату публикации тега в реестре (docker inspect показывает Created) и на статус поддержки апстрима, если он публикует свою политику EOL. Если ни того, ни другого нет — это самостоятельный повод насторожиться: проект без прозрачного версионирования сложнее контролировать в принципе.

Автоматическое применение обновлений (Watchtower) — это нарушение регламента?

Не обязательно, но применимо избирательно. Для внутренних некритичных сервисов автоприменение патч-версий снижает нагрузку на регламент. Для продакшен-СУБД и всего, что напрямую держит выручку, лучше PR-модель через Renovate — вы видите diff версии до того, как она окажется на проде, а не после.

Как часто реально нужно пересматривать мажорные версии, если всё и так работает?

Раз в квартал — минимум, чтобы не пропустить момент выхода ветки из EOL. Реальный переход можно откладывать дольше, если ветка ещё поддерживается и критичных CVE нет, но сам факт пересмотра должен происходить регулярно, а не по факту, что кто-то случайно заметил дату последнего патча.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →