Files
SnapOtter/apps/docs/zh-CN/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 94e82ef1fffe 2

部署

SnapOtter 以 3 容器 Docker Compose 栈的形式部署:SnapOtter 应用镜像、PostgreSQL 17 和 Redis 8。应用镜像支持 linux/amd64(配合 NVIDIA CUDA 实现 AI 加速)和 linux/arm64CPU),因此可以在 Intel/AMD 服务器、Apple Silicon Mac,以及 Raspberry Pi 4/5 等 ARM 设备上原生运行。目前不支持通过 VA-API、Quick Sync 或 OpenCL 使用 Intel/AMD 核显加速 AI 推理。

关于 GPU 配置、Docker Compose 示例和版本锁定,请参阅 Docker 镜像

::: 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 内存 GPU 存储
最低 图像、文件和轻量 PDF 工具;单用户;小批量 2 核 2 GB ~7 GB
推荐 全部五种模态,含视频、PDF 和 CPU 上的 AI;批量处理;少量用户 4 核 4 GB ~25 GB
完整 高速运行所有功能,含 GPU AI;大批量;多用户 6-8 核 8 GB NVIDIA 8 GB+ 显存(12 GB 更宽裕) ~35 GB

架构:仅支持 64 位linux/amd64linux/arm64)。SnapOtter 可在 Intel/AMD 服务器、Apple Silicon Mac,以及包括 Raspberry Pi 4 和 54-8 GB)在内的 64 位 ARM 板卡上原生运行。它不能在 32 位 ARMarmv7/armhf)上运行(没有为其构建镜像),也不能在 Pi Zero 等 512 MB 级别的板卡上运行,这些设备低于内存下限(见下文)。

最低(图像、文件和轻量 PDF 工具;无 AI)

资源 要求
CPU 2 核
内存 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 核
内存 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 上运行)
内存 8 GB
GPU NVIDIA8+ GB 显存(推荐 12 GB
磁盘 总计 ~35 GB

NVIDIA GPUCUDA)能大幅加速繁重的 AI 模型。在 RTX 4070 与现代 CPU 上对比测得:

AI 工具 GPU 加速比 备注
AI 放大(RealESRGAN 2×) ~47× 收益最大 - 不到一秒 vs ~33 秒(大图数分钟)
人脸增强(CodeFormer ~12× ~0.9 秒 vs ~11 秒
转录(Whisper ~4.5×
背景移除 / 替换 / 模糊 ~4× GPU 上 ~7 秒 vs CPU 上 ~29 秒
上色 ~1.8×
OCR、人脸检测、红眼、降噪 ~1× 在 CPU 上已经很快 - GPU 无济于事
照片修复 即使在 GPU 上也受 CPU 限制(GPU 利用率 0%);此处快速 CPU 比 GPU 更重要

值得配 GPU 的工具是 放大、人脸增强、转录和背景移除。人脸检测、OCR 和红眼受 CPU 限制且已经很快,因此 GPU 毫无帮助。

在放大配合人脸增强时,显存峰值占用达到 7.5 GB。6 GB 的 NVIDIA GPU 对大多数单独运行的 AI 工具都够用,但在放大上会失败。8-12 GB 显存可以应对一切。

目前不支持通过 VA-API、Quick Sync 或 OpenCL 使用 Intel/AMD 核显加速 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 秒 vs 2 秒)。大多数使用场景推荐 GFPGAN。

挂载 / 卷 用途 是否必需?
/data(应用) AI 模型、Python venv、用户文件 - 缺少将导致文件丢失
/tmp/workspace(应用) 临时处理文件(自动清理) 推荐
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 用户),使其与镜像的所有权匹配。

  • 对主机数据集执行 chown,改为容器运行所用的 UID,从 TrueNAS shell 中操作:

    # 使用启动错误中的 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"