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ừ @BotFather và CHAT_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.

