Files
obsidian-vault/personal/tech/watchtower-docker-updates.md

17 KiB
Executable File
Raw Permalink Blame History

title, created, updated, type, namespace, tags, related
title created updated type namespace tags related
Watchtower — почему не обновляет контейнеры 2026-09-20 2026-09-27 tech personal
watchtower
docker
updates
infra
pitfall
compose
family/how-to/kraken-watchtower
personal/tech/docker-data-root-mount-race
personal/tech/jellyfin-db-schema-mismatch
personal/tech/kraken-docker-startup-order

Watchtower — почему не обновляет контейнеры

Реюзабельная теория. Живой разбор на Kraken — в family/how-to/kraken-watchtower.

Главный вывод: docker ps показывает Up (healthy) — это ничего не говорит о том, что Watchtower хоть раз что-то обновил. Отсутствие обновлений не даёт ни ошибки, ни алерта. На Kraken Watchtower «работал» 2.5 месяца и не обновил ни один из 12 контейнеров.

Четыре причины (проверять в этом порядке)

1. Не задан --interval → таймер сбрасывается каждым стартом

Без флага интервал = 24ч от момента старта процесса. Любой рестарт контейнера (reboot, docker restart, обновление самого Watchtower) обнуляет отсчёт. Если контейнер рестартуют чаще раза в сутки — проверка не наступает никогда.

Признак в логе:

level=info msg="Scheduling first run: ... in 23 hours, 59 minutes, 59 seconds"
level=info msg="Waiting for running update to be finished..."

Вторая строка — процесс проснулся, увидел незавершённый апдейт и снова уснул. Признак, что цикл заклинило.

Фикс: --interval 86400 явно, либо --schedule "0 0 4 * * *" (cron-синтаксис из 6 полей), если нужен запуск в тихое время.

2. DOCKER_API_VERSION запинен на старую версию

DOCKER_API_VERSION=1.40 в environment ломает чтение метаданных образа. Симптом:

level=info msg="Found new lscr.io/linuxserver/prowlarr:latest image (3e9bd62ca90c)"
level=info msg="Unable to update container \"/prowlarr\": no available image info. Proceeding to next."

Watchtower нашёл новый образ, но не может обновиться. В новых версиях API показывается сам: using Docker API v1.54.

Фикс: убрать DOCKER_API_VERSION из environment вообще. Watchtower сам согласует версию с dockerd.

3. Образ containrrr/watchtower не развивается

Last push containrrr/watchtower — 2023-11-11 (проверено Docker Hub API). Он несовместим с современным dockerd, отсюда и no available image info — это не только про API-версию.

Фикс: активный форк nickfedor/watchtower (актуальная версия 1.22.2, теги latest, nightly, semver).

# проверить дату образа
curl -s "https://hub.docker.com/v2/repositories/containrrr/watchtower/tags?page_size=5" \
  | jq -r '.results[] | "\(.name) updated=\(.last_updated)"'

# проверить поддержку своей архитектуры (например arm64 для RPi)
curl -s "https://hub.docker.com/v2/repositories/nickfedor/watchtower/tags/1.22.2" \
  | jq -r '.images[] | "\(.architecture) \(.os)"'

4. --label-enable без единого label → не обновит НИЧЕГО

С флагом --label-enable Watchtower трогает только контейнеры с label com.centurylinklabs.watchtower.enable=true. Если label нет ни у кого — сам по себе флаг делает Watchtower бесполезным.

Проверка, кто помечен:

for c in $(docker ps --format "{{.Names}}"); do
  echo "$c -> $(docker inspect $c -f '{{index .Config.Labels "com.centurylinklabs.watchtower.enable"}}')"
done

⚠️ docker inspect на контейнере без label вернёт пустую строку, а не «false» — не путать с false (явное отключение).

Рабочий конфиг

