--- description: All SnapOtter environment variables with defaults. Configure auth, storage, AI models, analytics, and more. --- # Configuration {#configuration} All configuration is done through environment variables. Every variable has a sensible default, so SnapOtter works out of the box without setting any of them. ## Environment variables {#environment-variables} ### Server {#server} | Variable | Default | Description | |---|---|---| | `PORT` | `1349` | Port the server listens on. | | `RATE_LIMIT_PER_MIN` | `1000` | Maximum requests per minute per IP. Set to 0 to disable rate limiting. | | `CORS_ORIGIN` | (empty) | Comma-separated allowed origins for CORS, or empty for same-origin only. | | `LOG_LEVEL` | `info` | Log verbosity. One of: `fatal`, `error`, `warn`, `info`, `debug`, `trace`. | | `TRUST_PROXY` | `loopback,linklocal,uniquelocal` | Which peers may set the client IP through `X-Forwarded-For`. The default believes only a private-network peer, so a reverse proxy on a Docker network or a LAN is trusted and a public client's forged header is not. Set `true` only when a proxy you control sits in front on a public address. | ### Authentication {#authentication} The two booleans below accept only `true` and `false`. Anything else, `1` or `yes` or `on`, fails validation and the server exits before it starts listening. | Variable | Default | Description | |---|---|---| | `AUTH_ENABLED` | `true` | Require a login. Set to `false` to run with no accounts at all, which grants every request admin rights, so keep that to a trusted network. | | `DEFAULT_USERNAME` | `admin` | Username for the initial admin account. Only used on first run. | | `DEFAULT_PASSWORD` | `admin` | Password for the initial admin account. Change this after first login. | | `MAX_USERS` | `0` (unlimited) | Maximum number of registered user accounts. Set to 0 for unlimited. | | `SESSION_DURATION_HOURS` | `168` | Login session lifetime in hours (default is 7 days). | | `SKIP_MUST_CHANGE_PASSWORD` | `false` | Set to `true` to skip the forced password-change prompt on first login. | ### Storage {#storage} | Variable | Default | Description | |---|---|---| | `STORAGE_MODE` | `local` | `local` or `s3`. S3 and MinIO need a license with the s3_storage feature plus the `S3_*` variables below. | | `DATABASE_URL` | `postgres://snapotter:snapotter@localhost:5432/snapotter` | PostgreSQL connection string. The Compose stack points this at its `postgres` service; leave it unset (together with `REDIS_URL`) to get embedded mode. | | `REDIS_URL` | `redis://localhost:6379` | Redis connection string (used for BullMQ job queues). Compose points this at its `redis` service. | | `WORKSPACE_PATH` | `./tmp/workspace` | Directory for temporary files during processing. Cleaned up automatically. The image sets `/tmp/workspace`. | | `FILES_STORAGE_PATH` | `./data/files` | Directory for persistent user files (uploaded images, saved results). The image sets `/data/files`. | ### S3 object storage {#s3-object-storage} Only read when `STORAGE_MODE=s3`. Miss any of the three required ones and startup fails with the name of the variable you left out. | Variable | Default | Description | |---|---|---| | `S3_BUCKET` | (empty) | Bucket that holds uploads and outputs. Required. | | `S3_ACCESS_KEY_ID` | (empty) | Access key. Required. In the container you can mount it instead, via `S3_ACCESS_KEY_ID_FILE`. | | `S3_SECRET_ACCESS_KEY` | (empty) | Secret key. Required. Same file convention: `S3_SECRET_ACCESS_KEY_FILE`. | | `S3_REGION` | `us-east-1` | Bucket region. | | `S3_ENDPOINT` | (empty) | Custom endpoint for MinIO, R2, Backblaze, and other S3-compatible stores. Empty means AWS. | | `S3_FORCE_PATH_STYLE` | `false` | Set to `true` for MinIO and anything else that wants `endpoint/bucket/key` instead of virtual-host addressing. | | `S3_PREFIX` | (empty) | Key prefix, so one bucket can hold several instances. | ### Encryption at rest {#encryption-at-rest} | Variable | Default | Description | |---|---|---| | `DATA_ENCRYPTION_KEY` | (empty) | 64 hex characters (32 bytes). Encrypts sensitive settings stored in the database. Anything that is not 64 hex characters is rejected at startup. | | `DATA_ENCRYPTION_KEY_PREVIOUS` | (empty) | The key you are rotating away from, same format. Set both during a rotation so existing rows still decrypt, then drop this one. | ### Embedded mode {#embedded-mode} Run the image with no `DATABASE_URL` and no `REDIS_URL` and it starts its own PostgreSQL 17 and Redis inside the container, bound to loopback, with all data on the `/data` volume. This restores the single-command `docker run` experience for quick start, homelab, and upgrades from 1.x. It is a convenience path, not a production deployment: for production, run the 3-container Compose stack with separate PostgreSQL and Redis. Embedded mode requires running the container as root and is incompatible with arbitrary-UID runtimes (OpenShift, Kubernetes `runAsNonRoot`); use Compose there. | Variable | Default | Description | |---|---|---| | `EMBEDDED` | `auto` | Auto-enabled when both `DATABASE_URL` and `REDIS_URL` are unset. Set to `0` to disable it (the app then fails fast if no external `DATABASE_URL`/`REDIS_URL` is set, rather than silently starting an in-container database). | | `REDIS_MAXMEMORY` | `512mb` | Memory cap for the embedded Redis (embedded mode only). Lower it on memory-constrained hosts such as a Raspberry Pi. | Upgrading from 1.x: put your old `snapotter.db` at `/data/snapotter.db` in the volume and embedded mode imports it into the embedded PostgreSQL on first boot. The import runs once; later boots skip it. Telemetry note: embedded mode inherits the image's analytics default like any other configuration. The published image ships with analytics on; build with `--build-arg SNAPOTTER_ANALYTICS=off`, or use the in-app admin opt-out, to disable it. ### Processing limits {#processing-limits} | Variable | Default | Description | |---|---|---| | `MAX_UPLOAD_SIZE_MB` | `0` (unlimited) | Maximum file size per upload in megabytes. Set to 0 for unlimited. The published image ships `0`; a source build starts at 100. | | `MAX_BATCH_SIZE` | `0` (unlimited) | Maximum number of files in a single batch request. Set to 0 for unlimited. The published image ships `0`; a source build starts at 100. | | `CONCURRENT_JOBS` | `0` (auto) | Number of batch jobs that run in parallel. Set to 0 to auto-detect based on available CPU cores. | | `MAX_MEGAPIXELS` | `0` (unlimited) | Maximum image resolution allowed in megapixels. Set to 0 for unlimited. | | `MAX_WORKER_THREADS` | `0` (auto) | Maximum worker threads for image processing. Set to 0 to auto-detect based on available CPU cores. | | `PROCESSING_TIMEOUT_S` | `0` (no limit) | Maximum processing time per request in seconds. Set to 0 for no timeout. | | `MAX_PIPELINE_STEPS` | `20` | Maximum number of steps in a pipeline. Set to 0 for no limit. | | `MAX_CANVAS_PIXELS` | `0` (no limit) | Maximum canvas size in pixels for output images. Set to 0 for no limit. | | `MAX_SVG_SIZE_MB` | `50` | Largest SVG accepted before sanitizing, in megabytes. `0` behaves differently here than in the rows around it. It removes the pre-parse size cap entirely rather than raising it, so leave this one set. | | `MAX_PDF_PAGES` | `0` (unlimited) | Maximum number of PDF pages for PDF-to-image conversion. Set to 0 for unlimited. | ### Cleanup {#cleanup} | Variable | Default | Description | |---|---|---| | `FILE_MAX_AGE_HOURS` | `72` | How long unsaved processing results (raw uploads and tool outputs) are kept before automatic deletion. Files you explicitly save to the Files library are not affected and persist until you delete them. | | `CLEANUP_INTERVAL_MINUTES` | `60` | How often the cleanup job runs. | ### Appearance {#appearance} | Variable | Default | Description | |---|---|---| | `DEFAULT_THEME` | `light` | Default theme for new sessions. `light`, `dark`, or `system`. | | `DEFAULT_LOCALE` | `en` | Default interface language. | | `DEFAULT_TOOL_VIEW` | `sidebar` | Default tool layout. `sidebar` or `fullscreen`. | ### Docker permissions {#docker-permissions} | Variable | Default | Description | |---|---|---| | `PUID` | `999` | Run the container process as this UID. Set to match your host user for bind mounts (`id -u`). | | `PGID` | `999` | Run the container process as this GID. Set to match your host group for bind mounts (`id -g`). | ## Docker example {#docker-example} ```yaml services: SnapOtter: image: snapotter/snapotter:latest ports: - "1349:1349" volumes: - SnapOtter-data:/data - SnapOtter-workspace:/tmp/workspace environment: - AUTH_ENABLED=true - DEFAULT_USERNAME=admin - DEFAULT_PASSWORD=changeme - DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter - REDIS_URL=redis://redis:6379 - MAX_UPLOAD_SIZE_MB=200 - CONCURRENT_JOBS=4 - FILE_MAX_AGE_HOURS=12 depends_on: postgres: condition: service_healthy redis: condition: service_healthy restart: unless-stopped postgres: image: postgres:17-alpine environment: POSTGRES_USER: snapotter POSTGRES_PASSWORD: snapotter # Change this for non-local deployments POSTGRES_DB: snapotter volumes: - SnapOtter-pgdata:/var/lib/postgresql/data restart: unless-stopped healthcheck: test: ["CMD-SHELL", "pg_isready -U snapotter -d snapotter"] interval: 10s timeout: 5s retries: 12 redis: image: redis:8-alpine command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"] volumes: - SnapOtter-redisdata:/data restart: unless-stopped healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 12 volumes: SnapOtter-data: SnapOtter-workspace: SnapOtter-pgdata: SnapOtter-redisdata: ``` ## Volumes {#volumes} The Docker Compose stack uses four volumes: - `/data` (app) - AI models, Python venv, and user files. Mount this to keep uploaded files and installed AI bundles across restarts. - `/tmp/workspace` (app) - Temporary storage for files being processed. This can be ephemeral, but mounting it avoids filling up the container's writable layer. - `SnapOtter-pgdata` (postgres) - PostgreSQL data directory. This holds all relational data (users, settings, pipelines, jobs, audit log). Back up via `pg_dump` or volume snapshot. - `SnapOtter-redisdata` (redis) - Redis append-only file for durable job queues.