Tự Động Cập Nhật Docker Container Trên HomeLab Với Watchtower và Docker Compose

HomeLab tutorial - IT technology blog
HomeLab tutorial - IT technology blog

Vấn Đề: Cập Nhật Thủ Công Ngốn Thời Gian Hơn Bạn Nghĩ

Ban đầu thì vô hại thôi. Bạn dựng vài Docker container — Nextcloud, Nginx Proxy Manager, Vaultwarden — mọi thứ chạy ngon. Rồi ba tuần sau, một bản vá bảo mật được phát hành. Bạn SSH vào, chạy docker pull, restart container, kiểm tra log. Xong.

Nhưng đến tháng thứ ba, bạn đã có mười hai container. Cái thì cập nhật hàng tháng, cái thì hàng tuần, có cái gần như hàng ngày. Chẳng mấy chốc, bạn tốn cả tiếng đồng hồ mỗi cuối tuần chỉ để giữ image cho mới. Lúc này không còn là HomeLab nữa — đó là việc làm thêm.

Tôi đã gặp đúng tình huống này. HomeLab của tôi lúc đó đã lên đến mười lăm service, và tôi rất ngại cái quy trình cập nhật đó. Có lần tôi bỏ lỡ một bản vá Vaultwarden và chuyện trở nên khó xử — một đồng nghiệp hỏi tôi đang chạy phiên bản đã vá chưa trong lúc thảo luận về một CVE. Không phải khoảnh khắc dễ chịu gì.

Tại Sao Lại Vậy: Nguyên Nhân Gốc Rễ

Docker image không tự cập nhật theo mặc định. Khi bạn chạy docker-compose up -d, Docker dùng image đang được cache sẵn ở local. Container của bạn bị đóng băng theo thời gian trừ khi bạn chủ động chạy docker-compose pull rồi mới đến docker-compose up -d.

Quy trình thủ công điển hình trông như thế này:

# Quy trình cập nhật thủ công (cách đau khổ)
docker pull vaultwarden/server:latest
docker stop vaultwarden
docker rm vaultwarden
docker run -d --name vaultwarden ... vaultwarden/server:latest

Nhân lên với mười hai container — mỗi cái có flag, volume, và cấu hình mạng khác nhau — bạn sẽ hiểu tại sao người ta tránh né cho đến khi có gì đó thực sự hỏng.

Ba Phương Án Và Tại Sao Hầu Hết Người Chọn Sai

Phương Án 1: Dùng Cron Job Đơn Giản

Một số người viết bash script lặp qua các service và chạy docker-compose pull && docker-compose up -d theo lịch. Dùng được, nhưng vấn đề sẽ lộ ra nhanh thôi:

  • Không biết image mới có thực sự khả dụng hay không
  • Không có thông báo — bạn không biết cái gì đã cập nhật hay cập nhật lúc nào
  • Dễ gãy: một lần pull lỗi có thể kéo theo nhiều service hỏng theo
  • Không có cơ chế rollback

Phương Án 2: Diun (Docker Image Update Notifier)

Diun theo dõi các container đang chạy và gửi thông báo khi có image mới hơn trên registry. Nhưng nó chỉ dừng ở đó — không tự cập nhật gì cả. Hoàn hảo nếu bạn muốn kiểm soát toàn bộ, nhưng bực bội nếu bạn muốn tự động thực sự. Với hầu hết HomeLab, đây là công cụ quá chuyên biệt cho sai vấn đề.

Phương Án 3: Watchtower (Công Cụ Phù Hợp Ở Đây)

Watchtower chạy như một Docker container. Nó theo dõi các container còn lại, kiểm tra registry xem có image mới không, pull về và restart container — tất cả mà không cần bạn đụng tay vào bàn phím. Nó hoạt động với Docker Compose hiện có của bạn và hỗ trợ thông báo qua Slack, Email, Gotify, và Telegram.

Với HomeLab, Watchtower cân bằng tốt: cài đặt cực đơn giản, gần như không cần bảo trì, và đủ linh hoạt cho các trường hợp thực tế. Tôi đã chạy nó trên mười lăm service hơn sáu tháng. Có hai lần container restart do upstream thay đổi breaking — cả hai đều được phát hiện ngay qua thông báo. Không bỏ lỡ bản cập nhật nào.

Cài Đặt Watchtower Với Docker Compose

Bước 1: Tạo Service Watchtower

