6 min read

在 k3s 上部署 Vaultwarden,並用 Cloudflare Tunnel 安全對外(不開 Port、不套 Access)

在 k3s 上部署 Vaultwarden,並用 Cloudflare Tunnel 安全對外(不開 Port、不套 Access)
Photo by Nicolas Savignat / Unsplash

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/302
    • curl -I http://vault.example.tld/301/308Location: 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)

流量路徑圖

vaultwarden_flow.png


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
  • cloudflared connector 已在 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(名稱可能略有差異):

  • NetworksTunnels
  • 選你的 Tunnel
  • Public HostnamesAdd a public hostname

設定:

  • Hostnamevault.example.tld
  • Servicehttp://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 RulesRedirect 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) 建立第一個帳號後,立刻關閉註冊

  1. 用瀏覽器開 https://vault.example.tld 建立第一個帳號
  2. 建完後立刻關註冊:
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

回滾(最小可回復)

由外到內:

  1. Cloudflare:刪掉 Tunnel 的 vault.example.tld Public Hostname(外部立即不可用)
  2. Cloudflare:刪掉 Redirect Rule(恢復 HTTP 行為)
  3. k3s:下線 Vaultwarden(保留資料)
kubectl delete -f vaultwarden-app.yaml
  1. 若要連資料一起清(不可逆風險高,請先備份)
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),並建立備份習慣。