週末にまとめて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本にまとめています。
ラボ構成のまとめを見る →