17 KiB
Executable File
title, created, updated, type, namespace, tags, related
| title | created | updated | type | namespace | tags | related | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Watchtower — почему не обновляет контейнеры | 2026-09-20 | 2026-09-27 | tech | personal |
|
|
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-файл.
Связанные
- family/how-to/kraken-watchtower — живой разбор и точные команды на Kraken
- personal/tech/kraken-docker-startup-order — что Watchtower ломает на Kraken (убийство контейнеров, exit 137)
- personal/tech/jellyfin-db-schema-mismatch — что ломает автообновление мажора
- personal/tech/docker-data-root-mount-race — другой дефект «контейнеры пропали»