在 k3s 上部署 Vaultwarden,並用 Cloudflare Tunnel 安全對外(不開 Port、不套 Access)
What:
在 k3s(單節點)部署 Vaultwarden,並透過 Cloudflare Tunnel 將服務以 HTTPS 對外提供(
https://vault.example.tld)。
Why:
不做路由器 port forwarding;使用 Cloudflare Tunnel 作為唯一對外入口。同時在 Vaultwarden 端關閉註冊與附件,降低暴露面與寫入量(對 microSD/家用環境更友善)。
Result:
手機 App / 瀏覽器外掛可正常使用的自架密碼庫入口(外網 → Cloudflare → Tunnel → k3s Service → Pod),且具備最小可回滾方案。
說明:本文以「新手可照做」為主;所有敏感資訊以
example.tld、/srv/...等 placeholder 表示,請自行替換。
範圍與驗收標準(Scope & DoD)
範圍
- Kubernetes:k3s 單節點
- 對外:Cloudflare Tunnel(不開 inbound port)
- URL:
vault.example.tld - 部署方式:YAML manifests(不使用 Helm)
- 安全策略:Vaultwarden 不套 Cloudflare Access(原因見後文)
完成定義(DoD)
kubectl -n vaultwarden get pod:Vaultwarden Pod 為Running- 內部探測:
curl -I http://vaultwarden:80/回200/302 - 外部可用:
curl -I https://vault.example.tld/回200/302curl -I http://vault.example.tld/回301/308並Location: https://...
- 註冊關閉:
SIGNUPS_ALLOWED=false,且註冊請求會被拒絕 - 附件關閉:
ATTACHMENTS_ENABLED=false - (可選)手機 App 設定
Server URL=https://vault.example.tld可登入同步
架構概覽(不開 inbound port 的核心)
資料路徑(簡化):
- Client(外網)→
https://vault.example.tld - Cloudflare Edge(DNS + TLS)
- Cloudflare Tunnel
cloudflared(跑在 k3s 內)- Kubernetes Service(ClusterIP)→ Pod(Vaultwarden)
流量路徑圖

0) 變數(先統一命名,後面直接替換)
- 對外網域:
vault.example.tld - Vaultwarden namespace:
vaultwarden - Vaultwarden service:
vaultwarden.vaultwarden.svc.cluster.local:80 - 資料落盤(hostPath):
/srv/k8s-data/vaultwarden - 備份輸出:
/srv/backups/vaultwarden/
前置條件(Prerequisites)
1) k3s 已可用
kubectl get nodes
kubectl get pods -A | head -n 80
2) Cloudflare Tunnel 已可用
你需要已經:
- 網域已加到 Cloudflare(DNS 在 Cloudflare)
- 已建立 Tunnel
cloudflaredconnector 已在 k3s 內跑起來(且 Tunnel 狀態 Healthy)
若你之前已成功用 Tunnel 對外開過其他服務,通常代表這步已完成。
為什麼 Vaultwarden 不套 Cloudflare Access(重要)
Cloudflare Access 的典型流程是「互動式登入(SSO)」;但 Vaultwarden 常見用戶端包含:
- 手機 App
- 瀏覽器 extension
- Desktop client
這些 client 往往不會像瀏覽器那樣處理「被導到登入頁 → 完成 OAuth → 回來」流程,容易造成:
- 一直 302 重導、無法登入
- 或登入流程卡住
本文採用的最小可用策略
vault.example.tld:不套 Access(client 直連)- 強化靠:
- Vaultwarden 帳密 + 2FA(建議必開)
- Cloudflare TLS
- 關閉註冊、關閉附件
-(選配)Cloudflare WAF / rate limit 規則(看你風險偏好)
若你一定要用 Access 白名單,常見做法是「分域名」:例如
vault-web.example.tld給瀏覽器(可套 Access)、vault.example.tld給 client(不套)。本文先不擴展到這套設計。
1) 準備資料目錄(建議落在 /srv,避免 microSD 長期寫入)
sudo mkdir -p /srv/k8s-data/vaultwarden
sudo chmod 700 /srv/k8s-data/vaultwarden
2) 建立 PV/PVC(hostPath → /srv)
設計重點:刪掉 Deployment/Service 不會刪資料(PV 設
Retain)。
cat > vaultwarden-storage.yaml <<'YAML'
apiVersion: v1
kind: Namespace
metadata:
name: vaultwarden
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-vaultwarden-data
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath:
path: /srv/k8s-data/vaultwarden
type: Directory
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-vaultwarden-data
namespace: vaultwarden
spec:
accessModes:
- ReadWriteOnce
storageClassName: manual
volumeName: pv-vaultwarden-data
resources:
requests:
storage: 2Gi
YAML
kubectl apply -f vaultwarden-storage.yaml
kubectl -n vaultwarden get pvc
kubectl get pv pv-vaultwarden-data
3) 部署 Vaultwarden(不開 /admin、不開附件、先短暫開註冊)
3.1 建 Secret + Deployment + Service
image: ...:latest方便起步;長期建議改成固定版本 tag,避免不可預期升級。
cat > vaultwarden-app.yaml <<'YAML'
apiVersion: v1
kind: Secret
metadata:
name: vaultwarden-secret
namespace: vaultwarden
type: Opaque
stringData:
DOMAIN: "https://vault.example.tld"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: vaultwarden
namespace: vaultwarden
spec:
replicas: 1
selector:
matchLabels:
app: vaultwarden
template:
metadata:
labels:
app: vaultwarden
spec:
containers:
- name: vaultwarden
image: vaultwarden/server:latest
ports:
- name: http
containerPort: 80
env:
- name: DOMAIN
valueFrom:
secretKeyRef:
name: vaultwarden-secret
key: DOMAIN
# 建完第一個帳號後要關掉
- name: SIGNUPS_ALLOWED
value: "true"
# 新手/家用:先關附件,降低寫入與風險
- name: ATTACHMENTS_ENABLED
value: "false"
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: pvc-vaultwarden-data
---
apiVersion: v1
kind: Service
metadata:
name: vaultwarden
namespace: vaultwarden
spec:
selector:
app: vaultwarden
ports:
- name: http
port: 80
targetPort: 80
YAML
kubectl apply -f vaultwarden-app.yaml
kubectl -n vaultwarden get pods -w
3.2 內部測通(不經 Cloudflare)
kubectl -n vaultwarden run -it --rm curl --image=curlimages/curl --restart=Never -- \
curl -sS -I http://vaultwarden:80/ | head -n 10
4) Cloudflare Tunnel:建立對外 hostname → 指到 k3s Service
在 Cloudflare Zero Trust Dashboard(名稱可能略有差異):
Networks→Tunnels- 選你的 Tunnel
Public Hostnames→Add a public hostname
設定:
- Hostname:
vault.example.tld - Service:
http://vaultwarden.vaultwarden.svc.cluster.local:80
這裡的
Service是 Cloudflare → 內網 origin 的協議,用 HTTP 正常;對外仍是 HTTPS。
5) 強制 HTTPS(HTTP → HTTPS Redirect)
Vaultwarden UI 可能會提示「請使用 https」。做法是把 http://vault.example.tld 轉到 https://vault.example.tld。
建議只針對該 hostname(影響面最小):
- Cloudflare
Rules→Redirect Rules(或 Single Redirect) - Request URL:
http://vault.example.tld/* - Target URL:
https://vault.example.tld/${1} - Status code:
301 - Preserve query string:勾選
驗證:
curl -I http://vault.example.tld/ | head -n 10
curl -I https://vault.example.tld/ | head -n 20
6) 建立第一個帳號後,立刻關閉註冊
- 用瀏覽器開
https://vault.example.tld建立第一個帳號 - 建完後立刻關註冊:
kubectl -n vaultwarden set env deploy/vaultwarden SIGNUPS_ALLOWED="false"
kubectl -n vaultwarden rollout status deploy/vaultwarden
確認:
kubectl -n vaultwarden exec deploy/vaultwarden -- printenv | egrep 'DOMAIN|SIGNUPS_ALLOWED|ATTACHMENTS_ENABLED'
註:前端可能仍看得到
Create account按鈕,但後端會拒絕註冊請求(屬正常現象)。
7) 手機 App 設定(Bitwarden client)
- Server URL:
https://vault.example.tld - 登入後確認能同步(sync)
驗收清單(簡短)
kubectl -n vaultwarden get deploy,po,svc -o wide
kubectl -n vaultwarden run -it --rm curl --image=curlimages/curl --restart=Never -- \
curl -sS -I http://vaultwarden:80/ | head -n 10
curl -I https://vault.example.tld/ | head -n 20
curl -I http://vault.example.tld/ | head -n 10
kubectl -n vaultwarden exec deploy/vaultwarden -- printenv | egrep 'SIGNUPS_ALLOWED|ATTACHMENTS_ENABLED'
備份(簡單提)
把
/data打包出來(含 SQLite/設定)。建議至少每週一份,並放到不同主機/不同硬碟才算真正的備援。
mkdir -p /srv/backups/vaultwarden
kubectl -n vaultwarden exec deploy/vaultwarden -- sh -c \
'tar czf /tmp/vw-backup.tgz -C /data .'
kubectl -n vaultwarden cp deploy/vaultwarden:/tmp/vw-backup.tgz \
/srv/backups/vaultwarden/vw-backup-$(date +%F).tgz
ls -lh /srv/backups/vaultwarden | tail
回滾(最小可回復)
由外到內:
- Cloudflare:刪掉 Tunnel 的
vault.example.tldPublic Hostname(外部立即不可用) - Cloudflare:刪掉 Redirect Rule(恢復 HTTP 行為)
- k3s:下線 Vaultwarden(保留資料)
kubectl delete -f vaultwarden-app.yaml
- 若要連資料一起清(不可逆風險高,請先備份)
kubectl delete -f vaultwarden-storage.yaml
常見問題(Troubleshooting)
1) Could not resolve host
- DNS 沒建立、或 hostname 拼字錯(最常見)。
2) 手機 App / extension 無法登入、一直 302
- 檢查 Cloudflare Access:
vault.example.tld不應被 Access 保護(避免互動式登入頁介入)。
3) microSD 長期風險
- 儘量把
/data落在 SSD/NAS 掛載點(例如/srv),並建立備份習慣。