mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
The Open File button in the Files section did nothing due to a race condition where the home page reset the file store on mount before files from handleOpenFile could render. Upload on the files page used fetch with no timeout, progress, or retry, causing silent failures on mobile and slow connections. SSE connections for job progress had no keepalive pings, allowing reverse proxies to kill idle streams.
409 lines
14 KiB
Markdown
409 lines
14 KiB
Markdown
---
|
|
description: Deploy SnapOtter to production with Docker. Hardware requirements, GPU setup, and reverse proxy configs for Nginx, Traefik, and Cloudflare.
|
|
---
|
|
|
|
# Deployment
|
|
|
|
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.
|
|
|
|
See [Docker Image](./docker-tags) for GPU setup, Docker Compose examples, and version pinning.
|
|
|
|
## Quick Start (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 # Database, 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)
|
|
|
|
# --- 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:
|
|
SnapOtter-data: # Named volume — Docker manages permissions automatically
|
|
SnapOtter-workspace:
|
|
```
|
|
|
|
```bash
|
|
docker compose up -d
|
|
```
|
|
|
|
The app is then available at `http://localhost:1349`.
|
|
|
|
> **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.
|
|
|
|
## 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
|
|
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
|
|
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"
|
|
|
|
volumes:
|
|
SnapOtter-data:
|
|
SnapOtter-workspace:
|
|
```
|
|
|
|
```bash
|
|
docker compose -f docker-compose-gpu.yml up -d
|
|
```
|
|
|
|
Check GPU detection in the logs:
|
|
|
|
```bash
|
|
docker logs SnapOtter 2>&1 | head -20
|
|
# Look for: [INFO] GPU detected — AI tools will use CUDA acceleration
|
|
```
|
|
|
|
## Hardware Requirements
|
|
|
|
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).
|
|
|
|
### 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)
|
|
|
|
| Resource | Requirement |
|
|
|---|---|
|
|
| CPU | 1 core |
|
|
| RAM | 1 GB |
|
|
| Disk | 3 GB (image) + 1 GB (data volume) |
|
|
| GPU | Not required |
|
|
|
|
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.
|
|
|
|
```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)
|
|
|
|
| Resource | Requirement |
|
|
|---|---|
|
|
| CPU | 4+ cores |
|
|
| RAM | 8 GB |
|
|
| GPU | NVIDIA with 8+ GB VRAM (12 GB recommended) |
|
|
| Disk | 30 GB total |
|
|
|
|
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
|
|
|
|
SnapOtter supports **55+ input formats** and **14 output formats**, including RAW files from 20+ camera brands, professional formats (PSD, EPS, OpenEXR, HDR), modern codecs (JPEG XL, AVIF, HEIC, QOI), and scientific/gaming formats (FITS, DDS).
|
|
|
|
See the [complete format list](/guide/supported-formats) for details on every supported format, decoder used, and available quality controls.
|
|
|
|
### 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.
|
|
|
|
## Volumes
|
|
|
|
| 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 |
|
|
|
|
### Bind mounts vs. named volumes
|
|
|
|
**Named volumes** (recommended) — Docker manages permissions automatically:
|
|
```yaml
|
|
volumes:
|
|
- SnapOtter-data:/data
|
|
```
|
|
|
|
**Bind mounts** — You manage permissions. Set `PUID`/`PGID` to match your host user:
|
|
```yaml
|
|
volumes:
|
|
- ./SnapOtter-data:/data
|
|
environment:
|
|
- PUID=1000 # Your host UID (run: id -u)
|
|
- PGID=1000 # Your host GID (run: id -g)
|
|
```
|
|
|
|
## 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
|
|
docker inspect --format='{{.State.Health.Status}}' SnapOtter
|
|
|
|
# Manual health check
|
|
curl http://localhost:1349/api/v1/health
|
|
# {"status":"healthy","version":"x.y.z"}
|
|
```
|
|
|
|
## Reverse Proxy
|
|
|
|
SnapOtter sets `TRUST_PROXY=true` by default so rate limiting and logging use the real client IP from `X-Forwarded-For` headers.
|
|
|
|
### 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. Add a new Proxy Host
|
|
2. Set Domain Name to your domain
|
|
3. Set Scheme to `http`, Forward Hostname to `SnapOtter` (or your container IP), Forward Port to `1349`
|
|
4. Enable WebSocket support
|
|
5. Under Advanced, add: `client_max_body_size 500M;` and `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
|
|
|
|
```caddyfile
|
|
images.example.com {
|
|
reverse_proxy localhost:1349 {
|
|
flush_interval -1
|
|
transport http {
|
|
read_timeout 300s
|
|
write_timeout 300s
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
`flush_interval -1` disables response buffering, which is required for SSE progress events (batch processing, AI tools, feature installs). The extended timeouts allow large file uploads to complete without Caddy closing the connection early.
|
|
|
|
### 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.
|
|
|
|
## CI/CD
|
|
|
|
The GitHub repository has three workflows:
|
|
|
|
- **ci.yml** -- Runs automatically on every push and PR. Lints, typechecks, tests, builds, and validates the Docker image (without pushing).
|
|
- **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`).
|
|
- **deploy-docs.yml** -- Builds this documentation site and deploys it to Cloudflare Pages on push to `main`.
|
|
|
|
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.
|