Cài Caddy làm reverse proxy trên VPS bằng Docker
Dựng Caddy làm reverse proxy bằng Docker trên VPS mới: tạo network dùng chung, viết Caddyfile và docker-compose, tự động có HTTPS, rồi thêm domain mới chỉ bằng một lệnh reload không downtime.
Mục lục
- Vì sao chọn Caddy
- 1. Cài đặt Caddy lần đầu
- Tạo thư mục chứa cấu hình Caddy
- Tạo Docker network dùng chung
- Tạo file cấu hình Caddyfile
- Tạo docker-compose.yml cho Caddy
- Khởi động Caddy
- 2. Thêm một ứng dụng / domain mới
- Bước 1: Nối container mới vào network của Caddy
- Bước 2: Khai báo domain trong Caddyfile
- Bước 3: Reload Caddy (zero downtime)
- Quy tắc quan trọng: không dùng localhost trong reverse_proxy
- Lưu ý
Caddy là reverse proxy nổi bật ở chỗ tự động cấp phát và gia hạn chứng chỉ SSL (HTTPS) mà gần như không cần cấu hình gì (zero-config SSL). Bài này dựng Caddy bằng Docker trên một VPS mới, sau đó đưa ra quy trình 3 bước để thêm một ứng dụng/domain mới mà không gây downtime.
Trong toàn bài, thay example.com, app.example.com, api.example.com bằng domain
của bạn, và các tên container (my-new-app, my-nextjs-container, my-backend-api)
bằng tên container thực tế.
Vì sao chọn Caddy
- Tự động lấy chứng chỉ SSL từ Let's Encrypt / ZeroSSL.
- File cấu hình cực kỳ ngắn gọn và dễ hiểu.
- Hỗ trợ reload cấu hình không làm gián đoạn dịch vụ (zero-downtime).
1. Cài đặt Caddy lần đầu
Tạo thư mục chứa cấu hình Caddy
Gom các file cấu hình vào cùng một thư mục để sau này dễ quản lý, chỉnh sửa và sao lưu.
mkdir -p ~/services/caddy
cd ~/services/caddyTạo Docker network dùng chung
Để Caddy "nhìn thấy" và chuyển tiếp (proxy) traffic đến các container khác, tất cả
phải nằm trên cùng một network. Tạo một network tên caddy-net (hoặc giữ tên
nginx-proxy nếu bạn đã dùng từ trước và muốn tương thích ngược):
docker network create caddy-netTạo file cấu hình Caddyfile
Caddyfile là nơi bạn khai báo các domain và dịch vụ tương ứng.
cat > ~/services/caddy/Caddyfile << 'EOF'
# =============================================
# Caddy Reverse Proxy Configuration
# =============================================
# Chỉ cần khai báo domain, Caddy sẽ tự lo phần SSL!
# Ví dụ 1: Proxy đến một Next.js app
# app.example.com {
# reverse_proxy my-nextjs-container:3000
# }
# Ví dụ 2: Proxy đến một API backend
# api.example.com {
# reverse_proxy my-backend-api:8080
# }
EOFKhi dùng thật, thay example.com và tên container bằng domain và ứng dụng của bạn.
Tạo docker-compose.yml cho Caddy
File này định nghĩa container Caddy, các cổng (ports) cần mở và các thư mục cần lưu trữ (volumes).
Volume caddy_data rất quan trọng: nó giữ chứng chỉ SSL qua mỗi lần khởi động lại.
Mất volume này, Caddy phải xin lại chứng chỉ và dễ dính rate limit của Let's Encrypt.
cat > ~/services/caddy/docker-compose.yml << 'EOF'
services:
caddy:
image: caddy:2-alpine
container_name: caddy-proxy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp" # Bắt buộc mở UDP port 443 nếu muốn hỗ trợ HTTP/3
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
networks:
- caddy-net
volumes:
caddy_data:
caddy_config:
networks:
caddy-net:
external: true
EOFKhởi động Caddy
Trước khi bật Caddy, hãy chắc chắn VPS không có dịch vụ nào khác đang chiếm cổng 80
và 443 (ví dụ Nginx, Apache hay một proxy cũ).
Nếu đang chạy Nginx Proxy Manager, tắt nó để giải phóng port:
docker stop nginx-proxy-manager-app-1Khởi động Caddy:
cd ~/services/caddy
docker compose up -dĐể kiểm tra Caddy đã chạy ổn và đã xin được SSL thành công chưa, xem logs:
docker compose logs -fKhi thấy log báo certificate obtained successfully thì bấm Ctrl+C để thoát.
2. Thêm một ứng dụng / domain mới
Mỗi khi có một ứng dụng mới (ví dụ một dịch vụ Docker chạy trên port 3000), bạn chỉ cần làm đúng 3 bước sau.
Bước 1: Nối container mới vào network của Caddy
Vì cả Caddy và ứng dụng đều chạy trong Docker, cách thiết lập đúng nhất (best
practice) là cho ứng dụng tự động join vào network của Caddy (caddy-net) ngay trong
file docker-compose.yml.
Cách 1 (khuyến nghị): khai báo trong docker-compose
Thêm đoạn cấu hình networks này vào file docker-compose.yml của ứng dụng mới:
services:
my-new-app:
# ... (các cấu hình image, ports...)
networks:
- caddy-net
networks:
caddy-net:
external: trueCờ external: true báo cho Docker biết network caddy-net đã tồn tại sẵn bên ngoài
(đã tạo ở phần 1). Khi chạy docker compose up -d, container sẽ tự động gia nhập
network này. Nhờ vậy ứng dụng mang sang VPS nào cũng tự chạy đúng mà không cần gõ lệnh
thủ công.
Cách 2 (tạm thời): gắn network thủ công bằng lệnh
Nếu ứng dụng đã chạy và bạn chưa thể sửa docker-compose ngay, chạy:
docker network connect caddy-net my-new-appCách này chỉ là tạm thời: container sẽ mất kết nối tới network nếu bị tạo lại hoặc rebuild.
Bước 2: Khai báo domain trong Caddyfile
Mở Caddyfile:
nano ~/services/caddy/CaddyfileThêm khối cấu hình mới xuống cuối file:
app.example.com {
reverse_proxy my-new-app:3000
}Bước 3: Reload Caddy (zero downtime)
Caddy hỗ trợ reload file cấu hình mà không cần khởi động lại (restart) container. Người dùng đang truy cập các site khác đi qua cùng Caddy sẽ không hề hay biết và không bị rớt kết nối.
docker exec caddy-proxy caddy reload --config /etc/caddy/CaddyfileXong. Khi chạy lệnh này, Caddy tự nhận diện domain mới app.example.com, tự đi xin
chứng chỉ SSL từ Let's Encrypt và bắt đầu chuyển tiếp request ngay lập tức.
Quy tắc quan trọng: không dùng localhost trong reverse_proxy
Caddy đang chạy trong một container Docker riêng, nên localhost trong Caddyfile là
chính container Caddy, KHÔNG phải VPS của bạn.
- KHÔNG dùng
reverse_proxy localhost:3000. Lúc nàylocalhostkhông phải là máy VPS mà là bản thân container chạy Caddy. Caddy sẽ tìm dịch vụ ở port 3000 bên trong chính container Caddy, dẫn đến lỗi502 Bad Gateway. - Cách dùng đúng:
reverse_proxy <tên-container>:3000. Khi Caddy và app đã chung networkcaddy-net, Docker có sẵn DNS nội bộ. Bạn chỉ cần dùng đúngcontainer_namekhai báo trongdocker-compose.ymlcủa app làm hostname trỏ tới, ví dụreverse_proxy my-new-app:3000.
Lưu ý
| Triệu chứng | Cách xử lý |
|---|---|
502 Bad Gateway dù app đang chạy | Caddyfile đang trỏ localhost, hoặc app chưa join caddy-net - dùng reverse_proxy <tên-container>:<port> và kiểm tra lại network |
| Caddy không khởi động được vì port 80/443 đã bị chiếm | Tắt Nginx, Apache hoặc proxy cũ (ví dụ docker stop nginx-proxy-manager-app-1) rồi chạy lại docker compose up -d |
| App mất kết nối với Caddy sau khi rebuild | Network được gắn bằng docker network connect - chuyển sang khai báo networks trong docker-compose.yml của app |
| Bị rate limit khi xin chứng chỉ SSL | Đừng xóa volume caddy_data; nó giữ chứng chỉ qua các lần khởi động lại |
Muốn người dùng thấy một trang bảo trì tử tế thay vì lỗi 502 mỗi lần deploy? Xem tiếp Trang bảo trì tự động với Caddy.