A release-readiness QA pass over the whole product. The commits split into defects a user would hit and gates that were reporting green while measuring nothing. ## Fixes that change behaviour Rate limiting was bypassable on every install: TRUST_PROXY defaulted to true, so request.ip came from a client-set header and a forged X-Forwarded-For got past the login limiter. The default is now a private-network trust list. A transient Postgres outage stranded in-flight jobs, leaving finished output on disk with no row pointing at it. A reconciler now resolves those rows and adopts the bytes rather than dropping the work. A Redis connection that moved to a new address wedged every read-blocked consumer, so completions stopped signalling while health still answered 200. Socket timeouts plus subscriber pings recover it. Installing more than one AI bundle left the shared venv multi-versioned and silently broke three tools. The installer now reconciles distributions to one version each. Converting an image to JXL at quality 1 through 4 returned a 500, because libjxl 0.7 rejects the distance those values compute. The quality is floored at what the encoder honours. A missing ffmpeg was also reported to the user as a corrupt upload; it now says the engine is unavailable. RAW uploads reached an unpatched LibRaw on arm64, so it is built from source at 0.22.2, and the release scan was split so it can fail on an unfixed critical instead of hiding it behind ignore-unfixed. ## Gates that could not fail Two mutation lanes ran zero mutants because Stryker crawled the gitignored docs build; coverage discarded its whole report on any failing test; the lint gate skipped root tests, scripts, and two workspaces; and several generated matrices counted a host missing ffmpeg as a passing tool. Each now measures what it claims. Full evidence and the outstanding release items are tracked locally and are not part of this branch.
7.1 KiB
description
| description |
|---|
| Install SnapOtter with Docker in one command. Includes Docker Compose setup, building from source, and a full feature overview. |
Getting Started
::: tip Try before installing Explore the full UI at demo.snapotter.com - no signup or install required. :::
Quick Start
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data snapotter/snapotter:latest
This single container runs everything it needs: with no DATABASE_URL set, it starts its own PostgreSQL and Redis on the loopback interface (embedded mode) and keeps all data in the SnapOtter-data volume. It is the fastest way to try SnapOtter or self-host on a homelab. For production, use the canonical Docker Compose stack, which keeps PostgreSQL and Redis in their own containers. Embedded mode runs as root (the default) and turns off automatically as soon as you set DATABASE_URL.
Installing on a Raspberry Pi, an old laptop, or a small VPS? See Low-Resource Setups for a tuned walkthrough and what to expect from constrained hardware.
You will be asked to change your password on first login.
::: tip Anonymous Product Analytics SnapOtter includes anonymous product analytics by default. To turn it off, open Settings → System → Privacy and switch off Anonymous Product Analytics. It stops immediately for the whole instance.
You can also set the environment variable SNAPOTTER_TELEMETRY=0 (false and off work too) to disable all telemetry for the instance without a rebuild.
Error monitoring is powered by Sentry, which sponsors SnapOtter through its open-source program.
For details about what is collected, see What SnapOtter collects. :::
::: tip NVIDIA CUDA acceleration
Add --gpus all for NVIDIA CUDA-accelerated background removal, upscaling, face enhancement, and restoration. OCR remains CPU-based and works in the same image with or without GPU access:
docker run -d --name SnapOtter -p 1349:1349 --gpus all -v SnapOtter-data:/data snapotter/snapotter:latest
Requires the NVIDIA Container Toolkit. Falls back to CPU automatically when CUDA is unavailable. Intel/AMD iGPU acceleration through VA-API, Quick Sync, or OpenCL is not supported for AI inference today. See Docker Tags for benchmarks. If AI tools run on CPU despite --gpus all, see Verify GPU acceleration.
:::
::: details Also on GHCR
docker run -d --name SnapOtter -p 1349:1349 -v SnapOtter-data:/data ghcr.io/snapotter-hq/snapotter:latest
Both registries publish the same image on every release. :::
Docker Compose
Use the production file maintained and tested with each release instead of copying an abbreviated Compose example from this page:
install -d -m 700 snapotter && cd snapotter
curl --proto '=https' --tlsv1.2 -fsSLo docker-compose.yml \
https://raw.githubusercontent.com/snapotter-hq/SnapOtter/v2.1.0/docker/docker-compose.yml
# Keep generated service credentials out of shell history and world-readable files.
umask 077
POSTGRES_PASSWORD="$(openssl rand -hex 32)"
REDIS_PASSWORD="$(openssl rand -hex 32)"
printf 'POSTGRES_PASSWORD=%s\nREDIS_PASSWORD=%s\n' \
"$POSTGRES_PASSWORD" "$REDIS_PASSWORD" > .env
docker compose -f docker-compose.yml pull
docker compose -f docker-compose.yml up -d --no-build
The canonical docker/docker-compose.yml includes all four runtime volumes, health checks, resource limits, durable Redis configuration, pinned database/cache images, and the current container hardening. Change the default admin password immediately after first login. For a reproducible deployment, pin the SnapOtter application image to the release tag or digest you verified instead of following latest.
See Configuration for all environment variables and Security & Hardening for secrets, network policy, and backup guidance.
Build from Source
Prerequisites: Node.js 22.22+, pnpm 9+, Docker (for Postgres + Redis), Python 3.11+ (for AI features), Git.
git clone https://github.com/snapotter-hq/SnapOtter.git
cd SnapOtter
docker compose -f docker-compose.dev.yml up -d # start Postgres + Redis
pnpm install
pnpm dev
- Frontend: http://localhost:1351
- Backend: http://localhost:13490
What You Can Do
File Processing (200+ Tools)
| Modality | Count | Example Tools |
|---|---|---|
| Image | 107 | Resize, Crop, Compress, Convert, Remove Background, Upscale, OCR, Watermark, Collage, Colorize, GIF Tools, format presets |
| Video | 57 | Trim, Crop, Compress, Convert, Merge, Extract Audio, Auto Subtitles, Video to GIF, Resize, Stabilize, format presets |
| Audio | 27 | Trim, Merge, Convert, Normalize, Noise Reduction, Transcribe, Pitch Shift, Fade, Ringtone Maker, format presets |
| PDF / Document | 29 | Merge, Split, Compress, OCR, Watermark, Redact, Word to PDF, Excel to PDF, Rotate, Protect, Repair |
| Files | 23 | CSV to JSON, JSON to XML, Merge CSVs, Split CSV, Create ZIP, Extract ZIP, Chart Maker, YAML/JSON |
Pipelines
Chain tools into multi-step workflows and apply them to one image or a whole batch:
- Open Pipelines in the sidebar.
- Add steps (any tool, any settings).
- Run on a single file - or an entire batch at once.
- Save the pipeline for later reuse.
Pipelines allow 20 steps by default. Set MAX_PIPELINE_STEPS=0 to make the limit unlimited.
File Library
Every file you process can be saved to your Files library. SnapOtter tracks the full version history so you can trace every processing step from the original upload to the final output.
Saving is explicit: results you save to the library are kept until you delete them, while results you process and leave unsaved are cleared automatically after 72 hours (configurable via FILE_MAX_AGE_HOURS).
REST API & API Keys
Every tool is accessible via HTTP:
curl -X POST http://localhost:1349/api/v1/tools/image/resize \
-H "Authorization: Bearer si_<your-api-key>" \
-F "file=@photo.jpg" \
-F 'settings={"width":800,"height":600,"fit":"cover"}'
Generate API keys under Settings → API Keys. See the REST API reference for all endpoints, or visit http://localhost:1349/api/docs for the interactive reference.
Multi-User & Teams
Enable multiple users with role-based access control:
- Admin: full access - manage users, teams, settings, all files/pipelines/API keys
- User: use tools, manage own files/pipelines/API keys
Create teams under Settings → Teams to group users.
Set AUTH_ENABLED=true (or false for single-user/self-use without login).