2026-03-2010 phút đọcVI
- 1.Tại sao cần Secret Manager? — Vượt ra ngoài .env files
- 2.Tổng quan Vaultwarden — Tương thích Bitwarden, chạy bằng Rust
- 3.Cài đặt Docker với bảo mật nâng cao cho Vaultwarden(bài này)
- 4.Cloudflare Tunnel và Zero Trust Access — Truy cập bảo mật không cần mở port
- 8.Backup và Disaster Recovery — Vault là sinh mệnh của bạn
Cài đặt Vaultwarden bằng Docker với bảo mật nâng cao
Mở đầu — Tại sao Docker là phương pháp deploy ưa chuộng
Khi triển khai Vaultwarden — hay bất kỳ dịch vụ self-hosted nào — bạn có ba lựa chọn phổ biến: cài đặt trực tiếp lên hệ điều hành (bare metal), sử dụng máy ảo (VM), hoặc chạy trong container (Docker). Mỗi cách đều có ưu nhược điểm riêng, nhưng Docker đã trở thành phương pháp được cộng đồng Vaultwarden khuyến nghị vì một số lý do quan trọng.
Tính nhất quán môi trường: Docker image đóng gói toàn bộ dependencies, đảm bảo Vaultwarden chạy giống nhau trên mọi máy chủ — dù đó là Raspberry Pi, VPS Ubuntu hay NAS Synology.
Cách ly tiến trình: Container chạy trong namespace riêng biệt, hạn chế khả năng một lỗ hổng trong Vaultwarden ảnh hưởng đến toàn bộ hệ thống host.
Cập nhật và rollback dễ dàng: Chỉ cần pull image mới và restart container. Nếu có vấn đề, rollback về image cũ trong vài giây.
Infrastructure as Code: Docker Compose cho phép bạn mô tả toàn bộ cấu hình dưới dạng file YAML, dễ dàng version control, review, và tái tạo.
Tuy nhiên, Docker mặc định không tự động bảo mật mọi thứ. Một container chạy với cấu hình mặc định vẫn có thể bị khai thác nếu không được hardening đúng cách. Bài viết này sẽ hướng dẫn bạn từ cấu hình cơ bản đến production-ready, với từng lớp bảo mật được giải thích rõ ràng.
Docker Compose cơ bản — Khởi đầu tối giản
Trước khi đi vào hardening, hãy xem một file Docker Compose tối giản để hiểu cấu trúc:
# docker-compose.yml — phiên bản tối giản (KHÔNG dùng cho production)
services:
vaultwarden:
image: vaultwarden/server:1.32.5
container_name: vaultwarden
restart: unless-stopped
ports:
- "8080:80"
volumes:
- vw-data:/data
environment:
DOMAIN: "https://vault.example.com"
volumes:
vw-data:Giải thích từng trường:
image: Docker image từ Docker Hub.vaultwarden/serverlà image chính thức của dự án.container_name: Tên cố định cho container, giúp quản lý dễ hơn so với tên ngẫu nhiên Docker tự đặt.restart: unless-stopped: Container tự khởi động lại khi crash hoặc khi Docker daemon restart, trừ khi bạn chủ động stop nó.ports: Ánh xạ port 8080 trên host vào port 80 trong container. Lưu ý: cấu hình này expose ra tất cả network interfaces — một rủi ro bảo mật.volumes: Mount named volumevw-datavào/datatrong container — nơi Vaultwarden lưu database SQLite, attachments, và các file cấu hình.DOMAIN: URL đầy đủ mà người dùng sẽ truy cập. Vaultwarden cần biết domain để tạo đúng các URL trong email mời, icon favicon, và WebSocket.
Cấu hình trên hoạt động nhưng còn nhiều lỗ hổng bảo mật. Hãy cùng khắc phục từng lớp.
Security hardening layers — Chín lớp bảo vệ
1. Filesystem bất biến — read_only: true
read_only: true
tmpfs:
- /tmpTại sao quan trọng? Mặc định, container có quyền ghi vào toàn bộ filesystem bên trong nó. Nếu kẻ tấn công khai thác được một lỗ hổng (ví dụ: Remote Code Execution), chúng có thể ghi file độc hại, cài backdoor, hoặc sửa đổi binary của ứng dụng.
Khi bật read_only: true, toàn bộ filesystem trở thành chỉ đọc. Container chỉ có thể ghi vào các volumes đã mount và các đường dẫn tmpfs. Điều này có nghĩa:
- Kẻ tấn công không thể tạo file script mới trong container.
- Không thể sửa đổi binary của Vaultwarden.
- Không thể cài đặt thêm công cụ tấn công.
Tại sao cần tmpfs cho /tmp? Nhiều ứng dụng cần ghi tạm vào /tmp trong quá trình hoạt động (ví dụ: xử lý upload, session tạm). tmpfs tạo một filesystem trên RAM, không lưu xuống disk, và tự động xóa khi container restart — vừa đảm bảo hoạt động, vừa không lưu trữ dữ liệu nhạy cảm.
2. Drop tất cả Linux capabilities — cap_drop: ALL
cap_drop:
- ALLLinux capabilities là gì? Truyền thống, Linux phân biệt hai cấp quyền: root (toàn quyền) và non-root (hạn chế). Capabilities chia nhỏ quyền root thành khoảng 40 quyền riêng biệt — ví dụ NET_BIND_SERVICE (bind port < 1024), SYS_ADMIN (mount filesystem), NET_RAW (gửi raw packet).
Mặc định, Docker cấp cho container một tập hợp capabilities hạn chế nhưng vẫn rộng hơn mức cần thiết. cap_drop: ALL loại bỏ tất cả, đưa container về trạng thái quyền tối thiểu.
Vaultwarden có cần capability nào không? Trong hầu hết các cấu hình, không. Vaultwarden chạy web server trên port 80 (trong container), nhưng vì nó chạy dưới user non-root trong container và port mapping được Docker xử lý ở tầng host, nên không cần NET_BIND_SERVICE. Nếu bạn gặp lỗi permission denied sau khi drop tất cả, hãy thêm lại đúng một capability cần thiết thay vì bỏ cap_drop.
3. Chống leo thang đặc quyền — security_opt: no-new-privileges
security_opt:
- no-new-privileges:truePrivilege escalation là gì? Đây là kỹ thuật mà kẻ tấn công, sau khi chiếm được quyền truy cập cấp thấp, tìm cách nâng cấp lên quyền cao hơn (thường là root). Trên Linux, điều này có thể xảy ra qua setuid binaries, capabilities được gán cho file, hoặc lỗi kernel.
no-new-privileges ngăn chặn mọi tiến trình trong container có thêm quyền mới sau khi đã khởi chạy. Cụ thể:
- Setuid/setgid bit trên binary bị bỏ qua.
- Không thể gọi
execve()với binary có capabilities đặc biệt. - Tiến trình con không thể có nhiều quyền hơn tiến trình cha.
Đây là một trong những hardening có tỷ lệ "hiệu quả / chi phí" cao nhất — gần như không bao giờ gây vấn đề tương thích nhưng chặn được một lớp tấn công quan trọng.
4. Bind chỉ trên localhost — 127.0.0.1:PORT
ports:
- "127.0.0.1:8080:80"Sự khác biệt với 8080:80: Khi viết 8080:80 (không chỉ định IP), Docker bind port 8080 trên tất cả network interfaces — bao gồm cả IP public. Điều này có nghĩa bất kỳ ai biết IP máy chủ đều có thể truy cập Vaultwarden trực tiếp, bỏ qua mọi lớp bảo vệ phía trước.
Khi chỉ định 127.0.0.1:8080:80, port chỉ lắng nghe trên localhost. Để truy cập từ bên ngoài, traffic bắt buộc phải đi qua reverse proxy (Nginx, Caddy) hoặc tunnel (Cloudflare Tunnel) — nơi bạn có thể áp dụng SSL/TLS, rate limiting, và authentication bổ sung.
Lưu ý quan trọng: Docker có thể bypass iptables rules trên Linux. Nếu bạn dùng UFW (firewall phổ biến trên Ubuntu), việc mở port trong Docker có thể tạo rule iptables ưu tiên cao hơn UFW. Bind trên 127.0.0.1 là cách đáng tin cậy nhất để đảm bảo port không bị expose ngoài ý muốn.
5. Giới hạn tài nguyên — Resource limits
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
cpus: "0.25"
memory: 128MTại sao cần giới hạn? Nếu không đặt limits, một container có thể tiêu thụ toàn bộ CPU và RAM của host — dù do lỗi ứng dụng, memory leak, hay tấn công DoS. Điều này ảnh hưởng đến mọi dịch vụ khác trên cùng máy chủ.
limits: Ngưỡng tối đa. Container sẽ bị throttle CPU hoặc OOM-killed nếu vượt memory limit.reservations: Mức tối thiểu Docker đảm bảo cấp cho container. Hữu ích khi chạy nhiều container trên cùng host.
Khuyến nghị cho Vaultwarden: Vaultwarden rất nhẹ. Với dưới 50 người dùng, 512MB RAM và 1 CPU là dư thừa. Bạn có thể giảm xuống 256MB RAM nếu cần. Theo dõi docker stats trong vài ngày để tìm mức phù hợp.
6. Named volumes — Dữ liệu bền vững, sao lưu thuận tiện
volumes:
vw-data:
driver: localTại sao không dùng bind mount (./data:/data)? Bind mount gắn trực tiếp một thư mục trên host vào container. Tuy đơn giản, nó có nhược điểm:
- Phụ thuộc vào cấu trúc thư mục cụ thể trên host.
- Quyền file có thể conflict giữa host user và container user.
- Khó quản lý khi số lượng container tăng lên.
Named volumes do Docker quản lý, lưu tại /var/lib/docker/volumes/. Ưu điểm:
- Portable giữa các host.
- Docker xử lý permissions tự động.
- Dễ backup:
docker run --rm -v vw-data:/data -v $(pwd):/backup alpine tar czf /backup/vw-backup.tar.gz /data. - Dễ migrate: copy volume data sang host mới.
7. Health checks — Giám sát tình trạng container
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/alive"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20sHealth check làm gì? Docker định kỳ chạy lệnh test bên trong container. Nếu lệnh trả về exit code 0, container được đánh dấu healthy. Nếu thất bại liên tiếp (theo số retries), container chuyển sang unhealthy.
interval: Khoảng cách giữa các lần check. 30 giây là hợp lý — đủ nhanh để phát hiện vấn đề, không gây tải.timeout: Thời gian chờ tối đa cho mỗi lần check. Nếu Vaultwarden mất hơn 10 giây để respond, có vấn đề.retries: Số lần thất bại liên tiếp trước khi đánh dấu unhealthy. 3 lần giúp tránh false positive do network blip.start_period: Thời gian grace period sau khi container start. Trong giai đoạn này, health check failures không được tính.
Lưu ý: Health check mặc định không tự restart container. Bạn cần kết hợp với restart: unless-stopped hoặc dùng Docker Swarm / orchestrator để tự động restart container unhealthy. Ngoài ra, bạn có thể tích hợp với monitoring (Uptime Kuma, Prometheus) để nhận cảnh báo.
8. Log rotation — Ngăn log chiếm hết disk
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"Vấn đề: Mặc định, Docker lưu log dưới dạng JSON file không giới hạn kích thước. Theo thời gian, log có thể phình to đến hàng GB, lấp đầy disk và gây crash toàn bộ hệ thống.
max-size: 10m: Mỗi file log tối đa 10MB.max-file: 3: Giữ tối đa 3 file log. Khi file thứ 3 đầy, Docker tự xóa file cũ nhất.
Tổng dung lượng log tối đa: 10MB x 3 = 30MB. Đủ để debug khi cần, không gây rủi ro cho disk.
9. Image pinning — Tại sao không dùng :latest
image: vaultwarden/server:1.32.5 # Pin phiên bản cụ thể
# KHÔNG: image: vaultwarden/server:latestRủi ro của :latest: Tag latest trỏ đến phiên bản mới nhất tại thời điểm pull. Điều này có nghĩa:
- Hai lần
docker compose pullkhác nhau có thể tải về image khác nhau. - Bạn không biết chính xác phiên bản nào đang chạy.
- Breaking change có thể vô tình được áp dụng.
- Không thể rollback chính xác nếu có vấn đề.
Pin phiên bản cụ thể đảm bảo:
- Reproducibility — bạn luôn biết chính xác phiên bản đang chạy.
- Cập nhật có chủ đích — bạn đọc changelog, test, rồi mới đổi version.
- Rollback rõ ràng — chỉ cần đổi lại số version cũ.
Docker Compose hoàn chỉnh — Production-ready
Tổng hợp tất cả các lớp bảo mật vào một file duy nhất:
# docker-compose.yml — Vaultwarden production-ready
# Phiên bản: 2026-03-20
services:
vaultwarden:
image: vaultwarden/server:1.32.5
container_name: vaultwarden
restart: unless-stopped
# --- Security hardening ---
read_only: true
tmpfs:
- /tmp
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
# --- Network: chỉ bind localhost ---
ports:
- "127.0.0.1:8080:80"
- "127.0.0.1:3012:3012" # WebSocket notifications
# --- Resource limits ---
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
cpus: "0.25"
memory: 128M
# --- Persistent storage ---
volumes:
- vw-data:/data
- ./secrets/admin-token:/run/secrets/admin-token:ro
# --- Health monitoring ---
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/alive"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
# --- Log rotation ---
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# --- Environment configuration ---
environment:
# Core
DOMAIN: "https://vault.example.com"
WEBSOCKET_ENABLED: "true"
# Registration control
SIGNUPS_ALLOWED: "false"
INVITATIONS_ALLOWED: "true"
# Admin panel — file-based token
ADMIN_TOKEN_FILE: "/run/secrets/admin-token"
# Rate limiting
LOGIN_RATELIMIT_MAX_BURST: 5
LOGIN_RATELIMIT_SECONDS: 60
ADMIN_RATELIMIT_MAX_BURST: 3
ADMIN_RATELIMIT_SECONDS: 60
# Feature toggles
SENDS_ALLOWED: "true"
EMERGENCY_ACCESS_ALLOWED: "true"
ORG_CREATION_USERS: "admin@example.com"
# Logging
LOG_LEVEL: "warn"
EXTENDED_LOGGING: "true"
# --- Network ---
networks:
- vaultwarden-net
volumes:
vw-data:
driver: local
networks:
vaultwarden-net:
driver: bridgeBiến môi trường quan trọng
DOMAIN
DOMAIN=https://vault.example.com
URL đầy đủ bao gồm protocol (https://). Vaultwarden sử dụng giá trị này để tạo link trong email, WebSocket URL, và icon service. Bắt buộc phải đúng — nếu sai, client sẽ không kết nối được hoặc các tính năng như email verification không hoạt động.
SIGNUPS_ALLOWED
SIGNUPS_ALLOWED=false
Sau khi bạn đã tạo tài khoản đầu tiên, tắt đăng ký mở ngay lập tức. Đây là sai lầm phổ biến nhất — nhiều người quên tắt và bất kỳ ai biết URL đều có thể tạo tài khoản.
Thay vào đó, dùng INVITATIONS_ALLOWED=true — chỉ admin mới có thể mời người dùng mới qua email.
ADMIN_TOKEN_FILE vs ADMIN_TOKEN
Cách truyền thống (KHÔNG khuyến nghị):
ADMIN_TOKEN=argon2_hash_here
Vấn đề: Token nằm trực tiếp trong environment variable. Bất kỳ ai có quyền chạy docker inspect vaultwarden đều thấy token dưới dạng plain text. Process listing (/proc/*/environ) cũng có thể tiết lộ giá trị.
Cách file-based (khuyến nghị):
ADMIN_TOKEN_FILE=/run/secrets/admin-token
Token được lưu trong file riêng, mount vào container dưới dạng read-only. Ưu điểm:
docker inspectkhông hiển thị nội dung file.- File có thể được bảo vệ bằng quyền filesystem (chmod 600).
- Dễ rotate: thay đổi nội dung file và restart container.
- Tương thích với Docker Secrets nếu bạn dùng Docker Swarm.
Cách tạo admin token:
# Tạo thư mục secrets
mkdir -p ./secrets
# Tạo Argon2 hash cho admin token
# Cách 1: Dùng vaultwarden built-in
docker run --rm vaultwarden/server:1.32.5 /vaultwarden hash --preset owasp
# Cách 2: Dùng argon2 CLI
echo -n "your-strong-passphrase" | argon2 "$(openssl rand -base64 32)" -id -e
# Lưu hash vào file
echo 'GENERATED_HASH_HERE' > ./secrets/admin-token
chmod 600 ./secrets/admin-tokenWebSocket notifications
WEBSOCKET_ENABLED=true
WebSocket cho phép Vaultwarden push thông báo real-time đến client (ví dụ: khi password được sync từ thiết bị khác). Không bật cũng hoạt động, nhưng client phải poll định kỳ — gây delay và tăng tải. Port WebSocket mặc định là 3012.
Rate limiting
LOGIN_RATELIMIT_MAX_BURST=5
LOGIN_RATELIMIT_SECONDS=60
ADMIN_RATELIMIT_MAX_BURST=3
ADMIN_RATELIMIT_SECONDS=60
Giới hạn số lần đăng nhập thất bại: tối đa 5 lần trong 60 giây cho user login, 3 lần trong 60 giây cho admin panel. Đây là tuyến phòng thủ quan trọng chống brute-force.
Feature toggles
SENDS_ALLOWED=true # Bitwarden Send (chia sẻ tạm thời)
EMERGENCY_ACCESS_ALLOWED=true # Truy cập khẩn cấp
ORG_CREATION_USERS=admin@example.com # Chỉ admin tạo Organization
Nguyên tắc: bật tính năng cần, tắt tính năng không dùng. Mỗi tính năng bật thêm là một bề mặt tấn công (attack surface) tiềm tàng.
Network considerations
Bridge network
networks:
vaultwarden-net:
driver: bridgeDocker bridge network tạo một mạng ảo riêng biệt cho các container. Ưu điểm:
- Cách ly: Container trong network khác không thể truy cập Vaultwarden.
- DNS resolution: Các container trong cùng network có thể truy cập nhau bằng tên container (ví dụ:
vaultwarden:80từ container reverse proxy). - Kiểm soát: Bạn quyết định container nào được join vào network nào.
Firewall rules
Nếu chạy trên Linux với firewall (iptables/nftables/UFW):
# Kiểm tra Docker có bypass firewall không
sudo iptables -L DOCKER-USER -n -v
# Nếu dùng UFW, thêm rule cho Docker
# File: /etc/ufw/after.rules (thêm trước *filter)
*filter
:DOCKER-USER - [0:0]
-A DOCKER-USER -j RETURN
COMMITLưu ý: Vì đã bind 127.0.0.1, Docker không tạo rule forwarding cho port 8080. Đây là lý do 127.0.0.1 binding là lớp bảo vệ đáng tin cậy nhất — nó hoạt động độc lập với firewall configuration.
Tóm tắt
Trong bài này, bạn đã đi qua:
- Docker Compose cơ bản — cấu trúc và ý nghĩa từng trường.
- Chín lớp hardening — từ filesystem bất biến đến image pinning, mỗi lớp giải quyết một vector tấn công cụ thể.
- Biến môi trường quan trọng — cách cấu hình domain, tắt đăng ký, bảo vệ admin token, rate limiting.
- ADMIN_TOKEN_FILE — tại sao file-based token an toàn hơn environment variable.
- Network và firewall — cách cách ly container và tránh Docker bypass firewall.
Kết quả: Bạn có một file Docker Compose production-ready, có thể deploy ngay với mức bảo mật vượt xa cấu hình mặc định.