Files
SnapOtter/apps/docs/zh-TW/guide/deployment.md
T
SnapOtterandGitHub d10d0f544f fix: release QA hardening across processing, media, security, and CI gates (#649)
A release-readiness QA pass over the whole product. The commits split into
defects a user would hit and gates that were reporting green while measuring
nothing.

## Fixes that change behaviour

Rate limiting was bypassable on every install: TRUST_PROXY defaulted to true, so
request.ip came from a client-set header and a forged X-Forwarded-For got past
the login limiter. The default is now a private-network trust list.

A transient Postgres outage stranded in-flight jobs, leaving finished output on
disk with no row pointing at it. A reconciler now resolves those rows and adopts
the bytes rather than dropping the work.

A Redis connection that moved to a new address wedged every read-blocked
consumer, so completions stopped signalling while health still answered 200.
Socket timeouts plus subscriber pings recover it.

Installing more than one AI bundle left the shared venv multi-versioned and
silently broke three tools. The installer now reconciles distributions to one
version each.

Converting an image to JXL at quality 1 through 4 returned a 500, because
libjxl 0.7 rejects the distance those values compute. The quality is floored at
what the encoder honours. A missing ffmpeg was also reported to the user as a
corrupt upload; it now says the engine is unavailable.

RAW uploads reached an unpatched LibRaw on arm64, so it is built from source at
0.22.2, and the release scan was split so it can fail on an unfixed critical
instead of hiding it behind ignore-unfixed.

## Gates that could not fail

Two mutation lanes ran zero mutants because Stryker crawled the gitignored docs
build; coverage discarded its whole report on any failing test; the lint gate
skipped root tests, scripts, and two workspaces; and several generated matrices
counted a host missing ffmpeg as a passing tool. Each now measures what it
claims.

Full evidence and the outstanding release items are tracked locally and are not
part of this branch.
2026-07-27 15:37:30 +08:00

30 KiB
Raw Blame History

description, i18n_source_hash, i18n_provenance, i18n_output_hash, i18n_hash_version
description i18n_source_hash i18n_provenance i18n_output_hash i18n_hash_version
使用 Docker 將 SnapOtter 部署到正式環境。硬體需求、GPU 設定,以及 Nginx、Traefik 和 Cloudflare 的反向代理設定。 2a722f86da75 human 97aa47ef2602 2

部署

SnapOtter 以 3 個容器的 Docker Compose 堆疊部署:SnapOtter 應用程式映像檔、PostgreSQL 17 和 Redis 8。應用程式映像檔支援 linux/amd64(搭配 NVIDIA CUDA 進行 AI 加速)與 linux/arm64(CPU),因此可原生執行於 Intel/AMD 伺服器、Apple Silicon Mac,以及 Raspberry Pi 4/5 等 ARM 裝置。目前不支援透過 VA-API、Quick Sync 或 OpenCL 進行 Intel/AMD iGPU 加速的 AI 推論。

關於 GPU 設定、Docker Compose 範例與版本鎖定,請參閱 Docker Image

::: info 韓語 OCR 相容性 快速 OCR 支援 autoendeesfrzhja,但不支援韓語 (ko)。韓語需要精確 OCR 套件以及 balancedbest。此套件可在官方 Linux amd64 和 arm64 容器上執行;即使是 NVIDIA 主機,OCR 仍使用 CPU。不受支援的系統會傳回明確的相容性錯誤,絕不會靜默回退至 fast。韓語搭配 fast 或舊版 tesseract 別名時,會在排入佇列前以 FEATURE_INCOMPATIBLEfast-korean-unsupported 拒絕。 :::

快速開始(CPU

# docker-compose.yml - Copy this file and run: docker compose up -d
services:
  SnapOtter:
    image: snapotter/snapotter:latest    # or ghcr.io/snapotter-hq/snapotter:latest
    container_name: SnapOtter
    ports:
      - "1349:1349"                # Web UI + API
    volumes:
      - SnapOtter-data:/data           # AI models, user files (PERSISTENT)
      - SnapOtter-workspace:/tmp/workspace  # Temp processing files (can be tmpfs)
    environment:
      # --- Authentication ---
      - AUTH_ENABLED=true          # Set to false to disable login entirely
      - DEFAULT_USERNAME=admin     # First-run admin username
      - DEFAULT_PASSWORD=admin     # First-run admin password (you'll be forced to change it)

      # --- Database + Queue ---
      - DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
      - REDIS_URL=redis://redis:6379

      # --- Limits (set 0 for unlimited) ---
      # - MAX_UPLOAD_SIZE_MB=100   # Per-file upload limit in MB
      # - MAX_BATCH_SIZE=100       # Max files per batch request
      # - RATE_LIMIT_PER_MIN=1000  # API rate limit per IP, default shown (0 = disabled)
      # - MAX_USERS=0              # Max user accounts

      # --- Networking ---
      # - TRUST_PROXY=loopback,linklocal,uniquelocal  # Which peers may set the client IP via X-Forwarded-For (default shown)

      # --- Bind mount permissions ---
      # - PUID=1000                # Match your host user's UID (run: id -u)
      # - PGID=1000                # Match your host user's GID (run: id -g)
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:1349/api/v1/health"]
      interval: 30s
      timeout: 5s
      start_period: 60s
      retries: 3
    shm_size: "2gb"            # Needed for Python ML shared memory
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  postgres:
    image: postgres:17-alpine
    container_name: SnapOtter-postgres
    environment:
      POSTGRES_USER: snapotter
      POSTGRES_PASSWORD: snapotter     # Change this for non-local deployments
      POSTGRES_DB: snapotter
    volumes:
      - SnapOtter-pgdata:/var/lib/postgresql/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U snapotter -d snapotter"]
      interval: 10s
      timeout: 5s
      retries: 12
      start_period: 15s

  redis:
    image: redis:8-alpine
    container_name: SnapOtter-redis
    command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
    volumes:
      - SnapOtter-redisdata:/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 12
      start_period: 10s

volumes:
  SnapOtter-data:       # Named volume - Docker manages permissions automatically
  SnapOtter-workspace:
  SnapOtter-pgdata:
  SnapOtter-redisdata:
docker compose up -d

之後即可在 http://localhost:1349 存取應用程式。

遇到 Docker Hub 速率限制?snapotter/snapotter:latest 換成 ghcr.io/snapotter-hq/snapotter:latest,改從 GitHub Container Registry 拉取。兩個登錄檔在每次發行時都會收到相同的映像檔。

快速開始(NVIDIA CUDA

對於支援的 AI 工具上的 NVIDIA CUDA 加速(背景去除、放大、臉部增強):

# docker-compose-gpu.yml - Requires: NVIDIA GPU + nvidia-container-toolkit
# Install toolkit: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
services:
  SnapOtter:
    image: snapotter/snapotter:latest
    container_name: SnapOtter
    ports:
      - "1349:1349"
    volumes:
      - SnapOtter-data:/data
      - SnapOtter-workspace:/tmp/workspace
    environment:
      - AUTH_ENABLED=true
      - DEFAULT_USERNAME=admin
      - DEFAULT_PASSWORD=admin
      - DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
      - REDIS_URL=redis://redis:6379
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:1349/api/v1/health"]
      interval: 30s
      timeout: 5s
      start_period: 60s
      retries: 3
    shm_size: "2gb"                # Required for PyTorch CUDA shared memory
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all           # Or set to 1 for a specific GPU
              capabilities: [gpu]
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  postgres:
    image: postgres:17-alpine
    container_name: SnapOtter-postgres
    environment:
      POSTGRES_USER: snapotter
      POSTGRES_PASSWORD: snapotter     # 針對非本地部署更改此設置
      POSTGRES_DB: snapotter
    volumes:
      - SnapOtter-pgdata:/var/lib/postgresql/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U snapotter -d snapotter"]
      interval: 10s
      timeout: 5s
      retries: 12
      start_period: 15s

  redis:
    image: redis:8-alpine
    container_name: SnapOtter-redis
    command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
    volumes:
      - SnapOtter-redisdata:/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 12
      start_period: 10s

volumes:
  SnapOtter-data:
  SnapOtter-workspace:
  SnapOtter-pgdata:
  SnapOtter-redisdata:
docker compose -f docker-compose-gpu.yml up -d

驗證 GPU 加速

檢查日誌中的 CUDA 檢測:

docker logs SnapOtter 2>&1 | head -20
# Look for: [gpu] CUDA available via torch

如果 AI 工具在 CPU 上運行,即使 --gpus all 和 NVIDIA Container Toolkit 設定正確,請從 設定 → AI 功能 重新安裝受影響的捆綁包(例如背景刪除)。安裝程式會恢復 ONNX 運行時的 GPU 版本,而由另一個捆綁包(例如轉錄)引入的僅 CPU 版本可能會在共享 AI 環境中隱藏。如果從 UI 重新安裝無法恢復舊映像上的 GPU,請參閱 問題 #490 中的手動修復。

硬體需求

這些數字來自跨多種系統的效能測試,從搭載 NVIDIA RTX 4070 的現代 amd64 工作站,一直到 Raspberry Pi,在每台機器上執行整個工具目錄,並掃描 Docker 資源限制以找出實際的下限。

在這些等級的最低階(Pi、舊筆電、2 GB 的 VPS)上執行?低資源環境部署將這些數字轉化為一份帶有調校上限的具體逐步教學。

快速參考

等級 使用情境 CPU RAM GPU 儲存空間
最低 影像、檔案與輕量 PDF 工具;單一使用者;小批次 2 核心 2 GB ~7 GB
建議 全部五種模態,包含影片、PDF 與 CPU 上的 AI;批次;少數使用者 4 核心 4 GB ~25 GB
完整 全部高速運作,包含 GPU AI;大量批次;多位使用者 6-8 核心 8 GB NVIDIA 8 GB 以上 VRAM12 GB 較充裕) ~35 GB

架構:僅限 64 位元linux/amd64linux/arm64)。SnapOtter 可原生執行於 Intel/AMD 伺服器、Apple Silicon Mac,以及 64 位元 ARM 板,包括 Raspberry Pi 4 和 54-8 GB)。它無法執行於 32 位元 ARMarmv7/armhf),因為沒有為其建置映像檔,也無法執行於 Pi Zero 這類 512 MB 等級的板子,因為它們低於記憶體下限(見下文)。

