* feat(api): parse DATA_DIR from env for 1.x import auto-detection Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w * test(migrator): build 1.17.2 fixtures by replaying legacy migrations Discovered the legacy migrations seed a Default team (0005) and builtin roles (0007), so the replayed fixture carries them. Seed uses a distinct custom team. Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w * fix(migrator): self-adjusting column copy, jobs.status map, drop sessions, advisory lock The importer now inserts only the intersection of source and live target columns, so the three analytics_* columns 2.x dropped no longer break the first users INSERT (and future dropped columns are handled generically). jobs.status is mapped onto the 2.x enum (error->failed). Sessions are no longer migrated. A pg_advisory_xact_lock serializes concurrent replicas. Includes login-after-migrate and library assertions. Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w * test(migrator): CI drift guard fails when a required column is unfillable from 1.17.2 Introspects every NOT-NULL-no-default column of each migrated table in the current schema and asserts the engine can fill it from a real 1.17.2 source. Turns a future breaking schema change into a PR-time failure instead of a production import break. Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w * feat(migrator): orchestrator with detection, boot states, marker, blob count sqlite-import.ts owns source resolution (explicit path, 'off' sentinel, DATA_DIR probe), the four boot states (import/leftover/locked/none), the persisted sqlite_import marker, and a read-only library-blob count. runBootImport wires them together and catches TargetNonEmptyError as a benign multi-replica skip. Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w * feat(api): route boot through the 1.x import orchestrator; hide marker from non-admins index.ts now calls runBootImport (which owns detection + the four boot states) instead of the inline SQLITE_MIGRATE_PATH block. The sqlite_import marker is added to SENSITIVE_KEYS (but not REDACTED_KEYS) so admins see the counts for the banner while non-admins don't see the key at all. Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w * feat(migrator): add analyzeSqlite + dry-run/verify CLI analyzeSqlite is a read-only pre-flight (no live Postgres): per-table row counts, library-blob presence, and out-of-enum job statuses. The migrate:sqlite CLI now lives in the orchestrator and supports --dry-run/--verify (prints the analysis and exits without writing) alongside the existing import and --force. Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w * docs: add 1.x to 2.0 upgrade guide; fix volume-name casing New apps/docs upgrade guide covering auto-detect, the SQLITE_MIGRATE_PATH override + off opt-out, the dry-run, what carries over, locked-state recovery, and non-destructive rollback. Leads with 'back up the WHOLE /data volume, not just snapotter.db' because 1.x WAL mode leaves data in snapotter.db-wal (surfaced by the real-image upgrade test). Standardizes README/DOCKERHUB compose volume names on the canonical SnapOtter-data casing so they match the repo compose and don't orphan an upgrader's volume. Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w * feat(web): admin 1.x migration banner + 21-locale strings A one-time admin banner reads the sqlite_import marker from /v1/settings and shows the import result (user + saved-file counts) on success, or a warning when a 1.x database was found but not imported. Dismissal persists to a sqlite_import.dismissedAt settings key. shouldShowMigrationBanner/parseMigrationMarker sit in feedback.ts with the other shouldShow helpers; strings added to en.ts and all 20 other locales. Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w * style(landing): biome-format Hero.astro trustBadges array Pre-existing formatting drift on main (its Lint check was skipped on the merge that introduced it); this PR's full Lint run surfaced it. Formatting-only, applied via the repo's own biome formatter to unblock the required Lint check. Claude-Session: https://claude.ai/code/session_01721WHAUGxnVk22qEeTub7w
10 KiB
SnapOtter
Open-source, self-hostable file manipulation suite. 200+ tools across image, video, audio, documents, and files, plus a layer-based image editor and local AI. Everything runs on your own hardware, so your files never leave your network.
SnapOtter v2.0.0 is coming soon. The current
latestimage is v1.x and includes image tools only. v2.0 adds 200+ tools across image, video, audio, documents, and files. We are fixing a last-minute issue with local AI installs before publishing the new image. Stay tuned.
What is SnapOtter?
SnapOtter is a privacy-first alternative to cloud file-processing services. Convert, compress, edit, and transform files in your browser while the work happens on a server you control. No uploads to third parties, no per-file pricing, no SaaS lock-in. It runs as a single container (embedded PostgreSQL 17 and Redis 8) or as a small Docker Compose stack for production, and works on AMD64 and ARM64.
Quick start
One command, no setup. The container starts an embedded PostgreSQL 17 and Redis 8 on the loopback interface and keeps all data in the SnapOtter-data volume:
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest
The same image is also published to GHCR as ghcr.io/snapotter-hq/snapotter:latest. Embedded mode turns off automatically as soon as you set DATABASE_URL, so moving to the Compose stack later is just a config change.
Open http://localhost:1349 and log in.
| Field | Value |
|---|---|
| Username | admin |
| Password | admin |
You will be asked to change your password on first login.
Production: Docker Compose
For production, run PostgreSQL and Redis in their own containers. 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
Change DEFAULT_PASSWORD for any non-local deployment.
Supported tags and platforms
| Tag | Description |
|---|---|
latest |
Latest release |
1.11.0 |
Exact version |
1.11 |
Latest patch in the 1.11.x line |
1 |
Latest minor in the 1.x line |
| Architecture | GPU support | Notes |
|---|---|---|
linux/amd64 |
NVIDIA CUDA | Full CUDA acceleration for AI tools |
linux/arm64 |
CPU only | Raspberry Pi 4/5, Apple Silicon via Docker Desktop |
The same image runs on CPU or NVIDIA CUDA. Intel/AMD iGPU acceleration through VA-API, Quick Sync, or OpenCL is not supported for AI inference today; those systems run AI tools on CPU. See Docker Tags for benchmarks and version-pinning details.
Features
- 200+ tools across 5 modalities
- Image (105): 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 (57): convert, compress, trim, resize, crop, merge, video-to-GIF, extract audio, stabilize, change FPS, burn or extract subtitles, and more.
- Audio (27): convert, trim, normalize, volume, fade, pitch shift, silence removal, noise reduction, merge or split, waveform, and more.
- Documents / PDF (28): merge, split, compress, convert (Word, Excel, PowerPoint, EPUB), protect or unlock, redact, watermark, page numbers, OCR, and more.
- Files (23): CSV, JSON, XML, and YAML conversion, CSV merge or split, chart maker, ZIP create or 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: log in with Google, GitHub, Okta, or any OpenID Connect provider.
- 21 languages: including Arabic (with RTL support), Chinese (Simplified and Traditional), French, German, Hindi, Japanese, Korean, Portuguese, Russian, Spanish, and more.
- Pipelines: chain tools into reusable workflows with unlimited steps. Import and 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. - Privacy first: your files never leave your network. SnapOtter asks once whether you want to share anonymous product analytics (which tools are used and errors encountered, never file data). Change it anytime in Settings, or set
ANALYTICS_ENABLED=falseto disable it completely.
Configuration
Common environment variables (set on the snapotter service). Use 0 for unlimited or auto where noted.
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
(required) | PostgreSQL connection string. |
REDIS_URL |
(required) | Redis connection string. |
AUTH_ENABLED |
true |
Set false to run without login (creates a synthetic anonymous admin). |
DEFAULT_USERNAME |
admin |
Initial admin username. |
DEFAULT_PASSWORD |
admin |
Initial admin password. Change this for any non-local deployment. |
MAX_UPLOAD_SIZE_MB |
0 |
Max upload size in MB. 0 is unlimited. |
MAX_BATCH_SIZE |
0 |
Max files per batch. 0 is unlimited. |
CONCURRENT_JOBS |
0 |
Worker concurrency. 0 auto-detects from CPU. |
PROCESSING_TIMEOUT_S |
0 |
Per-job timeout in seconds. 0 is unlimited. |
RATE_LIMIT_PER_MIN |
300 |
API requests per minute per client. |
SESSION_DURATION_HOURS |
168 |
Login session length in hours. |
TRUST_PROXY |
true |
Trust X-Forwarded-* headers behind a reverse proxy. |
ANALYTICS_ENABLED |
asks on first run | Set false to disable anonymous product analytics entirely. |
EXTERNAL_URL |
Public URL of the instance, required for OIDC redirects. | |
SQLITE_MIGRATE_PATH |
Path to a 1.x SQLite database to import on first boot. |
OIDC, SSO, S3 storage, and the full variable reference are documented in Configuration and OIDC / SSO.
Volumes
| Path | Purpose |
|---|---|
/data |
AI models and persistent user files; in single-container mode also the embedded PostgreSQL and Redis data. Back this up. |
/tmp/workspace |
Temporary processing files (auto-cleaned). |
In the Compose stack, PostgreSQL and Redis keep their own volumes (SnapOtter-pgdata, SnapOtter-redisdata).
Ports
| Port | Purpose |
|---|---|
1349 |
Web UI and REST API |
NVIDIA CUDA acceleration
The amd64 image bundles CUDA. With an NVIDIA GPU and the NVIDIA Container Toolkit installed, add this to the snapotter service to accelerate background removal, upscaling, OCR, and transcription:
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
The image auto-detects NVIDIA CUDA at runtime and falls back to CPU when CUDA is unavailable. Mapping /dev/dri for Intel or AMD GPUs does not accelerate SnapOtter AI tools today. Benchmarks are in Docker Tags.
Upgrading from SnapOtter 1.x
v1.x stored data in SQLite. To import it into the new PostgreSQL stack, set SQLITE_MIGRATE_PATH=/data/snapotter.db on the snapotter service for the first boot, then remove the variable once the migration succeeds. Your files and settings are preserved.
Documentation
- Getting Started
- Configuration
- Deployment
- Docker Tags and GPU
- OIDC / SSO
- REST API
- Source on GitHub
- Report an issue
License
Dual-licensed under AGPLv3 and a commercial license. Use, modify, and self-host freely under the AGPLv3; if you run a modified version as a network service, you must make your source available under the AGPLv3. For proprietary or SaaS use where source disclosure is not suitable, a commercial license is available. Contact contact@snapotter.com.

