Skip to content

SFTP on Kubernetes 部署

在 Kubernetes 中部署 SFTP 服務的兩種方案:輕量的 atmoz/sftp 與功能完整的 SFTPGo(推薦)。

概述

在 Kubernetes 環境中提供 SFTP 服務,有兩種主流選擇。atmoz/sftp 是一個輕量級映像,設定方式直覺,但有一個重要限制:每個使用者必須掛載獨立的 subPath,無法統一掛載在 home 目錄根目錄,對多使用者管理較不便。

SFTPGo 是功能更完整的解決方案,支援 Web 管理介面、多使用者管理、虛擬資料夾,並可同時搭配 Nginx 提供 HTTP 檔案瀏覽服務,是實際部署的推薦選擇。兩個方案都使用標準的 SFTP 協定(Port 22 或 2022),可透過 NodePort 或 Azure Internal LoadBalancer 對外暴露服務。

核心內容

方案比較

特性atmoz/sftpSFTPGo
映像atmoz/sftpdrakkan/sftpgo
Web 管理介面有(Port 8080)
使用者管理ConfigMap 靜態設定Web UI 動態管理
Volume 掛載每個使用者需獨立 subPath統一掛載,靈活分配
HTTP 檔案瀏覽不支援可搭配 Nginx Sidecar
容器權限需求需要 SYS_ADMIN capability無特殊需求
推薦程度簡單場景一般推薦

SFTPGo 目錄結構設計

SFTPGo 使用三個 subPath 分別掛載不同用途的目錄:

Volume subPath容器內路徑用途
upload/srv/sftpgo實際存放上傳檔案的目錄
home/var/lib/sftpgoSFTPGo 內部資料(使用者設定等)
config/etc/sftpgoSFTPGo 設定檔

Nginx Sidecar 掛載同一個 PVC 的 upload subPath(唯讀),提供 HTTP 瀏覽功能,與 SFTPGo 共享同一份檔案。

AKS 與本地 K8s 的部署差異

在 AKS 環境中,Service 架構有所不同:

  • Nginx 檔案瀏覽:透過 Ingress 對外暴露(HTTP/HTTPS)
  • SFTP:透過獨立的 Azure Internal LoadBalancer Service 暴露(TCP 協定)
  • WebAdmin 管理介面:同時透過 Internal LB(port 8080)及 Ingress 外部 domain + subpath(bj6.wiwynn.com/sftpgo)兩種方式存取;Ingress 方式需搭配 WEB_ROOT=/sftpgo 環境變數,讓 SFTPGo 自行處理 subpath,不需要 rewrite-target
  • 本地 K8s 通常使用 NodePort 即可應付所有服務

