2026-03-22 21:00:37 +08:00
# Deployment
2026-04-24 18:02:21 +08:00
SnapOtter ships as a single Docker container. The image supports **linux/amd64** (with NVIDIA CUDA) and **linux/arm64** (CPU), so it runs natively on Intel/AMD servers, Apple Silicon Macs, and ARM devices like the Raspberry Pi 4/5.
2026-04-05 08:01:18 +08:00
2026-04-10 13:21:06 +08:00
See [Docker Image ](./docker-tags ) for GPU setup, Docker Compose examples, and version pinning.
2026-03-22 21:00:37 +08:00
2026-04-21 10:19:08 +08:00
## Quick Start (CPU)
2026-03-22 21:00:37 +08:00
```yaml
2026-04-21 10:19:08 +08:00
# docker-compose.yml — Copy this file and run: docker compose up -d
services :
2026-04-24 18:02:21 +08:00
SnapOtter :
2026-04-25 01:25:45 +08:00
image : snapotter/snapotter:latest # or ghcr.io/snapotter-hq/snapotter:latest
2026-04-24 18:02:21 +08:00
container_name : SnapOtter
2026-04-21 10:19:08 +08:00
ports :
- "1349:1349" # Web UI + API
volumes :
2026-04-24 18:02:21 +08:00
- SnapOtter-data:/data # Database, AI models, user files (PERSISTENT)
- SnapOtter-workspace:/tmp/workspace # Temp processing files (can be tmpfs)
2026-04-21 10:19:08 +08:00
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)
# --- Limits (0 = unlimited) ---
# - MAX_UPLOAD_SIZE_MB=0 # Per-file upload limit in MB
# - MAX_BATCH_SIZE=0 # Max files per batch request
# - RATE_LIMIT_PER_MIN=0 # API rate limit (0 = disabled, 100 = recommended for public)
# - 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)
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"
volumes :
2026-04-24 18:02:21 +08:00
SnapOtter-data : # Named volume — Docker manages permissions automatically
SnapOtter-workspace :
2026-04-21 10:19:08 +08:00
```
```bash
docker compose up -d
```
The app is then available at `http://localhost:1349` .
2026-04-25 01:25:45 +08:00
> **Docker Hub rate limits?** Replace `snapotter/snapotter:latest` with `ghcr.io/snapotter-hq/snapotter:latest` to pull from GitHub Container Registry instead. Both registries receive the same image on every release.
2026-04-21 10:19:08 +08:00
## Quick Start (GPU)
For NVIDIA GPU acceleration on AI tools (background removal, upscaling, face enhancement, OCR):
```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
2026-03-22 21:00:37 +08:00
services :
2026-04-24 18:02:21 +08:00
SnapOtter :
2026-04-25 01:25:45 +08:00
image : snapotter/snapotter:latest
2026-04-24 18:02:21 +08:00
container_name : SnapOtter
2026-03-22 21:00:37 +08:00
ports :
- "1349:1349"
volumes :
2026-04-24 18:02:21 +08:00
- SnapOtter-data:/data
- SnapOtter-workspace:/tmp/workspace
2026-03-22 21:00:37 +08:00
environment :
- AUTH_ENABLED=true
- DEFAULT_USERNAME=admin
- DEFAULT_PASSWORD=admin
restart : unless-stopped
2026-04-21 10:19:08 +08:00
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"
2026-03-22 21:00:37 +08:00
volumes :
2026-04-24 18:02:21 +08:00
SnapOtter-data :
SnapOtter-workspace :
2026-03-22 21:00:37 +08:00
```
```bash
2026-04-21 10:19:08 +08:00
docker compose -f docker-compose-gpu.yml up -d
2026-03-22 21:00:37 +08:00
```
2026-04-21 10:19:08 +08:00
Check GPU detection in the logs:
2026-03-22 21:00:37 +08:00
2026-04-21 10:19:08 +08:00
```bash
2026-04-24 18:02:21 +08:00
docker logs SnapOtter 2>& 1 | head -20
2026-04-21 10:19:08 +08:00
# Look for: [INFO] GPU detected — AI tools will use CUDA acceleration
```
2026-04-16 17:29:50 +08:00
2026-04-21 10:19:08 +08:00
## Hardware Requirements
2026-03-22 21:00:37 +08:00
2026-04-25 00:49:25 +08:00
These numbers come from benchmarks run across four systems (Apple M2 Max, AMD Ryzen 5 7500F + RTX 4070, Intel i7-7600U, Docker Desktop on Windows).
2026-04-23 20:50:38 +08:00
### Quick Reference
| Tier | Use Case | CPU | RAM | GPU | Storage |
|------|----------|-----|-----|-----|---------|
| Minimum | Core tools, single user | 1 core | 1 GB | None | 5 GB |
| Recommended | All tools + AI on CPU | 4 cores | 4 GB | None | 20 GB |
| Full | All tools + AI on GPU | 4+ cores | 8 GB | NVIDIA 8 GB+ | 30 GB |
### Minimum (core image tools)
2026-03-22 21:00:37 +08:00
2026-04-21 10:19:08 +08:00
| Resource | Requirement |
|---|---|
2026-04-23 20:50:38 +08:00
| CPU | 1 core |
2026-04-21 10:19:08 +08:00
| RAM | 1 GB |
| Disk | 3 GB (image) + 1 GB (data volume) |
| GPU | Not required |
2026-03-22 21:00:37 +08:00
2026-04-23 20:50:38 +08:00
All 35 non-AI tools (resize, crop, rotate, convert, compress, watermark, collage, etc.) run on any hardware. Most operations complete in under 1 second even on a single core. The exception is AVIF encoding, which takes ~27s on 1 core but drops to ~5s on 4 cores.
2026-03-22 21:00:37 +08:00
2026-04-23 20:50:38 +08:00
```yaml
deploy :
resources :
limits :
cpus : '1'
memory : 1G
```
### Recommended (AI tools on CPU)
| Resource | Requirement |
|---|---|
| CPU | 4 cores |
| RAM | 4 GB |
| Disk | 3 GB (image) + 14 GB (AI models) + workspace |
| GPU | Not required (CPU fallback) |
AI tools work on CPU but are significantly slower. Some tools are practical on CPU, others are not:
| AI Tool | CPU Time | Usable? |
|---|---|---|
| blur-faces, smart-crop, red-eye-removal | 2-5s | Yes |
| remove-background | 37-41s | Marginal (long wait) |
| upscale (small image) | 22s | Marginal |
| upscale (large image) | 241s | No |
| enhance-faces, colorize, noise-removal | 30-90s | Marginal to No |
AI model download sizes:
| Bundle | Disk Size |
|---|---|
| Background removal | 3-4 GB |
| Upscale + Face enhance + Noise removal | 4-5 GB |
| Face detection | 200-300 MB |
| Object eraser + Colorize | 1-2 GB |
| OCR | 3-4 GB |
| Photo restoration | 800 MB - 1 GB |
| **All bundles** | ** ~14 GB** |
```yaml
deploy :
resources :
limits :
cpus : '4'
memory : 4G
```
### Full (AI tools on GPU)
2026-03-22 21:00:37 +08:00
2026-04-21 10:19:08 +08:00
| Resource | Requirement |
|---|---|
| CPU | 4+ cores |
2026-04-23 20:50:38 +08:00
| RAM | 8 GB |
| GPU | NVIDIA with 8+ GB VRAM (12 GB recommended) |
2026-04-21 10:19:08 +08:00
| Disk | 30 GB total |
2026-03-28 11:19:09 +08:00
2026-04-23 20:50:38 +08:00
GPU acceleration gives 3-13,000x speedup depending on the operation. Measured on an RTX 4070 vs Intel i7-7600U:
| AI Tool | GPU Time | CPU Time | Speedup |
|---|---|---|---|
| noise-removal (quick) | 17ms | 228s | 13,400x |
| blur-faces | 0.27s | 27s | 100x |
| upscale 2x | 6.3s | >300s (timeout) | 47x+ |
| enhance-faces (GFPGAN) | 2.3s | 28s | 12x |
| remove-background | 5-10s | 21-41s | 3-8x |
| OCR (best) | 70s | 243s | 3.5x |
| restore-photo | 31s | 90s | 2.9x |
| colorize | 10s | 13s | 1.3x |
Peak VRAM usage reaches 7.5 GB during upscale with face enhancement. A 6 GB GPU works for most AI tools individually but will fail on upscale. 8-12 GB VRAM handles everything.
```yaml
deploy :
resources :
limits :
cpus : '4'
memory : 8G
reservations :
devices :
- driver : nvidia
count : all
capabilities : [ gpu]
```
### Concurrent Users
Benchmarked with parallel resize requests on a large image (Mac M2 Max, 10 Docker CPUs):
| Concurrent Users | Avg Response Time | Errors |
|---|---|---|
| 1 | 0.28s | 0 |
| 5 | 0.54s | 0 |
| 10 | 1.08s | 0 |
| 20 | 2.10s | 0 |
The server scales linearly with no errors or crashes up to 20 concurrent requests.
### Supported Image Formats
| Format | Read | Write | Notes |
|---|---|---|---|
| JPEG | Yes | Yes | |
| PNG | Yes | Yes | |
| WebP | Yes | Yes | |
| AVIF | Yes | Yes | Encode is CPU-intensive (~5s on 4 cores for a large image) |
| GIF | Yes | Yes | Animated GIF supported |
| TIFF | Yes | Yes | Multi-page supported |
| SVG | Yes | No | Rasterized on input, sanitized for security |
| HEIC | Yes | No | Decoded via heif-dec (~0.4s) |
| HEIF | Yes | No | Very slow decode (~15s) |
| DNG (RAW) | Yes (Linux) | No | Decoded via dcraw, not available on macOS |
| PSD | Yes | No | Decoded via ImageMagick |
| HDR | Yes | No | Tone-mapped on decode |
| TGA | Yes | No | Decoded via ImageMagick |
| ICO | Yes | Yes | Via favicon tool |
| PDF | Yes | Yes | Via pdf-to-image / image-to-pdf tools |
Not supported: BMP (V4/V5 headers), JPEG XL (JXL), EXR (missing decode delegate in Docker image).
### Known Limitations
- **Content-aware resize** crashes on large images (>5 MP) due to a limitation in the caire binary. Works fine with smaller images.
- **HEIF decode** takes 13-23 seconds. HEIC (Apple's variant) is much faster at 0.3-0.9 seconds.
- **OCR Japanese** fails on CPU due to a PaddlePaddle MKLDNN bug. Works on GPU.
- **Upscale** times out on CPU for anything beyond small images. GPU required for practical use.
- **CodeFormer** face enhancement is significantly slower than GFPGAN (53s vs 2s on GPU). GFPGAN is recommended for most use cases.
2026-03-28 11:19:09 +08:00
2026-03-22 21:00:37 +08:00
## Volumes
2026-04-21 10:19:08 +08:00
| Mount | Purpose | Required? |
|---|---|---|
| `/data` | SQLite database, AI models, Python venv, user files | **Yes** — data loss without it |
| `/tmp/workspace` | Temporary processing files (auto-cleaned) | Recommended |
2026-03-22 21:00:37 +08:00
2026-04-21 10:19:08 +08:00
### Bind mounts vs. named volumes
2026-03-22 21:00:37 +08:00
2026-04-21 10:19:08 +08:00
**Named volumes** (recommended) — Docker manages permissions automatically:
```yaml
volumes :
2026-04-24 18:02:21 +08:00
- SnapOtter-data:/data
2026-03-22 21:00:37 +08:00
```
2026-04-21 10:19:08 +08:00
**Bind mounts** — You manage permissions. Set `PUID` /`PGID` to match your host user:
```yaml
volumes :
2026-04-24 18:02:21 +08:00
- ./SnapOtter-data:/data
2026-04-21 10:19:08 +08:00
environment :
- PUID=1000 # Your host UID (run: id -u)
- PGID=1000 # Your host GID (run: id -g)
```
2026-03-22 21:00:37 +08:00
2026-04-21 10:19:08 +08:00
## Environment Variables
| Variable | Default | Description |
|---|---|---|
| `AUTH_ENABLED` | `true` | Enable/disable login requirement |
| `DEFAULT_USERNAME` | `admin` | Initial admin username |
| `DEFAULT_PASSWORD` | `admin` | Initial admin password (forced change on first login) |
| `MAX_UPLOAD_SIZE_MB` | `0` (unlimited) | Per-file upload limit |
| `MAX_BATCH_SIZE` | `0` (unlimited) | Max files per batch request |
| `RATE_LIMIT_PER_MIN` | `0` (disabled) | API requests per minute per IP |
| `MAX_USERS` | `0` (unlimited) | Maximum user accounts |
| `TRUST_PROXY` | `true` | Trust X-Forwarded-For headers from reverse proxy |
| `PUID` | `999` | Run as this UID (for bind mount permissions) |
| `PGID` | `999` | Run as this GID (for bind mount permissions) |
| `LOG_LEVEL` | `info` | Log verbosity: fatal, error, warn, info, debug, trace |
| `CONCURRENT_JOBS` | `0` (auto) | Max parallel AI processing jobs |
| `SESSION_DURATION_HOURS` | `168` | Login session lifetime (7 days) |
| `CORS_ORIGIN` | (empty) | Comma-separated allowed origins, or empty for same-origin |
## Health Check
The container includes a built-in health check:
```bash
# Check container health status
2026-04-24 18:02:21 +08:00
docker inspect --format= '{{.State.Health.Status}}' SnapOtter
2026-04-21 10:19:08 +08:00
# Manual health check
curl http://localhost:1349/api/v1/health
# {"status":"healthy","version":"1.15.9"}
```
## Reverse Proxy
2026-04-24 18:02:21 +08:00
SnapOtter sets `TRUST_PROXY=true` by default so rate limiting and logging use the real client IP from `X-Forwarded-For` headers.
2026-04-21 10:19:08 +08:00
### Nginx
2026-03-22 21:00:37 +08:00
```nginx
server {
listen 80 ;
server_name images.example.com ;
2026-04-21 10:19:08 +08:00
# Match MAX_UPLOAD_SIZE_MB (0 = nginx default 1M, so set high for unlimited)
client_max_body_size 500M ;
2026-03-22 21:00:37 +08:00
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 ;
2026-04-21 10:19:08 +08:00
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 ;
2026-03-22 21:00:37 +08:00
}
}
```
2026-04-21 10:19:08 +08:00
### Nginx Proxy Manager
1. Add a new Proxy Host
2. Set Domain Name to your domain
2026-04-24 18:02:21 +08:00
3. Set Scheme to `http` , Forward Hostname to `SnapOtter` (or your container IP), Forward Port to `1349`
2026-04-21 10:19:08 +08:00
4. Enable WebSocket support
5. Under Advanced, add: `client_max_body_size 500M;` and `proxy_buffering off;`
### Traefik
```yaml
2026-04-24 18:02:21 +08:00
# Add these labels to the SnapOtter service in docker-compose.yml
2026-04-21 10:19:08 +08:00
labels :
- "traefik.enable=true"
2026-04-24 18:02:21 +08:00
- "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"
2026-04-21 10:19:08 +08:00
# Increase upload limit (default 2MB is too low)
2026-04-24 18:02:21 +08:00
- "traefik.http.middlewares.snapotter-body.buffering.maxRequestBodyBytes=524288000"
- "traefik.http.routers.snapotter.middlewares=snapotter-body"
2026-04-21 10:19:08 +08:00
```
### Cloudflare Tunnels
```bash
cloudflared tunnel --url http://localhost:1349
```
Note: Cloudflare has a 100 MB upload limit on free plans. Set `MAX_UPLOAD_SIZE_MB=100` to match.
2026-03-22 21:00:37 +08:00
## CI/CD
2026-04-10 17:38:54 +08:00
The GitHub repository has three workflows:
2026-03-22 21:00:37 +08:00
2026-04-10 17:38:54 +08:00
- **ci.yml** -- Runs automatically on every push and PR. Lints, typechecks, tests, builds, and validates the Docker image (without pushing).
2026-04-25 01:25:45 +08:00
- **release.yml** -- Triggered manually via `workflow_dispatch` . Runs semantic-release to create a version tag and GitHub release, then builds a multi-arch Docker image (amd64 + arm64) and pushes to Docker Hub (`snapotter/snapotter` ) and GitHub Container Registry (`ghcr.io/snapotter-hq/snapotter` ).
2026-04-25 07:17:45 +08:00
- **deploy-docs.yml** -- Builds this documentation site and deploys it to Cloudflare Pages on push to `main` .
2026-03-22 21:00:37 +08:00
2026-04-10 17:38:54 +08:00
To create a release, go to **Actions > Release > Run workflow** in the GitHub UI, or run:
```bash
gh workflow run release.yml
```
Semantic-release determines the version from commit history. The `latest` Docker tag always points to the most recent release.