This page was machine-translated. Spotted a mistake?Help improve it.
Skip to content

배포

SnapOtter는 SnapOtter 앱 이미지, PostgreSQL 17, Redis 8로 구성된 3-컨테이너 Docker Compose 스택으로 배포된다. 앱 이미지는 linux/amd64(AI 가속용 NVIDIA CUDA 포함)와 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 이미지를 참고하라.

빠른 시작 (CPU)

yaml
# 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=true         # Trust X-Forwarded-For headers (set false if not behind a proxy)

      # --- 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"]
      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:
bash
docker compose up -d

이후 앱은 http://localhost:1349에서 사용할 수 있다.

Docker Hub 속도 제한이 걸리나요? snapotter/snapotter:latestghcr.io/snapotter-hq/snapotter:latest로 바꿔 GitHub Container Registry에서 대신 가져오세요. 두 레지스트리 모두 릴리스마다 동일한 이미지를 받습니다.

빠른 시작 (NVIDIA CUDA)

AI 도구(배경 제거, 업스케일링, 얼굴 보정, OCR)에 NVIDIA CUDA 가속을 사용하려면:

yaml
# 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"]
      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:
bash
docker compose -f docker-compose-gpu.yml up -d

로그에서 CUDA 감지 여부를 확인하라:

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

하드웨어 요구 사항

이 수치는 NVIDIA RTX 4070을 탑재한 최신 amd64 워크스테이션부터 Raspberry Pi에 이르기까지 다양한 시스템에서, 각각 전체 도구 카탈로그를 실행하고 Docker 리소스 제한을 조정하며 실제 하한선을 찾는 벤치마크에서 나온 것이다.

빠른 참조

등급사용 사례CPURAMGPU스토리지
최소이미지, 파일, 가벼운 PDF 도구; 단일 사용자; 소규모 배치2 코어2 GB없음~7 GB
권장CPU에서 비디오, PDF, AI를 포함한 다섯 모달리티 전체; 배치; 소수 사용자4 코어4 GB없음~25 GB
완전GPU AI를 포함한 모든 것을 빠르게; 대규모 배치; 다수 사용자6-8 코어8 GBNVIDIA 8 GB+ VRAM (12 GB 여유)~35 GB

아키텍처: 64비트 전용 (linux/amd64 또는 linux/arm64). SnapOtter는 Intel/AMD 서버, Apple Silicon Mac, 그리고 Raspberry Pi 4 및 5(4-8 GB)를 포함한 64비트 ARM 보드에서 네이티브로 실행된다. 32비트 ARM(armv7/armhf)에서는 실행되지 않으며(해당 이미지가 빌드되지 않음), 메모리 하한선 아래인 Pi Zero 같은 512 MB급 보드에서도 실행되지 않는다(아래 참고).

최소 (이미지, 파일, 가벼운 PDF 도구; AI 없음)

리소스요구 사항
CPU2 코어
RAM2 GB
디스크~5.5 GB (이미지) + 데이터 볼륨
GPU필요 없음

222개의 비AI 카탈로그 도구 전부, 즉 이미지(크기 조정, 자르기, 변환, 압축, 조정, 워터마크), 비디오(트림, 음소거, 리먹스), 오디오(변환, 노멀라이즈, 트림), PDF(병합, 분할, 압축, 회전, 보호), 파일 변환, 그리고 전용 변환 프리셋은 모두 소박한 하드웨어에서 실행된다. 대부분의 작업은 큰 파일에서도 1초를 크게 밑돌며 끝난다. 2.7 MB 이미지는 ~0.05초 만에 크기가 조정되고 ~2초 만에 WebP로 다시 인코딩된다.

메모리 하한선은 Docker 리소스 제한 조정 실험에서 확인된 실제 값이다. 512 MB로는 스택을 시작할 수 없고(단일 이미지 크기 조정조차 강제 종료됨), 1 GB는 단일 파일 작업은 처리하지만 다중 파일 배치에서 메모리가 부족해지며, 2 GB / 2 코어는 배치를 편안하게 처리하는 가장 작은 구성이다.

yaml
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코어).

리소스요구 사항
CPU4 코어
RAM4 GB
디스크3 GB (이미지) + 24 GB (AI 모델) + 작업 공간
GPU필요 없음 (CPU 폴백)

RAM을 4 GB로 끌어올리는 것은 AI 번들 설치다. AI를 설치하지 않으면 앱은 대략 360 MB에서 유휴 상태로 있지만, 7개 번들을 모두 설치하면 ~2.6 GB의 상주 메모리를 유지한다. Python AI 사이드카가 시작 시 모델(배경 제거, 업스케일링, OCR, 전사, 얼굴 감지, 복원)을 미리 로드하기 때문이다. 비AI 설치는 가볍게 유지되지만, AI 설치에는 4 GB 이상이 필요하다.

대부분의 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
OCR5-6 GB
사진 복원4-5 GB
모든 번들~24 GB
yaml
deploy:
  resources:
    limits:
      cpus: '4'
      memory: 4G

완전 (NVIDIA CUDA에서의 AI 도구)