最低(影像、檔案與輕量 PDF 工具;無 AI)

資源 需求
CPU 2 核心
RAM 2 GB
磁碟 ~5.5 GB(映像檔)+ 資料磁碟區
GPU 非必要

全部 222 個非 AI 目錄工具,包括影像(調整大小、裁切、轉換、壓縮、調整、浮水印)、影片(修剪、靜音、重新封裝)、音訊(轉換、正規化、修剪)、PDF(合併、分割、壓縮、旋轉、保護)、檔案轉換,以及專屬的轉換預設,都能在一般硬體上執行。即使是大型檔案,多數作業也能在遠低於一秒內完成:一張 2.7 MB 的影像調整大小約需 ~0.05 秒,重新編碼為 WebP 約需 ~2 秒。

記憶體下限是真實存在的,這來自一次 Docker 資源限制掃描:512 MB 無法啟動堆疊(即使是單一影像調整大小也會被終止),1 GB 可處理單一檔案作業,但多檔案批次會耗盡記憶體,而 2 GB / 2 核心 是能舒適處理批次的最小組態。

deploy:
  resources:
    limits:
      cpus: '2'
      memory: 2G

唯一吃重 CPU 的例外是影片重新編碼。 串流複製作業(修剪、靜音、容器重新封裝)是瞬間完成的,但轉碼為不同的編解碼器則受限於 CPU。一段 1080p / 45 秒的影片重新編碼為 VP9(WebM),在快速的現代 CPU 上約需 ~40 秒,在 Apple Silicon 上約 ~45 秒,在較舊的行動 4 核心上約 ~80 秒,在較舊的 4 核心伺服器上則需 ~130 秒。如果你的工作負載偏重影片,請優先考慮 CPU 核心數與時脈速度,或提高容器的 cpus: 限制。隨附的 compose 預設將應用程式上限設為 4 核心(GPU compose 為 8 核心)。