Thêm Watchtower vào file docker-compose.yml riêng. Để tách biệt với các application stack giúp bạn quản lý và cập nhật độc lập dễ hơn.

# /opt/watchtower/docker-compose.yml
services:
  watchtower:
    image: containrrr/watchtower
    container_name: watchtower
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - WATCHTOWER_CLEANUP=true
      - WATCHTOWER_POLL_INTERVAL=86400
      - WATCHTOWER_NOTIFICATIONS=slack
      - WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL=https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK
      - WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER=watchtower-homelab
      - TZ=Asia/Tokyo

Ba biến môi trường cần hiểu trước khi copy-paste:

  • WATCHTOWER_CLEANUP=true — xóa các layer image cũ sau khi cập nhật. Bỏ qua cái này và ổ đĩa sẽ đầy dần với các dangling image. Với HomeLab bận rộn, dễ tốn 2–5 GB mỗi tháng.
  • WATCHTOWER_POLL_INTERVAL=86400 — kiểm tra cập nhật mỗi 24 giờ (đơn vị là giây). Kiểm tra hàng ngày là nhịp phù hợp với hầu hết HomeLab. Kiểm tra hàng giờ là thừa và tạo tải không cần thiết lên public registry.
  • TZ — đặt theo múi giờ địa phương của bạn. Nếu không, timestamp trong log và thời gian thông báo sẽ ở UTC, và bạn sẽ phải tính toán giờ trong đầu mỗi lần xem log.

Khởi động:

cd /opt/watchtower
docker-compose up -d

Bước 2: Loại Trừ Các Container Không Muốn Tự Động Cập Nhật

Không phải thứ gì cũng nên tự động cập nhật. Database là ví dụ rõ ràng nhất — nâng major version PostgreSQL hoặc MariaDB mà không có kế hoạch migration đúng đắn có thể làm hỏng dữ liệu một cách âm thầm. Đừng để Watchtower đụng vào những thứ đó.

Gắn label cho container nào bạn muốn Watchtower bỏ qua:

# Trong docker-compose.yml của ứng dụng
services:
  postgres:
    image: postgres:15
    container_name: postgres
    labels:
      - "com.centurylinklabs.watchtower.enable=false"
    volumes:
      - postgres_data:/var/lib/postgresql/data

Watchtower đọc label này và bỏ qua hoàn toàn container đó. Áp dụng cho bất kỳ thứ gì mà việc nâng version đòi hỏi can thiệp thủ công: database, các major framework version, service có các bước migration phức tạp.

Bước 3: Chế Độ Opt-In Cho Các Service Quan Trọng

Muốn kiểm soát chặt hơn? Chuyển Watchtower sang chế độ opt-in. Thay vì cập nhật tất cả rồi loại trừ ngoại lệ, nó chỉ đụng vào các container bạn đã đánh dấu rõ ràng là an toàn.

# docker-compose.yml của watchtower — chế độ opt-in
environment:
  - WATCHTOWER_LABEL_ENABLE=true   # Chỉ cập nhật các container có label

Rồi trên từng container bạn muốn tự động cập nhật:

labels:
  - "com.centurylinklabs.watchtower.enable=true"

Kiểm soát nhiều hơn, nhưng cũng phải quản lý nhiều label hơn. Với HomeLab có nhiều service, cách mặc định loại-trừ-những-gì-không-muốn thường ít rắc rối hơn. Opt-in phù hợp nếu bạn đang chạy thứ gì đó gần với môi trường production và muốn phê duyệt rõ ràng cho từng service được tự động cập nhật.

Bước 4: Thiết Lập Thông Báo

Chạy mà không có thông báo là thói quen xấu. Nếu Watchtower cập nhật thứ gì đó mà container không restart được, bạn muốn biết trong vài phút — chứ không phải ba ngày sau khi nhận ra RSS reader chưa đồng bộ từ thứ Ba.

Với Telegram (gửi thẳng vào điện thoại qua bot):

environment:
  - WATCHTOWER_NOTIFICATION_URL=telegram://BOT_TOKEN@telegram?chats=CHAT_ID

Lấy BOT_TOKEN từ @BotFatherCHAT_ID bằng cách gửi tin nhắn cho bot rồi kiểm tra API endpoint getUpdates. Watchtower dùng định dạng URL shoutrrr cho Telegram.

Thích Gotify hơn (push notification tự host):