리소스요구 사항
CPU6-8 코어 (GPU AI를 쓰더라도 비디오 준비 + 동시성은 CPU에서 실행됨)
RAM8 GB
GPU8+ GB VRAM을 갖춘 NVIDIA (12 GB 권장)
디스크총 ~35 GB

NVIDIA GPU(CUDA)는 무거운 AI 모델의 속도를 극적으로 높인다. RTX 4070 대 최신 CPU에서 측정한 값:

AI 도구GPU 사용 시 속도 향상비고
AI 업스케일 (RealESRGAN 2×)~47×가장 큰 이득으로, ~33초(큰 이미지는 수 분) 대비 1초 미만
얼굴 보정 (CodeFormer)~12×~11초 대비 ~0.9초
전사 (Whisper)~4.5×
배경 제거 / 교체 / 블러~4×CPU ~29초 대비 GPU ~7초
컬러화~1.8×
OCR, 얼굴 감지, 적목, 노이즈 제거~1×CPU에서 이미 빠름 (GPU가 도움 되지 않음)
사진 복원없음GPU에서도 CPU에 의존(GPU 사용률 0%); 여기서는 GPU보다 빠른 CPU가 더 중요함

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는 AI 도구를 CPU에서 실행한다.

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

동시 사용자

기본 4코어 제한 앱 컨테이너에 대한 병렬 이미지 크기 조정 요청:

동시 요청평균 응답 시간오류
10.4초0
51.2초0
102.1초0

워커 풀이 포화되면서 응답 시간은 오류 없이 준선형으로 저하된다. 앱 컨테이너의 cpus: 제한을 높이거나(또는 더 많은 코어를 갖춘 호스트를 사용하면) 상한선이 올라간다. 무거운 작업(비디오 트랜스코딩, CPU AI)은 전체 실행 동안 워커 하나를 점유하므로, 단순 요청 수가 아니라 예상되는 동시 무거운 작업 수에 맞춰 CPU를 산정하라.

지원 이미지 형식

SnapOtter는 20+ 카메라 브랜드의 RAW 파일, 전문 형식(PSD, EPS, OpenEXR, HDR), 최신 코덱(JPEG XL, AVIF, HEIC, QOI), 과학/게임 형식(FITS, DDS)을 포함해 55+ 입력 형식14 출력 형식을 지원한다.

지원되는 모든 형식, 사용된 디코더, 사용 가능한 품질 제어에 대한 자세한 내용은 전체 형식 목록을 참고하라.

알려진 제한 사항

  • 콘텐츠 인식 크기 조정은 caire 바이너리의 한계 때문에 큰 이미지(>5 MP)에서 충돌한다. 더 작은 이미지에서는 정상 작동한다.
  • HEIF 디코드는 13-23초가 걸린다. HEIC(Apple 변형)는 0.3-0.9초로 훨씬 빠르다.
  • OCR 일본어는 PaddlePaddle MKLDNN 버그 때문에 CPU에서 실패한다. GPU에서는 작동한다.
  • 업스케일은 작은 이미지를 넘어서면 CPU에서 타임아웃된다. 실용적으로 쓰려면 GPU가 필요하다.
  • CodeFormer 얼굴 보정은 GFPGAN보다 상당히 느리다(GPU에서 53초 대 2초). 대부분의 사용 사례에는 GFPGAN이 권장된다.

볼륨

마운트 / 볼륨용도필수 여부
/data (app)AI 모델, Python venv, 사용자 파일 - 없으면 파일 손실
/tmp/workspace (app)임시 처리 파일 (자동 정리됨)권장
SnapOtter-pgdata (postgres)PostgreSQL 데이터 디렉터리 (사용자, 설정, 파이프라인, 작업) - 없으면 데이터 손실
SnapOtter-redisdata (redis)내구성 있는 작업 큐를 위한 Redis append-only 파일권장

바인드 마운트 vs. 명명된 볼륨

명명된 볼륨(권장). Docker가 권한을 자동으로 관리한다:

yaml
volumes:
  - SnapOtter-data:/data

바인드 마운트. 권한을 직접 관리한다. 호스트 사용자에 맞게 PUID/PGID를 설정하라:

yaml
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을 설정하라:

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

이미지의 쓰기 가능 디렉터리는 GID 0이 그룹 소유하며 그룹 쓰기가 가능하므로, 임의 UID에 root 보조 그룹(OpenShift 기본값)을 더해 실행되는 파드는 chown 없이도 쓸 수 있다.

TrueNAS Scale (및 기타 "외부 UID" 설정): TrueNAS는 앱을 비root 사용자(대개 568:568)로 실행하고 다른 사용자가 소유한 호스트 데이터셋을 마운트하므로, 엔트리포인트도 fsGroup도 스스로 이를 쓰기 가능하게 만들지 못한다. 다음 중 하나를 선택하라:

  • 앱을 root로 실행(권장): 앱의 사용자를 설정하지 않고 두거나 0로 설정한 뒤, 기본 엔트리포인트가 권한을 수정하고 snapotter로 강등하도록 둔다.

  • UID 999로 실행: 앱의 사용자/그룹을 999:999(SnapOtter 내장 snapotter 사용자)로 설정해 이미지의 소유권과 일치시킨다.

  • 컨테이너가 실행되는 UID로 호스트 데이터셋을 **chown**하라. TrueNAS 셸에서:

    bash
    # 시작 오류에서 나온 UID를 사용하라(또는 컨테이너 안에서 `id` 실행)
    chown -R 568:568 /mnt/<pool>/<dataset>