資源 需求
CPU 4 核心
RAM 4 GB
Disk 3 GB(影像)+ 約 20 GB(所有選用 AI 套件)+ 工作區
GPU 非必要(CPU 後備)

安裝和運行更大的 AI 捆綁包將 RAM 的建議量推至 4 GB。 如果未安裝可選包,則應用程式空閒時間約為 360 MB。 舊版 Python 工具共享 sidecar,而精確的 OCR 使用固定到活動不可變產生的專用長壽命 dispatcher。 在啟動之前,安裝程式會在候選程式上執行 smoke test。 然後,它會自動切換到新的 dispatcher,並在 garbage collection 之前耗盡先前的 dispatcher。 每個官方準確的 OCR 工件都必須通過 4 GiB cgroup 內最壞情況的 release suite 雖然 4 GB 主機建議為 Node.js 應用程式留出了空間, Postgres, Redis, 隊列, 並同時進行工作。

多數 AI 工具在 CPU 上完全可用;少數確實需要 GPU。在現代 4 核心 CPU 上測得:

AI 工具 CPU 時間 CPU 上可用?
臉部偵測(模糊臉部、智慧裁切、紅眼)、雜訊移除 不到 1 秒
OCR、轉錄、字幕 1-3 秒
上色、臉部強化 ~10 秒
去背 / 替換 / 模糊 ~29 秒 是(需等待)
AI 放大(RealESRGAN 小圖 ~33 秒;大圖需數分鐘 勉強可用 — 強烈建議 GPU
相片修復(完整流程) 數分鐘 否 — 需要 GPU 或快速的多核心 CPU

SnapOtter 刻意不將這些模型下載內建於 Docker 映像檔中。AI 套件組僅在管理員啟用相關工具時才會拉取,儲存於持久化的 /data/ai 磁碟區,並由所有依賴相同模型堆疊的工具共用。這讓最終容器映像檔保持精簡,同時仍允許完整的 AI 安裝達到下方較大的儲存數字。

有些工具依賴不只一個共用套件組。舉例來說,證件照同時需要 background-removalface-detection;如果 background-removal 已安裝,啟用證件照只會下載缺少的 face-detection 套件組。相同的重用機制適用於所有 AI 工具。

可選 AI 包儲存估算:

套件組 磁碟大小
去背 4-5 GB
放大 + 臉部強化 + 雜訊移除 5-6 GB
臉部偵測 200-300 MB
物件消除 + 上色 1-2 GB
精確 OCRbalanced/best ~208-234 MiB 下載 / ~409-488 MiB 安裝
相片修復 4-5 GB
轉錄 〜600MB
所有捆綁包 已安裝~20 GB

Fast OCR 透過 Tesseract 內建到映像中,增加約 25 個 MiB,並且不需要選購的 OCR 套件或其 4 個 GiB 記憶體需求。 準確的包可以在官方找到 Linux amd64 和 arm64 容器和運行 ONNX Runtime 在 CPU。 NVIDIA 主機使用相同的 CPU OCR 執行時,因此 OCR 不依賴 CUDA 版本或 GPU 架構。 準確的運行時需要至少 4 GiB 的有效記憶體:配置的容器 cgroup 限制,否則為主機記憶體。 在下載套件之前,SnapOtter 拒絕低於簽章相容性最低值的系統。 在無法保證 libc 和 Python ABI 的 bare-metal/預建檔案上,也會拒絕準確的套件安裝。

共用同一個 DATA_DIR 的複本必須使用相同的 CPU 架構;請透過節點親和性將多複本部署固定在相容的節點上。混合使用 amd64/arm64 的複本需要各自獨立的資料磁碟區和 SnapOtter 部署。

精確的運行時保持一代處於活動狀態,並在啟動後清除其下載快取。 對於此版本,首次安裝暫時需要大約 620-720 MiB 用於存檔和暫存,升級可以在老一代保持活動狀態時達到接近 1.2 GiB 的峰值。 安裝程式在下載或提取之前根據簽章索引和當前代計算確切的要求,如果資料量太小,安裝程式會提前失敗。

deploy:
  resources:
    limits:
      cpus: '4'
      memory: 4G

完整(NVIDIA CUDA 上的 AI 工具)

資源 需求
CPU 6-8 核心(即使使用 GPU AI,影片前置處理與並行仍在 CPU 上執行)
RAM 8 GB
GPU NVIDIA8 GB 以上 VRAM(建議 12 GB
磁碟 總計 ~35 GB

NVIDIA GPUCUDA)能大幅加速吃重的 AI 模型。在 RTX 4070 與現代 CPU 上測得:

AI 工具 GPU 加速倍數 備註
AI 放大(RealESRGAN 2×) ~47× 最大的收益 — 不到一秒,對比 ~33 秒(大圖需數分鐘)
臉部強化(CodeFormer ~12× ~0.9 秒對比 ~11 秒
轉錄(Whisper ~4.5×
去背 / 替換 / 模糊 ~4× GPU 上 ~7 秒對比 CPU 上 ~29 秒
上色 ~1.8×
OCR、臉部偵測、紅眼、雜訊移除 ~1× 在 CPU 上已很快 — GPU 幫不上忙
相片修復 即使在 GPU 上也受限於 CPU(0% GPU 使用率);此處快速的 CPU 比 GPU 更重要

值得使用 GPU 的工具是 放大、臉部強化、轉錄和去背。臉部偵測、OCR 和紅眼受限於 CPU 且已經很快,因此 GPU 幫不上忙。

在放大搭配臉部強化時,VRAM 峰值使用量達到 7.5 GB。6 GB 的 NVIDIA GPU 對多數個別 AI 工具都能運作,但在放大時會失敗。8-12 GB VRAM 可處理所有情況。

目前不支援透過 VA-API、Quick Sync 或 OpenCL 進行 Intel/AMD iGPU 加速的 AI 推論。將 /dev/dri 對應到容器中並不會啟用 AI GPU 加速;除非有 NVIDIA CUDA 可用,否則 SnapOtter 會在 CPU 上執行 AI 工具。

deploy:
  resources:
    limits:
      cpus: '4'
      memory: 8G
    reservations:
      devices:
        - driver: nvidia
          count: all
          capabilities: [gpu]

並行使用者

對預設限制 4 核心的應用程式容器發出的並行影像調整大小請求:

並行請求數 平均回應時間 錯誤
1 0.4s 0
5 1.2s 0
10 2.1s 0

隨著工作者集區飽和,回應時間以次線性方式劣化,且無錯誤。提高應用程式容器的 cpus: 限制(或使用核心數更多的主機)可提升上限。請注意,吃重的作業(影片轉碼、CPU AI)在整個執行期間都會佔住一個工作者,因此請依你預期的並行吃重作業數量來調整 CPU,而不只是依請求數。

支援的影像格式

SnapOtter 支援 55+ 種輸入格式14 種輸出格式,包括來自 20+ 相機品牌的 RAW 檔、專業格式(PSD、EPS、OpenEXR、HDR)、現代編解碼器(JPEG XL、AVIF、HEIC、QOI),以及科學/遊戲格式(FITS、DDS)。

關於每種支援格式、使用的解碼器與可用的品質控制的詳細資訊,請參閱完整格式清單

已知限制

  • 內容感知調整大小在大型影像(>5 MP)上會因 caire 二進位檔的限制而當機。對較小的影像運作正常。
  • HEIF 解碼需要 13-23 秒。HEICApple 的變體)快得多,僅需 0.3-0.9 秒。
  • 放大在 CPU 上對超出小圖的任何影像都會逾時。實務使用需要 GPU。
  • CodeFormer 臉部強化明顯比 GFPGAN 慢(GPU 上 53 秒對比 2 秒)。多數使用情境建議使用 GFPGAN。

磁碟區

掛載 / 磁碟區 用途 是否必要?
/dataapp AI 模型、Python venv、使用者檔案 — 少了會遺失檔案
/tmp/workspaceapp 暫存處理檔案(自動清理) 建議
SnapOtter-pgdatapostgres PostgreSQL 資料目錄(使用者、設定、管線、作業) — 少了會遺失資料
SnapOtter-redisdataredis Redis 僅附加檔案,用於持久化的作業佇列 建議

繫結掛載 vs. 具名磁碟區

具名磁碟區(建議)— Docker 會自動管理權限:

volumes:
  - SnapOtter-data:/data

繫結掛載 — 由你管理權限。設定 PUID/PGID 以符合你的主機使用者:

volumes:
  - ./SnapOtter-data:/data
environment:
  - PUID=1000    # Your host UID (run: id -u)
  - PGID=1000    # Your host GID (run: id -g)

儲存權限

SnapOtter 在執行期間會寫入兩個位置:/data(使用者檔案、記錄、AI 模型與 Python venv)和 /tmp/workspace(暫存處理暫存區)。兩者都必須可由容器所執行的使用者寫入。若其中之一不可寫入,容器會在啟動時快速失敗,並顯示一則訊息,指出該目錄、執行中的 UID/GID,以及修正方式,而不是在啟動時看似「健康」,然後在第一次上傳時以難解的錯誤失敗。

權限的處理方式取決於容器的啟動方式:

預設(以 root 啟動,降權至 snapotter — 進入點以 root 啟動,修正已掛載磁碟區的擁有權,然後透過 gosu 降權至非特權的 snapotter 使用者。具名磁碟區無需任何設定即可運作。對於繫結掛載,請將 PUID/PGID 設為你的主機使用者(如上),使它寫入的檔案由你擁有。

Kubernetes / OpenShift(透過 runAsUser 以非 root 執行) — 直接以非 root 使用者啟動時,容器無法自行 chown 磁碟區,因此協調器必須使其可寫入。請設定 fsGroup

securityContext:
  runAsUser: 999
  runAsGroup: 999
  fsGroup: 999        # makes mounted volumes writable by the pod

映像檔的可寫入目錄群組擁有者為 GID 0 且群組可寫入,因此以任意 UID 加上 root 補充群組(OpenShift 的預設值)執行的 pod 無需 chown 即可寫入。

TrueNAS Scale(以及其他「外來 UID」設定) — TrueNAS 以非 root 使用者執行應用程式(通常是 568:568),並掛載由不同使用者擁有的主機資料集,因此進入點和 fsGroup 都無法自行使其可寫入。請擇一:

  • 以 root 執行應用程式(建議)— 將應用程式的使用者保持未設定或設為 0,讓預設進入點修正權限並降權至 snapotter

  • 以 UID 999 執行 — 將應用程式的使用者/群組設為 999:999SnapOtter 內建的 snapotter 使用者),使其符合映像檔的擁有權。

  • 從 TrueNAS shell 將主機資料集 chown 至容器所執行的 UID

    # 使用啟動錯誤中的 UID(或在容器內執行 `id`)
    chown -R 568:568 /mnt/<pool>/<dataset>
    

啟動錯誤會指出要使用的確切 UID,因此最快的做法是先啟動一次應用程式、讀取訊息,然後據此 chown(或調整使用者)。

環境變數

變數 預設值 說明
AUTH_ENABLED true 啟用/停用登入需求
DEFAULT_USERNAME admin 初始管理員使用者名稱
DEFAULT_PASSWORD admin 初始管理員密碼(首次登入時強制變更)
MAX_UPLOAD_SIZE_MB 0(無限制) 每個檔案的上傳限制(MB)。映像檔出廠即為 0;從原始碼建置則從 100 起
MAX_BATCH_SIZE 0(無限制) 每個批次請求的最大檔案數。映像檔出廠即為 0;從原始碼建置則從 100 起
RATE_LIMIT_PER_MIN 1000 每個 IP 每分鐘的 API 請求數(設為 0 以停用)
MAX_USERS 0(無限制) 最大使用者帳號數
TRUST_PROXY loopback,linklocal,uniquelocal 允許哪些對端透過 X-Forwarded-For 設定用戶端 IP。預設僅限私有網路
PUID 999 以此 UID 執行(用於繫結掛載權限)
PGID 999 以此 GID 執行(用於繫結掛載權限)
LOG_LEVEL info 記錄詳細程度:fatal、error、warn、info、debug、trace
CONCURRENT_JOBS 0(自動) 最大並行 AI 處理作業數
SESSION_DURATION_HOURS 168 登入工作階段存留期(7 天)
CORS_ORIGIN (空白) 以逗號分隔的允許來源,或留空表示同源

出站代理程式和私人 CA

官方容器啟用了 Node 的環境代理支援。 如果 SnapOtter 必須透過企業代理程式到達 OCR 執行階段儲存庫或其他 HTTPS 服務,請設定 HTTPS_PROXY(並在需要時設定 HTTP_PROXY)。 將 NO_PROXY 設定為必須直接存取的以逗號分隔的主機列表,例如 Postgres、Redis 和內部物件儲存。

如果代理程式或內部服務由私有憑證授權單位簽署,請將 CA 憑證掛載為唯讀並將 NODE_EXTRA_CA_CERTS 指向它。 Node進程啟動時該檔案必須存在:

services:
  app:
    environment:
      HTTPS_PROXY: http://proxy.example.internal:3128
      HTTP_PROXY: http://proxy.example.internal:3128
      NO_PROXY: postgres,redis,minio,localhost,127.0.0.1
      NODE_EXTRA_CA_CERTS: /etc/snapotter/custom-ca.pem
    volumes:
      - ./company-ca.pem:/etc/snapotter/custom-ca.pem:ro

將代理憑證保留在 Compose 檔案外部(例如,在受保護的 .env 檔案或機密中)。不要停用 TLS 驗證:簽署的 OCR 索引對發布元資料進行身份驗證,而正常的 TLS 驗證仍然保護傳輸和所有其他出站請求。

健康檢查

容器內建健康檢查:

# Check container health status
docker inspect --format='{{.State.Health.Status}}' SnapOtter

# Manual health check
curl http://localhost:1349/api/v1/health
# {"status":"healthy","version":"x.y.z"}

反向代理

TRUST_PROXY 預設為 loopback,linklocal,uniquelocal,因此 SnapOtter 只相信來自私有網路對端的 X-Forwarded-For。同一主機、Docker 網路或區域網路中的反向代理開箱即受信任,這表示速率限制、登入暴力破解限制、稽核記錄以及 enterprise 版的 IP 允許清單,都不必設定就能看到真實的用戶端 IP。

只有當前置代理從公開位址連到 SnapOtter 時才設為 TRUST_PROXY=true,例如位於另一個網路的雲端負載平衡器。在直接曝露的執行個體上,這個值會讓 request.ip 落入攻擊者手中,因為不斷更換該標頭的呼叫端每次請求都能拿到全新的速率限制計數。

動手量測用戶端 IP 之前,有兩件事要知道。macOS 與 Windows 上的 Docker Desktop 透過使用者空間代理提供已發布的連接埠,會把所有來源位址改寫成虛擬機閘道 192.168.65.1,因此在那裡無論 TRUST_PROXY 設成什麼都拿不回真實用戶端;面向網際網路的部署請放在 Linux 上。而且在任何平台上,經由 localhost 連到已發布的連接埠都會被視為橋接閘道而非你的用戶端,所以 localhost 測試完全說明不了真實用戶端是如何歸屬的。TRUST_PROXY 各種取值的完整表格與 Docker Desktop 注意事項請見 SECURITY.md

對於下面的每個代理來說,有兩件事很重要:允許大型請求正文(上傳),並且不緩衝回應。響應緩衝代理會破壞 SSE 進度,更明顯的是,使大文件下載“開始但永遠不會完成”,因為代理在傳遞之前保存整個文件。 SnapOtter 在下載時發送 X-Accel-Buffering: no,因此即使在其他地方保留緩衝,nginx 也會對它們進行串流傳輸,但除 nginx 之外的代理需要明確停用回應緩衝(如下面的每個配置所示)。如果下載中途停止,首先要檢查前面的緩衝代理。

Nginx

server {
    listen 80;
    server_name images.example.com;

    # Match MAX_UPLOAD_SIZE_MB (0 = nginx default 1M, so set high for unlimited)
    client_max_body_size 500M;

    location / {
        proxy_pass http://localhost:1349;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # 串流響應而不是緩衝:SSE 進度(批次、AI、功能安裝)和大檔案下載需要。
        proxy_buffering off;
        proxy_read_timeout 300s;
    }
}

Nginx Proxy Manager

  1. 新增一個 Proxy Host
  2. 將 Domain Name 設為你的網域
  3. 將 Scheme 設為 httpForward Hostname 設為 SnapOtter(或你的容器 IP),Forward Port 設為 1349
  4. 啟用 WebSocket 支援
  5. 在 Advanced 底下,加入:client_max_body_size 500M;proxy_buffering off;

Traefik

# Add these labels to the SnapOtter service in docker-compose.yml
labels:
  - "traefik.enable=true"
  - "traefik.http.routers.snapotter.rule=Host(`images.example.com`)"
  - "traefik.http.routers.snapotter.entrypoints=websecure"
  - "traefik.http.routers.snapotter.tls.certresolver=letsencrypt"
  - "traefik.http.services.snapotter.loadbalancer.server.port=1349"
  # Increase upload limit (default 2MB is too low)
  - "traefik.http.middlewares.snapotter-body.buffering.maxRequestBodyBytes=524288000"
  - "traefik.http.routers.snapotter.middlewares=snapotter-body"

Caddy

images.example.com {
    reverse_proxy localhost:1349 {
        flush_interval -1
        transport http {
            read_timeout 300s
            write_timeout 300s
        }
    }
}

flush_interval -1 停用回應緩衝,這是 SSE 進度事件(批次、AI 工具、功能安裝)以及大檔案下載串流(而不是停滯)所必需的。延長的逾時允許完成大檔案上傳,而無需 Caddy 提前關閉連線。

Cloudflare Tunnels

cloudflared tunnel --url http://localhost:1349

注意:Cloudflare 在免費方案上有 100 MB 的上傳限制。請將 MAX_UPLOAD_SIZE_MB=100 設為相符。

CI/CD

GitHub 儲存庫有三個工作流程:

  • ci.yml — 在每次推送與 PR 時自動執行。進行 lint、型別檢查、測試、建置,並驗證 Docker 映像檔(不推送)。
  • release.yml — 透過 workflow_dispatch 手動觸發。執行 semantic-release 以建立版本標籤與 GitHub 發行,然後建置多架構 Docker 映像檔(amd64 + arm64)並推送到 Docker Hubsnapotter/snapotter)和 GitHub Container Registryghcr.io/snapotter-hq/snapotter)。
  • deploy-docs.yml — 建置這個文件網站,並在推送到 main 時部署到 Cloudflare Pages。

