午前2時、バグはすでに6時間前に修正されていた
あの夜のことは今でも鮮明に覚えている。開発チームが夜8時にDockerHubへホットフィックスをpushした。夕食を食べながら「後でSSHしてimageをpullしてコンテナを再起動すればいい」と思っていた。結果:午前2時にお客さんから「なんでまだバグが直ってないの?」というメッセージが来た。新しいimageはレジストリに6時間前から上がっていたのに、サーバーのコンテナは誰もpullしていなかったから古いimageで動き続けていた。
それがWatchtowerを調べ始めたきっかけだ。それ以来、個人サーバーでのコンテナ更新はほとんど手作業が不要になった。
Watchtowerとは何か、なぜ必要なのか
一言で言えば:Watchtowerはバックグラウンドで動くコンテナで、Dockerホスト上で稼働中のコンテナを監視する。レジストリ(Docker Hub、GHCR、プライベートレジストリ)に新しいimageを検出すると、自動で新しいimageをpullし、古いコンテナを停止して新しいimageで再起動する——誰かが見張る必要はない。
Portainer(管理UI)やDocker Swarm(複雑なオーケストレーション)とは違い、Watchtowerはただ一つの問題を解決する:pull → stop → runのループを自動化すること。VPS上でDockerを動かしている人なら誰でも何十回も繰り返してきた作業だ。
30台以上のコンテナが動くプロダクションクラスターで、かつてはbashスクリプトで手動でレジストリをポーリングしていた。Watchtowerに移行後、リソース使用量が約40%削減できた。理由はシンプルで:Watchtowerはimageのダイジェストだけを確認し、ダイジェストが変わっていなければ完全にスキップして不要なpullをしない。
動作の仕組み
- Watchtowerは定期的にレジストリのimageダイジェストを確認する(デフォルトは24時間ごと)
- ダイジェストが変わった場合 → 新しいimageをpull → 既存の設定(volumes、ports、env vars)を引き継いでコンテナを再作成
- ダイジェストが同じ場合 → スキップ、何もしない
実践:Watchtowerのインストールと設定
ステップ1:基本的なWatchtowerの起動
最速で試す方法:
docker run -d \
--name watchtower \
--restart unless-stopped \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower
このコマンドはDockerソケットをマウントして、WatchtowerがホストのDocker daemonと通信できるようにする。--restart unless-stoppedの挙動についてはDocker HealthcheckとRestart Policiesの詳細解説も参考になる。デフォルトでは24時間ごとに確認し、実行中のすべてのコンテナを更新する。
待たずにすぐテストしたい場合は--run-onceを追加する——Watchtowerは一度だけ確認して終了する:
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower --run-once
ステップ2:特定のコンテナだけを監視する
ここは多くの人が見落としがちな部分だ。デフォルトではWatchtowerはすべてのコンテナを更新する——データベースや監視スタックなど、日中に絶対自動再起動させたくないものも含めて。
方法1 — ホワイトリスト:コマンドの末尾にコンテナ名を渡すと、Watchtowerはそれらだけを監視する:
docker run -d \
--name watchtower \
--restart unless-stopped \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower myapp nginx-proxy
方法2 — ラベルベース:より柔軟で、Docker Composeとも使いやすい。環境変数でこのモードを有効にする:
docker run -d \
--name watchtower \
--restart unless-stopped \
-v /var/run/docker.sock:/var/run/docker.sock \
-e WATCHTOWER_LABEL_ENABLE=true \
containrrr/watchtower
そしてdocker-compose.ymlで、自動更新したいサービスにだけラベルを付ける——付けていないものはWatchtowerが無視する:
services:
myapp:
image: myuser/myapp:latest
labels:
- "com.centurylinklabs.watchtower.enable=true"
postgres:
image: postgres:16
# ラベルなし → Watchtowerがスキップ、DBは安全
ステップ3:インターバルと通知の設定
5分ごとに確認し、更新があったときにTelegram通知を送る:
docker run -d \
--name watchtower \
--restart unless-stopped \
-v /var/run/docker.sock:/var/run/docker.sock \
-e WATCHTOWER_POLL_INTERVAL=300 \
-e WATCHTOWER_NOTIFICATIONS=shoutrrr \
-e WATCHTOWER_NOTIFICATION_URL="telegram://BOT_TOKEN@telegram?channels=CHAT_ID" \
-e WATCHTOWER_CLEANUP=true \
-e WATCHTOWER_LABEL_ENABLE=true \
containrrr/watchtower
覚えておきたい環境変数:
WATCHTOWER_POLL_INTERVAL:確認の間隔(秒)(デフォルト86400 = 24時間)WATCHTOWER_CLEANUP=true:更新後に古いimageを削除する——これがないとディスクが週ごとに埋まっていくWATCHTOWER_INCLUDE_STOPPED=true:停止中のコンテナも更新するWATCHTOWER_NO_RESTART=true:新しいimageをpullするだけで再起動しない(低負荷時間帯の前に事前pullするのに使う)
ステップ4:CI/CDパイプラインとの連携
ここが本当に面白い部分だ。WatchtowerにインターバルでポーリングさせるのではなくCI/CDがimageをpushし終わったタイミングで、内蔵のHTTP APIを使って即座に更新をトリガーできる。
HTTP APIを有効にする:
docker run -d \
--name watchtower \
--restart unless-stopped \
-p 8080:8080 \
-v /var/run/docker.sock:/var/run/docker.sock \
-e WATCHTOWER_HTTP_API_UPDATE=true \
-e WATCHTOWER_HTTP_API_TOKEN="your-secret-token-here" \
-e WATCHTOWER_HTTP_API_PERIODIC_POLLS=true \
-e WATCHTOWER_LABEL_ENABLE=true \
-e WATCHTOWER_CLEANUP=true \
containrrr/watchtower
GitHub Actionsでimageをpushした直後にWatchtowerを呼び出すステップを追加する:
# .github/workflows/deploy.yml
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: DockerイメージのビルドとPush
uses: docker/build-push-action@v5
with:
push: true
tags: myuser/myapp:latest
- name: Watchtower更新をトリガー
run: |
curl -H "Authorization: Bearer ${{ secrets.WATCHTOWER_TOKEN }}" \
https://yourserver.com:8080/v1/update
コードをpush → CIがビルド → imageをpush → Watchtowerがシグナルを受信 → サーバーが自動更新。SSH不要、cronjob不要、夜更かし不要。
セキュリティに関する注意
/var/run/docker.sockのマウントはroot相当のアクセス権限だ——そのソケットを制御できる者は、マシン上のDocker全体を制御できる。いくつか覚えておくべき点:
- 8080ポートをインターネットに直接公開しない——SSLを付けたNginxのリバースプロキシの後ろに置くか、CI/CDのIPアドレスだけを許可するファイアウォールで保護する
- トークンは十分に長くランダムなものにする:
openssl rand -hex 32で生成すれば十分 - プライベートレジストリを使う場合はプライベートレジストリのセルフホストと合わせて
~/.docker/config.jsonに認証情報を設定してマウントする:-v $HOME/.docker/config.json:/config.json
複雑でないこと——それが強み
WatchtowerはKubernetesやArgoCDの代わりになろうとしていない。ただ一つのことをする:あなたが手作業でやり続けてきたpull-stop-runのループを自動化すること。homelabや数十台のコンテナが動く個人VPSにとって、これは最もコストが低く(無料)、設定が少なくて済む真の自動デプロイソリューションだ。
今の自分のセットアップ:どのコンテナを更新するかを正確にコントロールするためのラベルベース監視と、pushした直後に更新するためのGitHub ActionsからのHTTP APIトリガーを組み合わせている。DatabaseとRedisにはラベルがない——Watchtowerは絶対に触れない。アプリのコードはラベルを付けて、あとは任せている。
あの夜以来、imageのpullを忘れて午前2時まで起きていることは一度もない。