시작 오류는 사용할 정확한 UID를 알려주므로, 가장 빠른 방법은 앱을 한 번 시작해 메시지를 읽은 뒤 그에 맞춰 chown(또는 사용자 조정)하는 것이다.

환경 변수

변수기본값설명
AUTH_ENABLEDtrue로그인 요구 활성화/비활성화
DEFAULT_USERNAMEadmin초기 관리자 사용자 이름
DEFAULT_PASSWORDadmin초기 관리자 비밀번호 (첫 로그인 시 강제 변경)
MAX_UPLOAD_SIZE_MB100파일당 업로드 제한
MAX_BATCH_SIZE100배치 요청당 최대 파일 수
RATE_LIMIT_PER_MIN1000IP당 분당 API 요청 수 (0으로 설정 시 비활성화)
MAX_USERS0 (무제한)최대 사용자 계정 수
TRUST_PROXYtrue리버스 프록시의 X-Forwarded-For 헤더 신뢰
PUID999이 UID로 실행 (바인드 마운트 권한용)
PGID999이 GID로 실행 (바인드 마운트 권한용)
LOG_LEVELinfo로그 상세도: fatal, error, warn, info, debug, trace
CONCURRENT_JOBS0 (자동)최대 병렬 AI 처리 작업 수
SESSION_DURATION_HOURS168로그인 세션 수명 (7일)
CORS_ORIGIN(비어 있음)쉼표로 구분된 허용 오리진, 동일 오리진이면 비워 둠

헬스 체크

컨테이너에는 내장 헬스 체크가 포함되어 있다:

bash
# 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"}

리버스 프록시

SnapOtter는 속도 제한과 로깅이 X-Forwarded-For 헤더에서 실제 클라이언트 IP를 사용하도록 기본적으로 TRUST_PROXY=true를 설정한다.

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 support (batch progress, feature install progress)
        proxy_buffering off;
        proxy_read_timeout 300s;
    }
}

Nginx Proxy Manager

  1. 새 Proxy Host를 추가한다
  2. Domain Name을 도메인으로 설정한다
  3. Scheme을 http로, Forward Hostname을 SnapOtter(또는 컨테이너 IP)로, Forward Port를 1349로 설정한다
  4. WebSocket 지원을 활성화한다
  5. Advanced에서 client_max_body_size 500M;proxy_buffering off;을 추가한다

Traefik

yaml
# 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

txt
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

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

참고: Cloudflare는 무료 요금제에서 100 MB 업로드 제한이 있다. 이에 맞춰 MAX_UPLOAD_SIZE_MB=100를 설정하라.

CI/CD

GitHub 저장소에는 세 개의 워크플로가 있다:

  • ci.yml - 모든 푸시와 PR에서 자동으로 실행된다. 린트, 타입 체크, 테스트, 빌드를 수행하고 (푸시 없이) Docker 이미지를 검증한다.
  • release.yml - workflow_dispatch를 통해 수동으로 트리거된다. semantic-release를 실행해 버전 태그와 GitHub 릴리스를 만든 뒤, 멀티 아키텍처 Docker 이미지(amd64 + arm64)를 빌드해 Docker Hub(snapotter/snapotter)와 GitHub Container Registry(ghcr.io/snapotter-hq/snapotter)에 푸시한다.
  • deploy-docs.yml - main에 푸시할 때 이 문서 사이트를 빌드해 Cloudflare Pages에 배포한다.

릴리스를 만들려면 GitHub UI에서 Actions > Release > Run workflow로 이동하거나 다음을 실행하라:

bash
gh workflow run release.yml

semantic-release는 커밋 히스토리에서 버전을 결정한다. latest Docker 태그는 항상 가장 최근 릴리스를 가리킨다.

애널리틱스

SnapOtter에는 버그를 잡아내고 기능을 개선하는 데 도움이 되는 익명 제품 애널리틱스(도구 사용 패턴, 오류 보고)가 포함되어 있다. 기본적으로 켜져 있다. 사용자의 파일, 파일 이름, 개인 데이터는 이에 절대 포함되지 않는다. SnapOtter는 애널리틱스를 비활성화해도 정상적으로 작동한다.

애널리틱스 비활성화

런타임 옵트아웃은 원클릭 관리자 토글이다. Settings > System > Privacy를 열고 Anonymous Product Analytics를 끄면 된다. 리빌드 없이 전체 인스턴스에 대해 즉시 중지된다.

애널리틱스를 절대 내보낼 수 없는 이미지를 원한다면, 저장소를 복제하고 다시 빌드해 빌드 타임 하드 오프를 설정하라:

bash
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에 빌드 인수를 추가하라:

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