VPS cho lập trình viên - Phần 3: Docker và Caddy
Đưa một site chạy trong container lên VPS với file Compose tái lập được, HTTPS tự động nhờ Caddy và một quy trình deploy dễ nhớ.
VPS cho lập trình viên
Phần 3/1
Máy chủ đã được gia cố và chưa có gì lộ ra internet. Trong phần này, bạn sẽ đặt một site thật lên đó: build nhiều giai đoạn, một file Compose mô tả cả hệ thống, và Caddy lo TLS để chứng chỉ không còn là việc vặt.
Cần có trước
Bạn cần làm xong Phần 1 và 2 của loạt bài: một VPS đang chạy, một user không phải root có sudo, và tường lửa mở cổng 80, 443. Bạn cũng cần một tên miền có bản ghi A/AAAA trỏ về máy chủ.
Cài Docker
Dùng repository chính thức của Docker thay vì gói của bản phân phối, vì gói đó thường cũ:
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
Đăng xuất rồi đăng nhập lại để nhóm mới có hiệu lực, sau đó kiểm tra:
docker version
docker compose version
Site tự build
Ví dụ dưới đây build một site Astro tĩnh ở giai đoạn đầu và phục vụ nó bằng image Caddy ở giai đoạn sau. Image cuối không có Node.js, không có node_modules, không có công cụ build:
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM caddy:2-alpine
COPY --from=build /app/dist /srv
COPY Caddyfile /etc/caddy/Caddyfile
Nếu ứng dụng của bạn cần một Node server chạy liên tục, hãy giữ giai đoạn build và đổi giai đoạn cuối sang node:22-alpine, rồi cho Caddy trỏ tới nó bằng reverse_proxy.
File Caddyfile
Điểm mạnh nhất của Caddy là HTTPS tự động: nó xin và gia hạn chứng chỉ cho mọi tên miền có trong cấu hình. Với site tĩnh, cả file chỉ có bốn dòng:
example.com {
encode zstd gzip
root * /srv
file_server
}
Với ứng dụng Node, thay root/file_server bằng:
example.com {
encode zstd gzip
reverse_proxy app:4321
}
app là tên service trong Compose; DNS nội bộ của Docker sẽ phân giải nó.
Một file Compose cho tất cả
Compose cho máy chủ một bản mô tả khai báo về những gì cần chạy:
services:
web:
build: .
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:
Hai volume này quan trọng. /data chứa chứng chỉ và khóa tài khoản ACME; xóa nó thì Caddy phải xin lại mọi thứ và có thể chạm giới hạn tần suất. Hãy sao lưu nó cùng phần còn lại của máy chủ ở Phần 4.
Deploy thay đổi
Mỗi lần deploy chỉ cần ba lệnh, thêm một lệnh dọn để layer image cũ không làm đầy ổ đĩa:
git pull --ff-only
docker compose build
docker compose up -d
docker image prune -f
Bạn có thể chạy chúng bằng một script nhỏ hoặc một job CI SSH vào máy chủ. Vì build diễn ra ngay trên máy chủ, hãy giữ việc build nhẹ; nếu build chậm, chuyển bước build sang CI và đẩy image lên registry.
Log và bảo trì định kỳ
docker compose ps # đang chạy gì
docker compose logs -f web # theo dõi log
docker compose pull # lấy image gốc mới hơn
docker compose up -d # tạo lại container với image mới
Đặt giới hạn log trong cấu hình daemon (/etc/docker/daemon.json) để một container nói nhiều không làm đầy ổ đĩa, và cập nhật image gốc hằng tháng.
Kết luận
Docker giúp máy chủ tái lập được, còn Caddy khiến TLS gần như vô hình. Kết hợp lại, “deploy” từ việc mất cả buổi chiều thành một lệnh. Phần 4 khép lại loạt bài với phần ai cũng bỏ qua cho tới khi cần: sao lưu, giám sát và một lần khôi phục bạn đã thật sự chạy thử.