services:
  watchtower:
    image: nickfedor/watchtower:1.22.2
    container_name: watchtower
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: --interval 86400 --cleanup --label-enable
    # НЕ добавлять DOCKER_API_VERSION
  • --interval 86400 — сутки, видно в логе рядом со Next scheduled run
  • --cleanup — удалять старые образы; без него диск забивается (на Kraken 87%)
  • --label-enable — оптин: обновляется только то, что явно помечено

🔴 Причина 5 (главная по коварству): правка compose НЕ ПРИМЕНЕНА

--interval, дедуп по API-версии, форк образа — всё это может быть правильно прописано в файле и при этом не действовать, потому что контейнер не пересоздан. Compose-файл — декларация; работающая конфигурация меняется только после docker compose up -d.

Маркер проблемы: конфиг в файле ≠ docker inspect контейнера.

# что РЕАЛЬНО запущено (а не что написано в файле)
docker inspect watchtower --format 'Image:{{.Config.Image}}'
docker inspect watchtower --format 'Cmd:{{.Config.Cmd}}'
docker inspect watchtower --format 'Created:{{.Created}}'

Признак, что правка не применена: Created (дата создания контейнера) старше даты правки compose-файла. На Kraken: файл исправлен 2026-09-20, контейнер создан 2026-05-12 и не менялся — 4 месяца крутился старый containrrr/watchtower с Cmd: [].

🔴 Почему фейковая проверка проходит: docker run --rm проверяет ОБРАЗ, а не контейнер

Соблазнительный способ «проверить Watchtower»:

docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  nickfedor/watchtower:1.22.2 --run-once --monitor-only --label-enable
# → Update session completed failed=0 scanned=7 updated=0    ← выглядит как доказательство!

Этот прогон создаёт временный контейнер из указанного образа и корректно отвечает на вопрос «рабочий ли образ nickfedor/watchtower:1.22.2». Он ничего не говорит о том, какой образ и с какими флагами крутится в боевом контейнере watchtower. На Kraken именно этот прогон был записан в документацию как «проверено, фикс работает» — при том, что боевой Watchtower оставался старым и убивал контейнеры.

🔴 Правило: для проверки работающего сервиса смотреть docker inspect <container>, а не запускать отдельный экземпляр образа. docker run --rm = проверка образа.

🔴 Watchtower может УБИВАТЬ контейнеры (SIGTERM → SIGKILL)

Watchtower не только обновляет — он останавливает контейнеры, и это видно как:

container kill  signal=2     ← SIGTERM
container kill  signal=9     ← SIGKILL, пауза ~10 с = таймаут docker stop
container die   exitCode=137

exitCode=137 = 128 + 9 (SIGKILL). Не путать с OOM-killer: проверить dmesg | grep -i -E 'oom|killed process' и journalctl -k -b — если пусто, убийца не ядро.

⚠️ restart: unless-stopped НЕ защищает от Watchtower. Политики рестарта действуют на сбои; Watchtower останавливает контейнер сам, штатно, чтобы пересоздать его с новым образом.

Признак в логе Watchtower, что он полез вне расписания:

level=info msg="Waiting for running update to be finished..."

Это значит предыдущая сессия обновления зависла и не закрылась — Watchtower дождался её и пошёл по контейнерам немедленно.

Как исключить контейнер из-под Watchtower: метка com.centurylinklabs.watchtower.enable=false (требуется --label-enable, иначе метка игнорируется).

🔴 labels нельзя изменить у работающего контейнера

docker update --label-add НЕ СУЩЕСТВУЕТ (unknown flag: --label-add). Метки задаются только при создании контейнера. Единственный путь: правка labels в compose + docker compose up -d (пересоздание). Проверено на Docker 29.4.3 / Compose v5.1.3.

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

Проверка (обязательна)

Up + отсутствие ошибок ≠ работа. И docker run --rm ≠ проверка работающего сервиса (см. выше).

Правильный двухшаговый порядок:

# ШАГ 1 — что РЕАЛЬНО запущено (главная проверка)
docker inspect watchtower --format 'Image:{{.Config.Image}} Cmd:{{.Config.Cmd}} Created:{{.Created}}'
#   Image: nickfedor/watchtower:1.22.2
#   Cmd:   [--interval 86400 --cleanup --label-enable]
#   Created: <должна быть ПОЗЖЕ правки compose-файла>