若要建立發行,請在 GitHub UI 中前往 Actions > Release > Run workflow,或執行:

gh workflow run release.yml

Semantic-release 會依提交歷史決定版本。latest Docker 標籤永遠指向最近一次的發行。

分析

SnapOtter 包含匿名的產品分析(工具使用模式、錯誤回報),以協助抓出錯誤並改善功能。它預設為開啟。你的檔案、檔案名稱與個人資料絕不屬於這些資料的一部分。停用分析後,SnapOtter 仍正常運作。

停用分析

執行期間的退出是一鍵式的管理員切換。開啟 Settings > System > Privacy,關閉 Anonymous Product Analytics。它會立即對整個執行個體停止,無需重新建置。

若要取得一個絕不會發出分析的映像檔,請透過複製儲存庫並重新建置來設定建置時的硬性關閉:

git clone https://github.com/snapotter-hq/SnapOtter.git
cd SnapOtter
docker compose -f docker/docker-compose.yml build --build-arg SNAPOTTER_ANALYTICS=off
docker compose -f docker/docker-compose.yml up -d

或將建置引數加入你現有的 docker-compose.yml

services:
  snapotter:
    build:
      context: .
      dockerfile: docker/Dockerfile
      args:
        SNAPOTTER_ANALYTICS: "off"