Watchtowerで壊れた更新を試す前に、Docker APIバージョンで止まった検証ログ

Watchtower更新前後の差分確認を描いたアイキャッチ画像
目次

週末にまとめてComposeを更新している方へ

複数のDocker Composeサービスを自宅サーバーや家庭ラボで手動更新していると、更新漏れと更新事故のどちらも気になってきます。「更新を自動化したいが、壊れた更新を引いたときに戻せるのか」という不安は、サービスが増えるほど現実的になります。

この検証ログは、Watchtowerを「入れれば安全に自動更新してくれる道具」として紹介するものではありません。使い捨てVM上で、あえて壊れたイメージをローカルレジストリから流し、自動更新ログ・HTTP応答・コンテナ状態・手動ロールバックの手間を観測する記録です。

そして今回の最大の結果は、Watchtower自体が更新を一度も実行できなかったことでした。理由はDockerのAPIバージョン不一致です。失敗を隠さず、そこから読者が持ち帰れることを整理します。

検証環境

  • 実行場所: 某所ラボの使い捨て(ephemeral)VM上のDocker環境
  • Docker version 29.6.0
  • Docker Compose version v5.2.0
  • ローカルレジストリ: registry:2(127.0.0.1:5000、insecure-registries許可)
  • 検証対象イメージ: nginx:alpine をベースにした自作の lab-app(正常版はindex.htmlに「OK v1」を出す)
  • 自動更新ツール: containrrr/watchtower:latest(起動ログ上は Watchtower 1.7.1)

検証後はコンテナ・レジストリ・イメージ・作業ディレクトリをすべて削除しています。

実行コマンドとログ

1. 前提バージョンの確認

# docker --version && docker compose version
Docker version 29.6.0, build fb59821
Docker Compose version v5.2.0

2. ローカルレジストリと検証用ファイルを用意する

まずレジストリを起動します。

# docker run -d --restart=no -p 127.0.0.1:5000:5000 --name lab-registry registry:2
# curl -s http://localhost:5000/v2/_catalog
{"repositories":[]}

正常版イメージの Dockerfile(good/Dockerfile):

設定項目 nginx:alpine
RUN printf 'OK v1\n' > /usr/share/nginx/html/index.html

壊れた版イメージの Dockerfile(broken/Dockerfile)。起動直後に終了コード1で落ちるだけのものです。

設定項目 alpine:3.20
CMD ["sh","-c","echo broken-image-cannot-serve; exit 1"]

Composeファイル(docker-compose.yml)。watched 側にだけWatchtowerの対象ラベルを付けています。

services:
  app-watched:
    image: localhost:5000/lab-app:latest
    ports:
      - "127.0.0.1:8081:80"
    labels:
      - "com.centurylinklabs.watchtower.enable=true"
    restart: "no"
  app-unwatched:
    image: localhost:5000/lab-app:latest
    ports:
      - "127.0.0.1:8082:80"
    restart: "no"

3. 正常イメージ(v1)をpushして起動する

# docker build -t localhost:5000/lab-app:good ./good
# docker tag localhost:5000/lab-app:good localhost:5000/lab-app:latest
# docker push localhost:5000/lab-app:good
# docker push localhost:5000/lab-app:latest
latest: digest: sha256:017a7e69...2558c5a3 size: 856
# docker compose -f docker-compose.yml up -d
# curl -s http://localhost:8081/
OK v1
# curl -s http://localhost:8082/
OK v1

watched / unwatched の両サービスがv1で正常応答することを確認しました。

4. Watchtowerをrun-onceで動かす(ここで失敗)

更新がない状態でまず動作確認しようとしました。

# docker run --rm --network host -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower:latest --run-once --label-enable
time="2026-06-25T12:49:41Z" level=info msg="Watchtower 1.7.1"
time="2026-06-25T12:49:41Z" level=info msg="Only checking containers using enable label"
time="2026-06-25T12:49:41Z" level=info msg="Running a one time update."
time="2026-06-25T12:49:41Z" level=error msg="Error response from daemon: client version 1.25 is too old. Minimum supported API version is 1.40, please upgrade your client to a newer version"
panic: runtime error: invalid memory address or nil pointer dereference
[signal 設定項目: segmentation violation]

終了コードは2でした。WatchtowerはDockerデーモンに接続した時点で、クライアントのAPIバージョン1.25が古すぎる(デーモンの最小サポートは1.40)と拒否され、その後のメトリクス処理でnilポインタ参照によりpanicして停止しています。

つまり「更新対象を調べる」段階に入る前に落ちており、自動更新は一度も成立していません。

5. 壊れたイメージをlatestへ上書きpushする

本来ここで壊れた更新をWatchtowerに引かせたかった工程です。

# docker build -t localhost:5000/lab-app:broken ./broken
# docker tag localhost:5000/lab-app:broken localhost:5000/lab-app:latest
# docker push localhost:5000/lab-app:latest
latest: digest: sha256:04bfba5f...d6c8fd7f size: 855
# curl -s http://localhost:5000/v2/lab-app/tags/list
{"name":"lab-app","tags":["good","latest"]}

もう一度Watchtowerを実行しましたが、同じAPIバージョンエラーとpanicで終了(exit=2)しました。

6. 壊れた更新後のサービス状態を確認する

# docker compose -f docker-compose.yml ps -a
# curl -s http://localhost:8081/
OK v1
# curl -s http://localhost:8082/
OK v1

latestが壊れたイメージを指していても、Watchtowerが取り込めなかったため、watched / unwatched ともに稼働中のコンテナはv1のまま「OK v1」を返し続けました。