# ШАГ 2 — рабочий ли образ (вспомогательно)
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  nickfedor/watchtower:1.22.2 --run-once --monitor-only --label-enable

--monitor-only обязателен, иначе --run-once реально обновит контейнеры.

Что должно быть в выводе:

level=info msg="Watchtower 1.22.2 using Docker API v1.54"
level=info msg="Found new image" container=<X> new_id=<...>
level=info msg="Update session completed" failed=0 scanned=N skipped=0 updated=0

Found new image = баг с метаданными вылечен. updated=0 в monitor-only — нормально.

⚠️ Если scanned больше ожидаемого числа размеченных контейнеров — значит --label-enable не действует, и Watchtower трогает всё подряд. Проверять Cmd из шага 1.

⚠️ Автообновление может сломать сервис: пинить мажор версии

Тип latest + автообновление = переход на новую мажорную версию без предупреждения. Для сервисов с миграциями БД (EF Core, Prisma, любые forward-only миграции) это необратимо:

  • Jellyfin на Kraken: latest уехал 10.11.8 → 12.1, БД мигрировала вперёд, откатить схему нельзя → сервер отдавал 503, no such column: .... Детали: personal/tech/jellyfin-db-schema-mismatch.

Правило (общее): сервисам с forward-only миграциями БД безопаснее пиновый тег (image: jellyfin/jellyfin:12.1), а не latest + автообновление.

⚠️ Но это рекомендация, не аксиома — на Kraken решено иначе. Alex 2026-09-20 явно выбрал latest для Jellyfin: «пусть ставит до всех публичных релизов», т.е. включая мажор 13. Риск принят осознанно, бэкап БД делается перед пересозданием. См. family/how-to/kraken-watchtower § «Про Jellyfin и тег».

Отдельно важный нюанс: упал не мажорный апдейт как таковой, а рассинхрон «контейнер на старом образе + уже мигрированная БД». Сама миграция прошла нормально. То есть мажорный апдейт БД не ломает — ломает откат сервера назад без отката схемы.

Питфоллы диагностики

  • docker logs watchtower — Watchtower пишет в stdout, в отличие от Jellyfin (тот пишет в файл). Смотреть логи легко.
  • Таймстемпы в логе Watchtower — UTC, локальное время сервиса может быть другим.
  • Единожды увиденный Found new image не значит, что обновление применилось — нужна следующая строка (Unable to update / Updated).
  • Проверять надо фактическую дату запущенного образа, а не тег:
    docker inspect <container> -f '{{index .Config.Labels "org.opencontainers.image.version"}}'
    docker inspect <container> -f '{{index .Config.Labels "build_version"}}'
    

Урок

Watchtower — компонент, который молча не работает. Его нельзя «поставить и забыть»: обязательны (а) явный --interval, (б) отсутствие DOCKER_API_VERSION, (в) периодический --run-once --monitor-only как health-check, (г) осознанное решение, каким контейнерам вообще разрешено автообновление, (д) проверка docker inspect, что правка реально применена.

🔴 Два универсальных урока (2026-09-27)

1. Файл ≠ работающая конфигурация. Compose-файл — декларация. Пока не выполнен docker compose up -d, ничего не изменилось. Проверять docker inspect контейнера, а не файл. Дата создания контейнера (Created) старше даты правки файла = правка не применена.

2. docker run --rm <образ> проверяет ОБРАЗ, а не работающий контейнер. Прогон временного контейнера из «правильного» образа выглядит как полноценное доказательство (scanned=7 failed=0), но не говорит ничего о боевом сервисе. Именно так на Kraken в документацию попала запись «фикс выполнен», при том что 4 месяца крутился старый Watchtower и убивал контейнеры. Для проверки боевого сервиса — только docker inspect самого контейнера.

Оба урока общие, не только про Watchtower: они применимы к любому сервису, правка которого делается через compose-файл.

Связанные