environment:
  - WATCHTOWER_NOTIFICATIONS=gotify
  - WATCHTOWER_NOTIFICATION_GOTIFY_URL=http://gotify.yourdomain.com
  - WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN=your_app_token

Hoặc email nếu bạn muốn lưu lại lịch sử:

environment:
  - WATCHTOWER_NOTIFICATIONS=email
  - [email protected]
  - [email protected]
  - WATCHTOWER_NOTIFICATION_EMAIL_SERVER=smtp.yourdomain.com
  - WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT=587
  - WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER=smtp_user
  - WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD=smtp_password

Bước 5: Chạy Cập Nhật Thủ Công Một Lần

Đôi khi 24 giờ là quá lâu. Một CVE nghiêm trọng vừa được công bố và bạn cần vá tất cả ngay lập tức. Watchtower có chế độ run-once cho đúng tình huống này:

docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  containrrr/watchtower \
  --run-once \
  --cleanup

Lệnh này pull image mới, restart các container đã cập nhật, dọn sạch layer cũ, rồi thoát gọn. Instance Watchtower đang chạy của bạn không bị ảnh hưởng.

Cấu Hình HomeLab Thực Tế

Đây là ví dụ cấu hình điển hình với Watchtower tích hợp — tự động cập nhật cho hầu hết service, database thì loại trừ rõ ràng:

# /opt/apps/docker-compose.yml
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    # Không cần label — Watchtower mặc định cập nhật cái này
    volumes:
      - vaultwarden_data:/data

  nextcloud:
    image: nextcloud:latest
    container_name: nextcloud
    restart: unless-stopped
    volumes:
      - nextcloud_data:/var/www/html

  nextcloud_db:
    image: mariadb:10.11
    container_name: nextcloud_db
    restart: unless-stopped
    labels:
      - "com.centurylinklabs.watchtower.enable=false"  # KHÔNG tự động cập nhật
    environment:
      - MYSQL_ROOT_PASSWORD=yourpassword
      - MYSQL_DATABASE=nextcloud
    volumes:
      - nextcloud_db_data:/var/lib/mysql

volumes:
  vaultwarden_data:
  nextcloud_data:
  nextcloud_db_data:

Những Điều Cần Chú Ý

Watchtower không phải phép màu. Một số lưu ý thực tế:

  • Chỉ hoạt động với image tag latest — nếu bạn ghim cụ thể vaultwarden/server:1.30.0, Watchtower không có gì để cập nhật. Nó hoạt động bằng cách phát hiện thay đổi digest trên tag bạn đang theo dõi.
  • Breaking change trong minor release — hiếm với các project được bảo trì tốt, nhưng vẫn xảy ra. Nextcloud đặc biệt đã có những minor release yêu cầu migration database. Kiểm tra changelog cho các service quan trọng, nhất là những service có database backend.
  • Dung lượng ổ đĩa — luôn đặt WATCHTOWER_CLEANUP=true. Với HomeLab 15 container cập nhật hàng tuần, image không được dọn có thể ngốn hơn 10 GB trong vòng một tháng.
  • Private registry — pull từ private registry yêu cầu thông tin xác thực. Watchtower hỗ trợ ~/.docker/config.json (mount vào container) hoặc biến môi trường riêng cho từng registry.

Kiểm Tra Hoạt Động

Xem log Watchtower để xác nhận nó đang chạy và polling đúng:

docker logs watchtower --tail 50 -f

Một lần chạy thành công trông như thế này:

time="2026-03-30T09:00:00Z" level=info msg="Đang kiểm tra tất cả container (trừ những container bị tắt rõ ràng)"
time="2026-03-30T09:00:12Z" level=info msg="Tìm thấy image vaultwarden/server:latest mới"
time="2026-03-30T09:00:35Z" level=info msg="Đang dừng vaultwarden (abc123def456)"
time="2026-03-30T09:00:40Z" level=info msg="Đang tạo lại vaultwarden"
time="2026-03-30T09:00:41Z" level=info msg="Phiên hoàn tất" Found=1 Updated=1 Failed=0 Skipped=14 Scanned=15

Cái Failed=0 mới là thứ bạn cần nhìn thấy. Cấu hình thông báo và bản tóm tắt này sẽ tự động gửi vào Telegram hoặc hòm thư sau mỗi chu kỳ polling — không cần SSH nữa.

Share: