SnapOtter는 파일을 전적으로 사용자의 인프라에서 처리한다. 프로젝트 개선에 도움이 되도록 익명의, 콘텐츠 없는 제품 애널리틱스와 크래시 리포트를 기본으로 전송한다. 사용자의 파일, 파일 이름, 파일 내용, OCR 출력, 이미지 메타데이터, 문서 텍스트는 절대 전송하지 않는다. 선택적 피드백은 사용자가 제출한 뒤에만, 애널리틱스가 활성화된 경우에만 전송되며, 연락처 필드는 명시적 연락 동의가 있을 때만 포함된다. 관리자는 Settings > System > Privacy에서 리빌드 없이 원클릭으로 애널리틱스와 피드백 수집을 끌 수 있다. 파일 처리는 항상 컨테이너 안에 머문다.
컨테이너는 최소 필수 집합을 제외한 모든 Linux 기능(capability)을 제거한 전용 비root 사용자(`snapotter`)로 실행된다. 전체 취약점 공개 정책과 보안 아키텍처는 GitHub의 [SECURITY.md](https://github.com/snapotter-hq/SnapOtter/blob/main/SECURITY.md)를 참고하라.
## 컨테이너 강화 {#container-hardening}
[기본 docker-compose.yml](https://github.com/snapotter-hq/SnapOtter/blob/main/docker/docker-compose.yml)에는 프로덕션 보안 강화가 포함되어 있다. 각 옵션과 그것이 중요한 이유를 정리하면 다음과 같다:
```yaml
services:
SnapOtter:
image:snapotter/snapotter:latest
ports:
# Bind to localhost only for internet-facing deployments:
### `no-new-privileges`를 설정하지 않는 이유 {#why-no-new-privileges-is-not-set}
`security_opt: [no-new-privileges:true]`은 의도적으로 생략된다. 엔트리포인트는 볼륨 소유권을 수정하기 위해 root로 시작한 뒤, setuid가 필요한 [gosu](https://github.com/tianon/gosu)를 통해 `snapotter` 사용자로 강등한다. 권한 강등이 완료되면 프로세스는 위에 나열된 다섯 개를 제외한 모든 기능이 제거된 상태로 `snapotter`로 실행된다.
Kubernetes 또는 Docker의 `--user` 플래그를 사용해 비root로 직접 실행한다면(gosu를 우회), `no-new-privileges`를 활성화해도 안전하다.
### `read_only`를 설정하지 않는 이유 {#why-read-only-is-not-set}
`read_only: true`이 설정되지 않은 것은 PUID/PGID 리매핑이 시작 시 `/etc/passwd`과 `/etc/group`에 쓰기 때문이다. PUID/PGID 대신 Docker의 `--user` 플래그나 Kubernetes `runAsUser`을 사용한다면 읽기 전용 루트 파일 시스템을 안전하게 활성화할 수 있다.
## 네트워크 격리 {#network-isolation}
정상 작동 중에는 컨테이너가 **아웃바운드 네트워크 연결을 전혀 하지 않는다**. 모든 파일 처리는 번들된 라이브러리를 사용해 로컬에서 이루어진다.
유일한 예외는 **AI 모델 다운로드**다. 사용자가 UI를 통해 AI 기능 번들을 설치하면, 컨테이너는 사전 빌드된 번들 아카이브를 Hugging Face에서, 그리고 몇몇 개별 모델 파일을 GitHub Releases, Google Storage, PyPI에서 다운로드한다. 이 다운로드는 번들당 한 번 발생하며 `/data` 볼륨에 저장된다.
**방화벽 권장 사항:**
| 시나리오 | 아웃바운드 규칙 |
|---|---|
| 에어갭 (AI 없음) | 컨테이너의 모든 아웃바운드 트래픽 차단 |
| AI 번들 필요 | 설치 중 `huggingface.co`, `*.xethub.hf.co`, `cdn-lfs.huggingface.co`, `github.com`, `objects.githubusercontent.com`, `storage.googleapis.com`, `pypi.org`, `files.pythonhosted.org`에 대한 HTTPS를 허용한 뒤 차단 |
| AI 설치 후 | 모든 아웃바운드 트래픽 차단 - 모델이 로컬에 캐시됨 |
번들 아카이브는 Hugging Face의 Xet 스토리지에서 제공되며, 이는 `*.xethub.hf.co` 엔드포인트를 통해 병렬로 전송되어 수 GB 번들 다운로드를 빠르게 만든다. 방화벽이 `huggingface.co`을 허용하지만 `*.xethub.hf.co`를 차단하면, 설치는 여전히 성공하지만 더 느린 단일 스트림 다운로드로 폴백하므로, 빠른 경로를 유지하려면 Xet 호스트를 허용 목록에 추가하라. 완전 오프라인 설치는 이 모든 것을 건너뛰고 대신 [오프라인 번들 임포트](/ko/guide/deployment)를 사용할 수 있다.
Docker Compose 시크릿(Swarm 없이)에는 Compose v2.23 이상이 필요하다.
:::
## Kubernetes 배포 {#kubernetes-deployment}
엔트리포인트는 컨테이너가 이미 비root로 실행 중인 경우(예: Kubernetes `runAsUser`을 통해)를 감지해 gosu 권한 강등을 자동으로 건너뛴다. 그 경우 볼륨을 스스로 chown할 수 없으므로, 쓰기 가능 여부를 확인하고 그렇지 않으면 실행 가능한 안내와 함께 조기에 종료한다. `fsGroup` 및 외부 UID 설정(TrueNAS, OpenShift)은 [스토리지 권한](/ko/guide/deployment#storage-permissions)을 참고하라.
**권장 Pod SecurityContext:**
```yaml
apiVersion:apps/v1
kind:Deployment
metadata:
name:snapotter
spec:
replicas:1
selector:
matchLabels:
app:snapotter
template:
metadata:
labels:
app:snapotter
spec:
securityContext:
runAsNonRoot:true
runAsUser:999
runAsGroup:999
fsGroup:999
containers:
- name:snapotter
image:snapotter/snapotter:latest
ports:
- containerPort:1349
securityContext:
allowPrivilegeEscalation:false
capabilities:
drop:[ALL]
resources:
requests:
cpu:"1"
memory:2Gi
limits:
cpu:"4"
memory:6Gi
livenessProbe:
httpGet:
path:/api/v1/health
port:1349
initialDelaySeconds:60
periodSeconds:30
timeoutSeconds:5
readinessProbe:
httpGet:
path:/api/v1/health
port:1349
initialDelaySeconds:10
periodSeconds:10
timeoutSeconds:5
volumeMounts:
- name:data
mountPath:/data
- name:workspace
mountPath:/tmp/workspace
volumes:
- name:data
persistentVolumeClaim:
claimName:snapotter-data
- name:workspace
emptyDir:
medium:Memory
sizeLimit:2Gi
```
`runAsUser: 999`이 파드 수준에서 설정되므로 엔트리포인트는 gosu를 완전히 건너뛴다. 이로써 `allowPrivilegeEscalation: false`과 `drop: [ALL]` 기능을 충돌 없이 사용할 수 있다.
리소스 산정은 [하드웨어 요구 사항](/ko/guide/deployment#hardware-requirements)을 참고하라.