Skip to content
$ cd .. Go Back

Zero-downtime deployments với Docker Swarm

swarmup demo

Nhắc đến deployment, chắc nhiều người sẽ nghĩ ngay đến Kubernetes. Nhưng với các dự án cá nhân, side project, hoặc những hệ thống vừa và nhỏ, mình thường tránh dùng Kubernetes vì phần vận hành của nó hơi quá tay so với nhu cầu thực tế.

Trước đây mình hay dùng Docker Compose vì nó đơn giản, dễ bàn giao, dễ debug. Nhưng sau một thời gian dùng cho các project cần uptime tốt hơn, mình nhận ra Compose hơi yếu ở phần deploy: mỗi lần update image thường phải restart container, dễ tạo downtime đúng lúc mình cần hotfix hoặc cập nhật app khi đang có user.

Docker Swarm là điểm cân bằng mình thấy hợp lý hơn. Nó vẫn là Docker, vẫn dùng compose file quen thuộc, nhưng có thêm rolling update, rollback, secret, overlay network, và load balancing. Nói ngắn gọn là đủ những thứ mình cần để deploy gọn gàng hơn mà không phải kéo cả một hệ sinh thái lớn vào server.

Lưu ý

Tới thời điểm hiện tại, mặc dù không còn được cập nhật tính năng mới, Docker Swarm vẫn được duy trì và hỗ trợ bởi Docker. Mình nghĩ đơn giản là nó đã hoạt động ổn định, hoàn thành tốt nhiệm vụ của nó, nên không cần thêm tính năng mới nữa. Và mình cũng không thấy có lý do gì để Docker Swarm bị khai tử trong tương lai gần.

Trong bài này mình sẽ chia sẻ cách mình thường deploy app với swarm-up, một Bash script tổng hợp lại những thao tác mình hay làm khi deploy app web lên Docker Swarm.

Vì sao mình chọn Docker Swarm?

Docker Compose rất ổn nếu bạn cần chạy một app trên một VPS theo cách đơn giản nhất. Nhưng khi bắt đầu muốn deploy thường xuyên hơn, mình thường vướng vài thứ:

  • Không có rolling update đúng nghĩa.
  • Việc đổi image và restart container dễ tạo downtime.
  • Không có cơ chế rollback ở level orchestrator.
  • Quản lý secret dễ bị biến thành .env nằm rải rác trên server.
  • Khi muốn scale nhiều replica thì phải tự xử lý routing/load balancing bên ngoài.

Docker Swarm giải quyết vừa đủ các điểm đó. Không phải vì Swarm “xịn” hơn Compose trong mọi trường hợp, mà vì nó nằm đúng khoảng giữa: nhẹ hơn Kubernetes, nhưng có nhiều thứ phục vụ deployment hơn Compose. Một vài khái niệm Swarm cần biết:

  • Node: Máy chủ chạy Docker Engine. Node có thể là manager hoặc worker.
  • Service: Một nhóm container giống nhau được Swarm quản lý.
  • Task: Một container cụ thể được tạo ra từ service.
  • Stack: Một nhóm service được deploy cùng nhau từ compose file.

Điểm mình thích nhất là bạn vẫn có thể bắt đầu từ file compose quen thuộc, rồi thêm phần deploy: để Swarm biết cách rollout service:

deploy:
  replicas: 3
  update_config:
    order: start-first
    failure_action: rollback
  rollback_config:
    order: stop-first

order: start-first là phần quan trọng cho zero-downtime deployment: container mới được start trước, sau đó container cũ mới bị stop. Nếu update fail, Swarm có thể rollback service về trạng thái trước đó.

Tất nhiên, nếu bạn chỉ chạy một VPS thì đây chưa phải High Availability đúng nghĩa. Server chết thì app vẫn chết. Nhưng với các project nhỏ hoặc home lab với 1 server cho mọi app, mình thấy mô hình này đủ tốt nếu có backup dữ liệu, monitoring cơ bản, và quy trình restore rõ ràng.

swarm-up làm gì?

swarm-up là một script Bash mình dùng để chuẩn hóa flow deploy trên server. Nó dùng Traefik để routing và tự lấy SSL, dùng Gum để có prompt tương tác trong terminal.

Mục tiêu của script không phải là giấu Docker Swarm đi hoàn toàn. Mình vẫn muốn hiểu service, stack, network, secret hoạt động như thế nào. Script chỉ giúp những thao tác lặp lại nhanh hơn, tiện hơn, và ít lỗi hơn.

Mình thường dùng kiểu setup này cho các project làm cho khách. Với các project outsource, nhiều khi không cần dựng thêm dashboard quản lý deployment; chỉ cần một flow ổn định, dễ bàn giao, và đủ rõ ràng để CI/CD có thể SSH vào server chạy swarmup update, còn khách hoặc team vận hành vẫn có thể SSH trực tiếp để restart, xem logs, hoặc quản lý service khi cần.

Hiện tại script xử lý các việc chính:

  • Cài Gum và Docker nếu server chưa có.
  • Khởi tạo Docker Swarm.
  • Tạo overlay network cho Traefik và app.
  • Deploy Traefik với Let’s Encrypt.
  • Scaffold folder cho từng service trong ~/apps/<service>/.
  • Tạo compose file có sẵn rolling update và rollback.
  • Tạo/rotate Docker secret từ file secrets.
  • Start, update, xem logs, stop, hoặc remove service.

Sau khi setup, cấu trúc trên VPS thường sẽ trông như thế này:

/home/ubuntu/
├── apps/
│   └── example/
│       ├── docker-compose.yml
│       └── secrets
└── traefik/
    ├── docker-compose.yml
    └── certs/