AKS 版本加入了時區設定(TZ=Asia/Taipei)、資源限制。最新架構已將檔案瀏覽服務從 Nginx Sidecar 改為獨立的 Caddy Deploymentsftpgo-caddy-server):

  • SFTPGo Deployment:純粹負責 SFTP 服務與 WebAdmin,不再包含任何 Sidecar
  • sftpgo-caddy-server Deployment:3 replicas + requiredDuringScheduling pod anti-affinity,確保跨節點高可用;每個 Pod 含兩個 Caddy 容器,分別服務內部(port 80)與公開存取(port 81)
  • Caddy 的 admin/metrics endpoint(port 2019/2020)透過獨立的 Internal LB Service 供 Prometheus 抓取(詳見 Caddy 靜態檔案伺服器

WebAdmin CSRF Token 問題與修復(AKS 必做)

SFTPGo 的 WebAdmin 使用 JWT + CSRF Token 進行 session 管理,在 K8s + Azure LB 環境中有兩道常見失效機制,任何一道觸發都會出現「The form token is not valid」錯誤:

機制 A:Signing Key 未固定

signing_passphrase 若未設定,每次 Pod 啟動都會產生隨機金鑰:

  • Pod 重啟 → 瀏覽器中的舊 cookie 立即失效
  • 多 replica → 不同 Pod 各有金鑰 → 跨 Pod 的 token 驗證失敗

機制 B:IP Binding(AKS 預設觸發)

SFTPGo 預設將 token 綁定到簽發時的 Source IP。AKS 預設 externalTrafficPolicy: Cluster,kube-proxy 做 SNAT 後 Pod 看到的是「轉發節點的 IP」,而非真實 client IP。Azure LB 把 GET /login 和 POST /login 分散到不同節點 → Pod 眼中是兩個不同 IP → CSRF 驗證失敗。

三項修復(建議全上):

修復設定說明
修復 1SFTPGO_HTTPD__SIGNING_PASSPHRASE(Secret)固定簽章金鑰,解決 Pod 重啟和多副本問題
修復 2SFTPGO_HTTPD__TOKEN_VALIDATION=0停用 IP 綁定檢查(0 = 不驗證 IP),K8s + LB 環境必備
修復 3Service 加 externalTrafficPolicy: LocalPod 看到真實 client IP,讓 audit log、rate limit 正確運作;單副本無副作用,多副本須搭配 pod anti-affinity

Scale-up 前置準備(HA 時才需要): SQLite on PVC 無法多副本共享,需換成外部 PostgreSQL/MySQL,並設定 SFTPGO_DATA_PROVIDER__IS_SHARED: "1" 讓 active transfers、password reset、OIDC tokens 寫入資料庫以跨副本共享狀態。

使用者 SSH Key 認證

SFTPGo 的 SSH Key 分兩類:Server Host Key(Server 身份,啟動時自動產生至 /var/lib/sftpgo/,PVC 掛載後重啟不變)與User Public Key(使用者認證,需手動設定)。

以下為使用者認證金鑰設定流程:

  1. 在用戶端機器產金鑰對(私鑰原則上只有使用者自己碰)
  2. 將公鑰上傳至 SFTPGo(WebAdmin UI 或 REST API)
  3. 測試連線

金鑰演算法建議 Ed25519;若連接方為老舊 Java ETL 或 Azure ADF 不支援 ed25519,改用 RSA-4096。

WebAdmin 進階認證設定:

  • Key + 密碼雙驗(類 2FA):Login methods 勾 publickey+password
  • 僅允許 Key 登入:password 欄位留空 + Login methods 只勾 publickey
  • 一人一把 key 才能在 audit log 中追蹤到個人操作

關鍵要點

  • SFTPGo 是推薦方案,提供 Web 管理介面和更靈活的使用者管理
  • SFTPGo 搭配 Nginx Sidecar 共享同一 PVC,可同時提供 SFTP 和 HTTP 存取
  • AKS 環境中 SFTP 服務透過 Internal LoadBalancer 而非 Ingress 暴露(TCP 協定)
  • atmoz/sftp 需要 SYS_ADMIN capability,存在安全考量
  • Ingress 需設定大 proxy-body-size(建議 1024m)以支援大檔案上傳
  • AKS 部署必須設定 signing_passphrase Secret + TOKEN_VALIDATION=0(停用 IP 綁定),否則 WebAdmin 登入必定失敗(CSRF token 驗證)
  • WebAdmin 可透過 Ingress(bj6.wiwynn.com/sftpgo)暴露,需搭配 WEB_ROOT=/sftpgo;Ingress path 不需 rewrite-target,SFTPGo 自行處理 subpath
  • externalTrafficPolicy: Local 讓 audit log 記錄真實 client IP,單副本下無副作用
  • 使用者 SSH Key 建議一人一把,並在用戶端機器產生(私鑰不離開使用者手中)

實際應用

檔案分享場景中,SFTPGo + Nginx 的組合讓使用者可以用 SFTP 客戶端上傳,再透過瀏覽器 HTTP 下載,非常適合作為內部檔案分發平台。搭配 Azure Internal LoadBalancer 固定 IP 可確保 SFTP 連線端點穩定,Ingress 則提供 HTTPS 保護的 Web 介面。另可參考類似架構的 Samba + Nginx 檔案分享服務(Kubernetes 部署)

部署設定參考

以下為實際部署時使用的完整設定,供日後查詢與複製使用。

環境參數

項目本地 K8sAKS
Namespacesftpingress-basic
SFTPGo 映像drakkan/sftpgo(latest)drakkan/sftpgo:v2.7
檔案瀏覽映像nginx:stable(Sidecar)caddy:2-alpine(獨立 Deployment,3 replicas)
SFTP Port2022 (NodePort 30022)22 → 2022 (Internal LB 10.30.196.60)
管理介面 Port8080 (NodePort 30023)8080(Internal LB 10.30.196.60)/ bj6.wiwynn.com/sftpgo(Ingress HTTPS)
檔案瀏覽 Port80 (NodePort 30024)80(內部 Ingress)/ 81(公開 Ingress)
Caddy Metrics2019/2020 (Internal LB 10.30.196.60)
PVC 大小50Gi50Gi
StorageClassnfs-clientmanaged-csi(視設定)

atmoz/sftp 完整設定

Deployment + Service

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sftp-server
  namespace: sftp
  labels:
    app: sftp-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sftp-server
  template:
    metadata:
      labels:
        app: sftp-server
    spec:
      containers:
        - name: sftp-server
          image: atmoz/sftp
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 22
          volumeMounts:
            - mountPath: /home/bj6-admin
              name: sftp-volume
              subPath: bj6-admin
            - mountPath: /etc/sftp/users.conf
              name: sftp-users
              subPath: users
          securityContext:
            capabilities:
              add: ["SYS_ADMIN"]
      volumes:
        - name: sftp-volume
          persistentVolumeClaim:
            claimName: sftp-pvc
            readOnly: false
        - name: sftp-users
          configMap:
            name: sftp-users
---
apiVersion: v1
kind: Service
metadata:
  name: sftp-server-service
  namespace: sftp
spec:
  type: NodePort
  ports:
    - port: 22
      nodePort: 30022
      targetPort: 22
      protocol: TCP
  selector:
    app: sftp-server

PVC

yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: sftp-pvc
  namespace: sftp
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: nfs-client
  resources:
    requests:
      storage: 30Gi

ConfigMap(使用者設定)

yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: sftp-users
  namespace: sftp
data:
  users: |
    bj6-admin:CMzK5uKd:1001:100:upload
    bj6-user:HupWSmHx:1002:100:upload

使用者格式:使用者名稱:密碼:UID:GID:目錄

SFTPGo 本地 K8s 設定

Deployment + Service

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sftpgo-server
  namespace: sftp
  labels:
    app: sftpgo-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sftpgo-server
  template:
    metadata:
      labels:
        app: sftpgo-server
    spec:
      containers:
        - name: sftpgo-server
          image: drakkan/sftpgo
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 2022
            - containerPort: 8080
          volumeMounts:
            - mountPath: /srv/sftpgo
              name: sftpgo-volume
              subPath: upload
            - mountPath: /var/lib/sftpgo
              name: sftpgo-volume
              subPath: home
            - mountPath: /etc/sftpgo
              name: sftpgo-volume
              subPath: config
        - name: nginx-server
          image: nginx:stable
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80
          volumeMounts:
            - mountPath: /usr/share/nginx/html
              name: sftpgo-volume
              subPath: upload
            - name: nginx-config-volume
              mountPath: /etc/nginx/conf.d/default.conf
              subPath: nginx.conf
      volumes:
        - name: sftpgo-volume
          persistentVolumeClaim:
            claimName: sftpgo-pvc
            readOnly: false
        - name: nginx-config-volume
          configMap:
            name: sftpgo-nginx-config
---
apiVersion: v1
kind: Service
metadata:
  name: sftpgo-server-service
  namespace: sftp
spec:
  type: NodePort
  ports:
    - name: sftp
      port: 2022
      nodePort: 30022
      targetPort: 2022
    - name: http
      port: 8080
      nodePort: 30023
      targetPort: 8080
    - name: nginx
      port: 80
      nodePort: 30024
      targetPort: 80
  selector:
    app: sftpgo-server

PVC

yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: sftpgo-pvc
  namespace: sftp
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: nfs-client
  resources:
    requests:
      storage: 50Gi

Nginx 設定(本地版)

nginx
server {
  listen 80;
  root   /usr/share/nginx/html;
  location / {
    autoindex on;
  }
  charset utf-8;
}

建立/更新 ConfigMap

bash
kubectl create configmap sftpgo-nginx-config --from-file=nginx.conf -n sftp -o yaml --dry-run | kubectl replace -f -

SFTPGo AKS 設定

Secret(WebAdmin Signing Passphrase)

產生隨機 passphrase:

bash
openssl rand -base64 48

建立 Secret(務必用 stringData: 而非 data: + base64,避免二次 base64 編碼問題):

yaml
apiVersion: v1
kind: Secret
metadata:
  name: sftpgo-signing
  namespace: ingress-basic
type: Opaque
stringData:
  signing-passphrase: ygdlkyPFa4Saqwvw96d9ZeXiNEAz6OURZVjC465TESA4n0/9dIexBHMZEMgKckwu

SFTPGo Deployment

SFTPGo 僅負責 SFTP 服務與 WebAdmin,不再包含 Sidecar。

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sftpgo-server
  namespace: ingress-basic
  labels:
    app: sftpgo-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sftpgo-server
  template:
    metadata:
      labels:
        app: sftpgo-server
    spec:
      containers:
        - name: sftpgo-server
          image: drakkan/sftpgo:v2.7
          imagePullPolicy: Always
          env:
            - name: TZ
              value: Asia/Taipei
            - name: SFTPGO_HTTPD__SIGNING_PASSPHRASE
              valueFrom:
                secretKeyRef:
                  name: sftpgo-signing
                  key: signing-passphrase
            - name: SFTPGO_HTTPD__TOKEN_VALIDATION
              value: "0"
            - name: SFTPGO_HTTPD__TOKEN_AUTH_EXPIRY_HOURS
              value: "24"
            - name: SFTPGO_HTTPD__WEB_ROOT
              value: "/sftpgo"
          ports:
            - containerPort: 2022
            - containerPort: 8080
          volumeMounts:
            - mountPath: /srv/sftpgo
              name: sftpgo-volume
              subPath: upload
            - mountPath: /var/lib/sftpgo
              name: sftpgo-volume
              subPath: home
            - mountPath: /etc/sftpgo
              name: sftpgo-volume
              subPath: config
          resources:
            requests:
              memory: "500Mi"
              cpu: "100m"
      volumes:
        - name: sftpgo-volume
          persistentVolumeClaim:
            claimName: sftpgo-pvc
            readOnly: false

Caddy Deployment(檔案瀏覽服務)

Caddy 獨立部署,3 replicas + requiredDuringScheduling pod anti-affinity 確保副本分散於不同節點。每個 Pod 含兩個 Caddy 容器:caddy-server(port 80,服務內部網路)與 caddy-server-public(port 81,服務公開存取)。設定檔透過 ConfigMap sftpgo-caddy-config 注入,key 為 CaddyfileCaddyfile.public

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sftpgo-caddy-server
  namespace: ingress-basic
  labels:
    app: sftpgo-caddy-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: sftpgo-caddy-server
  template:
    metadata:
      labels:
        app: sftpgo-caddy-server
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - sftpgo-caddy-server
            topologyKey: "kubernetes.io/hostname"
      containers:
        - name: caddy-server
          image: caddy:2-alpine
          imagePullPolicy: Always
          env:
            - name: TZ
              value: Asia/Taipei
          ports:
            - containerPort: 80
            - containerPort: 2019
          volumeMounts:
            - mountPath: /srv/files
              name: sftpgo-volume
              subPath: upload
              readOnly: true
            - name: caddy-config-volume
              mountPath: /etc/caddy/Caddyfile
              subPath: Caddyfile
          resources:
            requests:
              memory: "300Mi"
              cpu: "150m"
        - name: caddy-server-public
          image: caddy:2-alpine
          imagePullPolicy: Always
          env:
            - name: TZ
              value: Asia/Taipei
          ports:
            - containerPort: 81
            - containerPort: 2020
          volumeMounts:
            - mountPath: /srv/files
              name: sftpgo-volume
              subPath: upload
              readOnly: true
            - name: caddy-config-volume
              mountPath: /etc/caddy/Caddyfile
              subPath: Caddyfile.public
          resources:
            requests:
              memory: "300Mi"
              cpu: "150m"
      volumes:
        - name: sftpgo-volume
          persistentVolumeClaim:
            claimName: sftpgo-pvc
            readOnly: false
        - name: caddy-config-volume
          configMap:
            name: sftpgo-caddy-config

Caddy ConfigMap(sftpgo-caddy-config

Caddyfile.public(port 81,對應 caddy-server-public 容器,metrics port 2020)採用分層快取策略:大型壓縮檔案不快取(每次下載都是新鮮的)、前端資源短快取搭配 stale-while-revalidate、目錄瀏覽只開放 /public-updater* 路徑、其他檔案中等快取。

{
  admin 0.0.0.0:2020
  servers {
    metrics {
      per_host
    }
  }
}
:81 {
  root * /srv/files
  encode zstd gzip

  # 大型下載檔案:無快取
  @largeFiles path_regexp \.(zip|tar|gz|tgz|bz2|xz|7z|rar|exe|AppImage|deb|rpm)$
  handle @largeFiles {
    header {
      Cache-Control "public, no-cache"
    }
    file_server
  }

  # 前端動態資源:短快取 + stale-while-revalidate
  @staticAssets path_regexp \.(html|css|js|json|wasm)$
  handle @staticAssets {
    header {
      Cache-Control "public, max-age=60, stale-while-revalidate=3600"
    }
    file_server
  }

  # 目錄瀏覽:僅開放 /public-updater 路徑
  handle /public-updater* {
    header {
      Cache-Control "public, max-age=60, stale-while-revalidate=3600"
    }
    file_server browse
  }

  # 其他檔案:中等快取 + stale-while-revalidate
  handle {
    header {
      Cache-Control "public, max-age=600, stale-while-revalidate=3600"
    }
    file_server
  }
}

Caddyfile(port 80,內部 caddy-server 容器,metrics port 2019)設定尚未記錄;結構與 Caddyfile.public 相同,但 admin 改為 0.0.0.0:2019、port 改為 :80,快取策略可依內部需求調整。

建立或更新 ConfigMap:

bash
kubectl create configmap sftpgo-caddy-config \
  --from-file=Caddyfile \
  --from-file=Caddyfile.public \
  -n ingress-basic \
  -o yaml --dry-run=client | kubectl apply -f -

# 套用後重啟 Caddy Deployment
kubectl rollout restart deployment/sftpgo-caddy-server -n ingress-basic

Services(AKS)

yaml
# ClusterIP(供 Ingress 使用)— selector 指向 Caddy Deployment
apiVersion: v1
kind: Service
metadata:
  name: sftpgo-server-service
  namespace: ingress-basic
spec:
  ports:
    - name: caddy
      port: 80
      targetPort: 80
    - name: caddy-public
      port: 81
      targetPort: 81
  selector:
    app: sftpgo-caddy-server
---
# Internal LB — SFTP port 22、WebAdmin port 8080
apiVersion: v1
kind: Service
metadata:
  name: sftpgo-server-sftp-service
  namespace: ingress-basic
  annotations:
    service.beta.kubernetes.io/azure-load-balancer-internal: "true"
    service.beta.kubernetes.io/azure-load-balancer-ipv4: 10.30.196.60
spec:
  type: LoadBalancer
  externalTrafficPolicy: Local
  ports:
    - name: sftp
      port: 22
      targetPort: 2022
    - name: http
      port: 8080
      targetPort: 8080
  selector:
    app: sftpgo-server
---
# Internal LB — Caddy Prometheus metrics
apiVersion: v1
kind: Service
metadata:
  name: sftpgo-caddy-metrics-service
  namespace: ingress-basic
  annotations:
    service.beta.kubernetes.io/azure-load-balancer-internal: "true"
    service.beta.kubernetes.io/azure-load-balancer-ipv4: 10.30.196.60
spec:
  type: LoadBalancer
  ports:
    - name: private-caddy-metrics
      port: 2019
      targetPort: 2019
    - name: public-caddy-metrics
      port: 2020
      targetPort: 2020
  selector:
    app: sftpgo-caddy-server
---
# ClusterIP — SFTPGo WebAdmin(供 Ingress sftpgo-web-ingress 使用)
apiVersion: v1
kind: Service
metadata:
  name: sftpgo-web-service
  namespace: ingress-basic
spec:
  ports:
    - name: http
      port: 8080
      targetPort: 8080
  selector:
    app: sftpgo-server
Service類型說明
sftpgo-server-serviceClusterIPIngress 後端,轉發至 Caddy(port 80 內部 / 81 公開)
sftpgo-server-sftp-serviceInternal LB 10.30.196.60SFTP port 22、WebAdmin port 8080
sftpgo-caddy-metrics-serviceInternal LB 10.30.196.60Caddy Prometheus metrics port 2019/2020
sftpgo-web-serviceClusterIPIngress 後端,轉發 WebAdmin(port 8080)至 sftpgo-server

WebAdmin 部署後驗證

bash
# 確認 SFTPGo Pod 正常啟動
kubectl rollout restart deployment/sftpgo-server -n ingress-basic

# 確認 signing passphrase 環境變數被讀到(應顯示 [redacted])
kubectl logs -n ingress-basic -l app=sftpgo-server | grep -i signing

# 若登入仍失敗,查看 CSRF 相關錯誤
kubectl logs -n ingress-basic -l app=sftpgo-server --tail=100 | grep -i "csrf\|token"

套用新設定後,必須清掉瀏覽器在 10.30.196.60:8080 的全部 cookie,舊 token 不會自動失效。

SSH Key 認證設定

步驟 1:在用戶端機器產金鑰對(WSL2 / Linux / macOS)

bash
ssh-keygen -t ed25519 -C "wilson@wiwynn" -f ~/.ssh/sftpgo_wilson
chmod 600 ~/.ssh/sftpgo_wilson   # Linux/WSL 必做,權限太寬 ssh client 會拒絕載入

Windows PowerShell:

powershell
ssh-keygen -t ed25519 -C "wilson@wiwynn" -f "$env:USERPROFILE\.ssh\sftpgo_wilson"

若連接方為老舊 Java 程式或不支援 Ed25519 的 legacy 工具,改用 ssh-keygen -t rsa -b 4096

步驟 2A:WebAdmin UI 設定公鑰

  1. 登入 http://10.30.196.60:8080 → Users → 新增或編輯使用者
  2. Public keys 欄位貼入整行 .pub 內容(ssh-ed25519 AAAA...
  3. 一個使用者可放多把 key(不同裝置各自一把)

查看公鑰內容:

bash
cat ~/.ssh/sftpgo_wilson.pub

步驟 2B:REST API 批次建立使用者

bash
TOKEN=$(curl -s -u admin:yourpassword \
  http://10.30.196.60:8080/api/v2/token | jq -r .access_token)

curl -X POST http://10.30.196.60:8080/api/v2/users \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "wilson",
    "status": 1,
    "home_dir": "/srv/sftpgo/wilson",
    "permissions": {"/": ["*"]},
    "public_keys": ["ssh-ed25519 AAAAC3Nz... wilson@wiwynn"]
  }'

步驟 3:測試連線

bash
sftp -i ~/.ssh/sftpgo_wilson -P 22 wilson@10.30.196.60

WinSCP 設定:File protocol SFTP、Host 10.30.196.60、Port 22、Advanced → SSH → Authentication 選私鑰。

WinSCP 只接受 .ppk 格式,OpenSSH 私鑰需先用 PuTTYgen 轉換。

Ingress(AKS)

pathType 改為 ImplementationSpecific,移除 proxy-buffers-number / proxy-buffer-size,改用 proxy-buffering: "off"。公開 Ingress 路由至 Caddy port 81(與內部的 port 80 分離)。另新增 sftpgo-web-ingress 將 WebAdmin 透過外部 domain + subpath 暴露:此 Ingress 不使用 rewrite-target,因 SFTPGo 已透過 SFTPGO_HTTPD__WEB_ROOT=/sftpgo 自行處理 subpath,Nginx 僅需轉發路徑即可。

yaml
# 內部 Ingress(bj6.wiwynn.com)→ Caddy port 80(檔案瀏覽)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: sftpgo-server-ingress
  namespace: ingress-basic
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "1024m"
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-buffering: "off"
spec:
  ingressClassName: nginx
  tls:
    - secretName: aks-ingress-tls
  rules:
    - host: bj6.wiwynn.com
      http:
        paths:
          - pathType: ImplementationSpecific
            backend:
              service:
                name: sftpgo-server-service
                port:
                  number: 80
            path: /sftpgo-fileserver(/|$)(.*)
---
# 公開 Ingress(one.wiwynn.com)→ Caddy port 81(檔案瀏覽)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: sftpgo-server-ingress-public
  namespace: ingress-basic
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "1024m"
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-buffering: "off"
spec:
  ingressClassName: nginx
  tls:
    - secretName: aks-ingress-tls
  rules:
    - host: one.wiwynn.com
      http:
        paths:
          - pathType: ImplementationSpecific
            backend:
              service:
                name: sftpgo-server-service
                port:
                  number: 81
            path: /sftpgo-fileserver(/|$)(.*)
---
# WebAdmin Ingress(bj6.wiwynn.com/sftpgo)→ SFTPGo port 8080
# 無 rewrite-target:WEB_ROOT=/sftpgo 由 SFTPGo 自行處理 subpath
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: sftpgo-web-ingress
  namespace: ingress-basic
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "1024m"
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-buffering: "off"
spec:
  ingressClassName: nginx
  tls:
    - secretName: aks-ingress-tls
  rules:
    - host: bj6.wiwynn.com
      http:
        paths:
          - pathType: ImplementationSpecific
            path: /sftpgo
            backend:
              service:
                name: sftpgo-web-service
                port:
                  number: 8080
域名路徑後端 Port說明
bj6.wiwynn.com/sftpgo-fileserver/Caddy 80內部檔案瀏覽
one.wiwynn.com/sftpgo-fileserver/Caddy 81公開檔案瀏覽
bj6.wiwynn.com/sftpgoSFTPGo 8080WebAdmin 管理介面(需搭配 WEB_ROOT=/sftpgo

Docker Compose 部署(非 K8s 環境)

yaml
version: '3'

services:
  sftpgo:
    image: drakkan/sftpgo
    container_name: sftpgo
    restart: always
    volumes:
      - "./upload:/srv/sftpgo"
      - "./home:/var/lib/sftpgo"
      - "./config:/etc/sftpgo"
    ports:
      - "2022:2022"
      - "9888:8080"

  sftpgo_nginx:
    image: nginx:stable
    container_name: sftpgo_nginx
    restart: always
    ports:
      - "9889:80"
    volumes:
      - "./nginx.conf:/etc/nginx/conf.d/default.conf"
      - "./upload/data/user:/usr/share/nginx/html"
服務主機 Port說明
SFTPGo SFTP2022SFTP 連線
SFTPGo Admin9888Web 管理介面
Nginx 檔案9889檔案瀏覽

相關概念

來源