7. latestタグでは履歴を追いにくいことの確認

# docker images --digests localhost:5000/lab-app
localhost:5000/lab-app   good     sha256:017a7e69...2558c5a3
localhost:5000/lab-app   broken   sha256:04bfba5f...d6c8fd7f
localhost:5000/lab-app   latest   sha256:04bfba5f...d6c8fd7f
# curl -s http://localhost:5000/v2/lab-app/tags/list
{"name":"lab-app","tags":["good","latest"]}

レジストリのtags/listには good と latest しか並ばず、latestが今どのdigestを指しているかはdigest表示まで降りないと分かりません。タグ名だけでは「いつ何に変わったか」を追えないことが見て取れます。

8. 手動ロールバック

Watchtowerに頼らず、goodをlatestへ戻して再作成します。

# docker tag localhost:5000/lab-app:good localhost:5000/lab-app:latest
# docker push localhost:5000/lab-app:latest
latest: digest: sha256:017a7e69...2558c5a3 size: 856
# docker compose -f docker-compose.yml up -d --force-recreate
# curl -s http://localhost:8081/
OK v1

別タグ(:good)とdigestを残しておけば、latestを戻して --force-recreate するだけでv1へ復旧できました。

9. 後片付け

# docker compose -f docker-compose.yml down -v
# docker rm -f lab-registry
# docker rmi -f localhost:5000/lab-app:latest localhost:5000/lab-app:good localhost:5000/lab-app:broken

結果

  • 正常イメージのpush・Compose起動・HTTP確認・手動ロールバックは想定どおり動いた。
  • Watchtower 1.7.1 は、稼働中のDocker 29.6.0 に対してクライアントAPIバージョン1.25が古すぎるとして拒否され、panicで停止(exit=2)した。更新の有無にかかわらず2回とも同じ結果だった。
  • そのため、壊れたイメージをlatestへ上書きしても自動更新は発生せず、稼働コンテナはv1のままだった。
  • enableラベルによる更新範囲の限定(watched / unwatched の差)は、Watchtowerが動かなかったため実測できなかった。
  • latestタグ運用では、レジストリのタグ一覧から変更履歴を追えず、digestを見るか別タグを残す必要があることを確認できた。
  • 手動ロールバック(:good を latest へ戻して再作成)はv1へ確実に復旧できた。

失敗・制約・再現条件

今回いちばん大きな制約は、Watchtowerが一度も更新を実行できなかったことです。原因は、Watchtower側のDockerクライアントが古いAPIバージョン(1.25)で接続しようとし、新しいDockerデーモン(最小サポート1.40)に弾かれた、いわゆるバージョン互換の問題でした。

この環境ではバージョンを揃える修正までは行っていないため、次の点は本検証では裏付けられていません。

  • Watchtowerが壊れたlatestを実際に取り込んだときのサービス停止の挙動
  • enableラベルによる更新対象の限定が効くかどうかの実測

つまり「自動更新が壊れた更新でどう転ぶか」は再検証が必要です。再現する場合は、使い捨てのVM+Docker環境で同じ手順を流し、WatchtowerのバージョンとDockerデーモンのAPI互換を先に確認してから自動更新工程に入る、という順番になります。

この失敗自体が、読者にとっては有益な前提です。自動更新ツールは、入れた瞬間に動くとは限らず、稼働中のDockerとのバージョン互換でつまずくことがあります。

三つの運用を比べる

読者が実際に迷うのは、次の三つです。Watchtowerの自動更新挙動とラベル限定は今回未実測のため、設計・公式ドキュメント上の前提として記載し、検証できた点と分けています。

評価軸 Watchtowerで自動更新 Watchtowerを監視・通知寄りに使う 手動でpull + up
更新対象の限定しやすさ enableラベルで選べる設計(本検証では未実測) 同様にラベル+監視のみ運用で限定 Composeファイル単位で完全に手動
失敗時の検出しやすさ 更新後に壊れても自動では戻らない。通知未設定だと気づきにくい 通知で気づき、適用は人が判断 実行者がその場で確認できる
ロールバックのしやすさ Watchtower自体にロールバック機能はなく、結局手動と同じ手順が要る 同上 元タグ/digestを残していれば確実に戻せる(検証で確認)
変更履歴の追いやすさ latest運用だとタグ一覧では追えずdigest確認が必要(全方式共通の注意) 同上 同上

家庭ラボではどこから始めるか

今回の範囲で言える、現実的な判断です。

自動更新を常用してよい条件:

  • WatchtowerのバージョンとDockerデーモンのAPI互換を先に確認できている
  • 通知を必ず設定し、何がいつ更新されたか分かるようにしている
  • 重要なサービスは latest だけに頼らず、:good のような別タグとdigestを残している
  • 手動ロールバック手順を事前に用意してある

まだ自動更新にしない方がよい条件:

  • 戻すためのタグやdigestを残していない
  • latestタグのみで運用していて履歴を追えない
  • 通知が未設定で、壊れた更新に気づく手段がない
  • 停止が許されないサービスである

Watchtowerは「入れれば安全に巻き戻してくれる」道具ではありません。まずバージョン互換と通知・限定対象から始め、latest運用の履歴の追いにくさと手動ロールバックの手順を先に用意しておく。それが、更新漏れと更新事故の両方を減らしたい家庭ラボ運用者にとって、現実的な入口だと考えます。

この検証を回している環境

この検証は、自宅の常設ラボ(使い捨てVM/LXCを回す母艦+GPU+VLAN分離ネットワーク)で動かしています。使っている機材と選定理由、全体構成は1本にまとめています。

ラボ構成のまとめを見る →
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

目次