~/traefik/docker-compose.yml là stack reverse proxy chung. Mỗi app nằm trong ~/apps/<service>/, có compose file riêng và file secrets riêng. File secrets là nơi mình đặt các biến dạng KEY=VALUE, sau đó script sẽ đưa nó vào Docker secret và mount vào container ở /run/secrets/<service>_secrets.

Cài đặt

Trên server mới, mình cài script bằng lệnh sau:

curl -fsSL https://github.com/xuannghia/swarm-up/raw/refs/heads/main/bin/swarmup.sh \
  -o /tmp/swarmup.sh && \
  sudo mv /tmp/swarmup.sh /usr/local/bin/swarmup && \
  sudo chmod +x /usr/local/bin/swarmup

Script hiện chạy tốt trên Ubuntu/Debian hoặc Arch Linux. Với server public thật, trước đó bạn vẫn nên cài thêm các phần cơ bản như firewall, fail2ban, update package, và mở port 22 cho SSH và 80/443 cho Traefik.

Setup server lần đầu

Trên một server mới, mình chạy:

swarmup setup

Lệnh này sẽ:

  • Cài Gum nếu thiếu.
  • Cài Docker nếu thiếu.
  • Chạy docker swarm init.
  • Tạo hai overlay network: traefik-publicapp-network.
  • Hỏi email Let’s Encrypt.
  • Deploy Traefik vào stack traefik.

Phần này chỉ cần chạy một lần cho mỗi server. Nếu chạy lại, script sẽ bỏ qua những phần đã có.

Tạo service

Ví dụ muốn test bằng image traefik/whoami, mình tạo service như sau:

swarmup create example traefik/whoami --domain example.swarm.localhost --port 80

Lệnh này chỉ scaffold file, chưa deploy ngay. Sau khi chạy xong, script tạo folder:

~/apps/example/
├── docker-compose.yml
└── secrets

Nếu app cần ENV hoặc secrets, mình sửa file ~/apps/example/secrets trước khi start:

DATABASE_URL=postgres://user:password@postgres:5432/app
APP_SECRET=change-me

Với app cần public ra internet, --domain sẽ làm script tự thêm label Traefik vào compose file. Nếu service chỉ dùng nội bộ, có thể bỏ qua --domain, khi đó service chỉ join vào app-network.

Khi dùng domain thật, nhớ trỏ DNS A record về IP của VPS trước khi deploy. Traefik sẽ xin certificate Let’s Encrypt lúc service được đưa lên, nên DNS sai hoặc chưa propagate xong thì SSL có thể fail.

Deploy service

Khi compose file và secrets đã ổn, mình start service:

swarmup start example

Script sẽ tạo Docker secret từ file ~/apps/example/secrets, rồi chạy docker stack deploy cho service đó.

Với ví dụ local ở trên, có thể kiểm tra bằng:

curl -k https://example.swarm.localhost

Nếu dùng domain thật, chỉ cần truy cập domain đã trỏ về VPS. Traefik sẽ route request vào service dựa trên label trong compose file.

Update app

Đây là phần mình dùng thường xuyên nhất. Khi có image mới, mình chỉ cần:

swarmup update example --image ghcr.io/your-org/your-app:latest

Hoặc vừa đổi image vừa scale replica:

swarmup update example --image ghcr.io/your-org/your-app:2026-06-23 --replicas 3

swarmup update sẽ tạo secret mới từ file secrets, update service bằng docker service update, rồi cleanup secret cũ nếu không còn service nào dùng. Nhờ update_config.order: start-first, Swarm sẽ start task mới trước khi dừng task cũ.

Trong thực tế, mình thường để CI build và push image lên registry trước. Khi cần deploy, SSH vào server và chạy swarmup update với tag image mới. Đơn giản, ít thứ phải vận hành, và vẫn có rolling update.

Các lệnh cho khách hoặc team vận hành

Sau khi bàn giao server, thường mình chỉ cần note lại vài lệnh cơ bản:

swarmup logs example

Xem live logs của service.

swarmup update example --image ghcr.io/your-org/your-app:2026-06-23

Update service lên image mới. Lệnh này cũng phù hợp để CI/CD chạy qua SSH sau khi build và push image xong.

swarmup stop example

Tạm dừng service. Folder ~/apps/example/ và secret vẫn được giữ lại.

swarmup start example

Bật lại service đã stop trước đó.

swarmup remove example

Xóa hẳn stack, secret, và folder service. Lệnh này có confirm trước khi chạy.

Bạn cũng không cần nhớ chính xác tên service. Với các lệnh như start, update, logs, stop, remove, nếu không truyền tên service thì script sẽ liệt kê các service đang có và cho chọn trực tiếp trên terminal nhờ Gum.

Một vài lưu ý

Cách deploy này không biến một VPS thành hệ thống High Availability. Nó chỉ giúp quá trình deploy app trên một server gọn gàng hơn, giảm downtime khi rollout version mới, và chuẩn hóa cấu trúc nhiều service.

Nếu dùng cho production nghiêm túc hơn, mình vẫn sẽ để ý các phần sau:

  • Backup database và volume định kỳ.
  • Monitoring disk, memory, CPU, container restart.
  • Alert khi Traefik hoặc app down.
  • Pin image tag rõ ràng thay vì lúc nào cũng dùng latest.
  • Test healthcheck của app để Swarm rollback có ý nghĩa hơn.
  • Kiểm tra DNS, DNSSEC, firewall trước khi deploy domain thật.

Với mình, swarm-up phù hợp nhất cho side project, app nội bộ, landing page, home lab, hoặc những hệ thống vừa và nhỏ không muốn vận hành Kubernetes. Nó không cố giải quyết mọi bài toán deployment, chỉ làm tốt một flow mình dùng đi dùng lại: setup server, tạo service, start, rồi rolling update khi có image mới.

Vậy là đủ dùng rồi :v