WatchtowerでDockerコンテナを自動更新する:個人サーバー向けシンプルCI/CD構築

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

午前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時まで起きていることは一度もない。

Share: