* fix(docker): pin CUDA base to 12.6 so the GPU image starts on R560+ drivers The amd64 base nvidia/cuda:12.9.2-cudnn-runtime bakes a cuda>=12.9 driver gate enforced by nvidia-container-toolkit at container start, so the image fails to launch on common production drivers (e.g. 570.x / CUDA 12.8). The AI bundles are all cu126 wheels and the image installs libcublas-12-6, so 12.9 was misaligned with the workload. Pin to nvidia/cuda:12.6.3-cudnn-runtime-ubuntu24.04 to match the wheels and lower the driver floor to R560+. * fix(ai): broaden OOM detection so the rembg lighter-model fallback fires onnxruntime/CUDA allocation failures surface as 'Failed to allocate memory for requested buffer', CUBLAS_STATUS_ALLOC_FAILED, or bad_alloc, not just 'out of memory'. The background-removal and transparency-fixer fallback-to-lighter-model paths only matched the literal 'out of memory', so the fallback was dead code and transparency-fixer (default birefnet-hr-matting) always failed with an allocation error. Add isMemoryAllocError() and use it in both checks. * fix(ai): use bundled PaddleOCR models so OCR runs offline ocr.py passed no model dirs to PaddleOCR, so PaddleX resolved models from ~/.paddlex and downloaded them from HuggingFace at runtime (slow first use, broken air-gapped), ignoring the models the OCR bundle ships in MODELS_PATH; it also pulled doc-orientation/unwarping models that are not bundled. Pin detection, recognition and textline models to the bundled dirs in MODELS_PATH (per language) and disable use_doc_orientation_classify / use_doc_unwarping, with per-component fallback when a model is absent. Verified: OCR runs with zero HuggingFace requests. * fix(docker): add CAP_KILL so container shutdown is graceful cap_drop: ALL without re-adding KILL meant tini (PID 1, root) could not forward SIGTERM to the gosu-dropped snapotter process (root minus CAP_KILL cannot signal a different UID). docker stop logged '[FATAL tini] forwarding signal: Operation not permitted', never delivered the signal, and fell back to SIGKILL after the 10s timeout. Add KILL to cap_add in both compose files. Verified: docker stop completes in 0s with SIGTERM delivered (exit 143) and no FATAL tini. * fix(ai): serialize bundle installs against AI jobs to prevent sidecar segfault A feature bundle install rewrites the shared Python venv (pip + copytree of site-packages/*.so) as a background subprocess, with no coordination against AI tool jobs that dlopen native libs (torch / onnxruntime CUDA) from the same venv; a job loading a shared object while it is overwritten segfaults the sidecar. Add a process-wide async mutex (venv-lock.ts): bridge.run() acquires it before every AI script and the install route holds it across the installer subprocess. Both run in the same Node process so a module-level lock suffices. Verified: concurrent install + AI job produces zero segfaults and the job serializes behind the install. * fix(ai): make the venv lock read/write so concurrent AI jobs are not serialized The first cut used an exclusive mutex, which (a) deferred the dispatcher spawn by a microtask and broke unit tests that synchronously drive the mocked spawn, and (b) serialized AI jobs against each other, removing the dispatcher's by-id request multiplexing. Make it a writer-preferring read/write lock: AI jobs are shared readers (with a synchronous fast path so spawn still happens in-tick) and a bundle install is the exclusive writer. Verified: all 764 AI unit tests pass. * fix(ai): degrade OCR to Tesseract on CPU-only hosts instead of segfaulting The amd64 AI bundle ships paddlepaddle-gpu, whose native libs dlopen libcuda.so.1 at import and segfault on a host without a GPU (libcuda is the driver lib, injected only by nvidia-container-toolkit on GPU hosts). The segfault crashed the shared long-lived AI dispatcher and, after a few attempts, tripped the bridge crash-recovery permanent-disable, wedging all AI until a container restart. The standalone ocr tool defaults to quality=balanced (PaddleOCR), so it hit this on every CPU-only deployment; ocr-pdf already hardcoded Tesseract and was unaffected. ocr.py now gates the PaddleOCR tiers on gpu_available(): balanced/best transparently fall back to fast (Tesseract, CPU-capable) when no usable GPU is present, and run_paddleocr_v5/run_paddleocr_vl refuse before importing paddle so the GPU build is never dlopen'd on CPU. GPU hosts are unchanged. Verified on a CPU-only Windows/WSL2 box: ocr returns Tesseract text across repeated runs with the dispatcher staying healthy (no wedge).
Note
SnapOtter v2.0.0 is coming soon. The current Docker image (
latest) is v1.x and includes image tools only. v2.0 adds 157 tools across image, video, audio, documents, and files. We're fixing a last-minute issue with local AI installs before publishing the new image. Stay tuned!
Key Features
- 157 tools across 5 modalities:
- Image (64): resize, crop, compress, convert, watermark, color adjust, beautify screenshots, generate memes, vectorize, GIF tools, find duplicates, passport photos, and more. Supports 55+ input formats (including 23 camera RAW formats) and 14 output formats
- Video (29): convert, compress, trim, resize, crop, merge, video-to-GIF, extract audio, stabilize, change FPS, burn/extract subtitles, and more
- Audio (17): convert, trim, normalize, volume, fade, pitch shift, silence removal, noise reduction, merge/split, waveform, and more
- Documents / PDF (37): merge, split, compress, convert (Word/Excel/PowerPoint/EPUB), protect/unlock, redact, watermark, page numbers, OCR, and more
- Files (10): CSV/JSON/XML/YAML conversion, CSV merge/split, chart maker, ZIP create/extract
- Image editor: Layer-based editor with brushes, shapes, adjustments, filters, curves, and keyboard shortcuts. Runs in your browser, processes on your hardware
- Local AI: Remove backgrounds, upscale images, restore and colorize old photos, erase objects, blur faces, enhance faces, extract text (OCR from images and PDFs), transcribe audio, auto-generate video subtitles, expand canvas, and fix transparency. All on your hardware, no internet required
- OIDC / SSO: Login with Google, GitHub, Okta, or any OpenID Connect provider
- 21 languages: English, Arabic, Chinese (Simplified & Traditional), Dutch, French, German, Hindi, Indonesian, Italian, Japanese, Korean, Polish, Portuguese, Russian, Spanish, Swedish, Thai, Turkish, Ukrainian, Vietnamese. RTL support for Arabic
- Pipelines: Chain tools into reusable workflows with unlimited steps. Import/export as JSON. Batch process unlimited files at once
- REST API: Every tool available via API with API key auth. Interactive docs at
/api/docs - Self-hosted stack: SnapOtter + Postgres 17 + Redis 8, run together with one
docker compose up. No external SaaS dependencies - Multi-arch: Runs on AMD64 and ARM64 (Intel, Apple Silicon, Raspberry Pi)
- Privacy first: Your files never leave your network. SnapOtter asks once whether you'd like to share anonymous product analytics (which tools are used, errors encountered, never file data). Change anytime in Settings, or set
ANALYTICS_ENABLED=falseto disable completely
Quick Start
SnapOtter runs as a small Docker Compose stack (app + Postgres 17 + Redis 8). Save this as compose.yaml:
services:
snapotter:
image: snapotter/snapotter:latest
ports: ["1349:1349"]
environment:
DATABASE_URL: postgres://snapotter:snapotter@postgres:5432/snapotter
REDIS_URL: redis://redis:6379
volumes:
- snapotter-data:/data
depends_on: [postgres, redis]
restart: unless-stopped
postgres:
image: postgres:17-alpine
environment:
POSTGRES_USER: snapotter
POSTGRES_PASSWORD: snapotter
POSTGRES_DB: snapotter
volumes: ["snapotter-pgdata:/var/lib/postgresql/data"]
restart: unless-stopped
redis:
image: redis:8-alpine
volumes: ["snapotter-redisdata:/data"]
restart: unless-stopped
volumes:
snapotter-data:
snapotter-pgdata:
snapotter-redisdata:
Then start the stack:
docker compose up -d
Have an NVIDIA GPU? Click here for GPU acceleration.
Use the GPU Compose file for GPU-accelerated background removal, upscaling, transcription, and OCR. See Docker Tags for the GPU Compose example and benchmarks.
Default credentials:
| Field | Value |
|---|---|
| Username | admin |
| Password | admin |
You will be asked to change your password on first login.
For Docker Compose, persistent storage, and other setup options, see the Getting Started Guide. For GPU acceleration and tag details, see Docker Tags.
Documentation
- Getting Started
- Configuration
- OIDC / SSO
- Deployment
- Supported Formats
- Docker Tags
- REST API
- AI Engine
- Image Engine
- Architecture
- Database
- Developer Guide
- Contributing
- Translation Guide
Contributing
We welcome bug reports, feature ideas, and pull requests. See CONTRIBUTING.md for the full guide, or jump in:
- Open an issue
- Submit a PR
- Join Discord for help and discussion
- Sponsor the project to keep SnapOtter free for everyone
Support SnapOtter
SnapOtter is built and maintained independently with no venture capital or corporate backing. Sponsorships fund infrastructure, keep releases flowing, and ensure the project stays free and open for everyone.
If SnapOtter saves you from paying for cloud file-processing services, consider supporting its development:
License
This project is dual-licensed under the AGPLv3 and a commercial license.
- AGPLv3 (free): You may use, modify, and distribute this software under the AGPLv3. If you run a modified version as a network service, you must make your source code available under the AGPLv3.
- Commercial license (paid): For use in proprietary software or SaaS products where AGPLv3 source-disclosure is not suitable, a commercial license is available. Contact us for pricing and terms.
See LICENSING.md for full details on the open-core boundary between AGPLv3 and commercial code.

