# Stirling Image — Complete Documentation Self-hosted, open-source image processing platform with 30+ tools. Runs in a single Docker container. Resize, compress, convert, remove backgrounds, upscale, run OCR, and more — without sending images to external services. - GitHub: https://github.com/stirling-image/stirling-image - Docs: https://stirling-image.github.io/stirling-image/ - License: MIT --- # Getting Started ## Run with Docker The fastest way to get Stirling Image running: ```bash docker run -d \ --name stirling-image \ -p 1349:1349 \ -v stirling-data:/data \ stirlingimage/stirling-image:latest ``` Open `http://localhost:1349` in your browser. Log in with `admin` / `admin`. ## Run with Docker Compose Create a `docker-compose.yml`: ```yaml services: stirling-image: image: stirlingimage/stirling-image:latest container_name: stirling-image ports: - "1349:1349" volumes: - stirling-data:/data - stirling-workspace:/tmp/workspace environment: - AUTH_ENABLED=true - DEFAULT_USERNAME=admin - DEFAULT_PASSWORD=admin restart: unless-stopped volumes: stirling-data: stirling-workspace: ``` ```bash docker compose up -d ``` ## Build from source Requirements: Node.js 22+, pnpm 9+, Python 3.10+ ```bash git clone https://github.com/stirling-image/stirling-image.git cd stirling-image pnpm install pnpm dev ``` This starts both the API server and the React frontend at `http://localhost:1349`. --- # Architecture Stirling Image is a monorepo managed with pnpm workspaces and Turborepo. Everything ships as a single Docker container. ## Project structure ``` Stirling-Image/ ├── apps/ │ ├── api/ # Fastify backend │ ├── web/ # React + Vite frontend │ └── docs/ # VitePress documentation site ├── packages/ │ ├── image-engine/ # Sharp-based image operations │ ├── ai/ # Python AI model bridge │ └── shared/ # Types, constants, i18n └── docker/ # Dockerfile and Compose config ``` ## Packages ### @stirling-image/image-engine Core image processing library built on Sharp. Handles all non-AI operations: resize, crop, rotate, flip, convert, compress, strip metadata, and color adjustments. No network dependencies, runs entirely in-process. ### @stirling-image/ai Bridge layer that calls Python scripts via child processes. Each AI capability has a TypeScript wrapper that spawns a Python subprocess, passes image data through the filesystem, and returns the result. Supported operations: - Background removal — BiRefNet-Lite model via rembg - Upscaling — RealESRGAN - OCR — PaddleOCR - Face detection/blurring — MediaPipe - Object erasing (inpainting) — LaMa Cleaner Python scripts live in `packages/ai/python/`. The Docker image pre-downloads all model weights during the build so the container works offline. ### @stirling-image/shared Shared TypeScript types, constants (like APP_VERSION and tool definitions), and i18n translation strings used by both frontend and backend. ## How a request flows 1. The user picks a tool in the web UI and uploads an image. 2. The frontend sends a multipart POST to `/api/v1/tools/:toolId` with the file and settings. 3. The API route validates the input with Zod, auto-orients the image based on EXIF metadata, then calls the appropriate package function. 4. For AI tools, the TypeScript bridge spawns a Python subprocess, waits for it to finish, and reads the output file. 5. The API returns a `jobId` and `downloadUrl`. The frontend can poll `/api/v1/jobs/:jobId/progress` via SSE for real time status. 6. The user downloads the processed image from `/api/v1/download/:jobId/:filename`. --- # Configuration All configuration is done through environment variables. Every variable has a sensible default. ## Server | Variable | Default | Description | |---|---|---| | PORT | 1349 | Port the server listens on | | RATE_LIMIT_PER_MIN | 100 | Maximum requests per minute per IP | ## Authentication | Variable | Default | Description | |---|---|---| | AUTH_ENABLED | false | Set to true to require login. Docker image defaults to true. | | DEFAULT_USERNAME | admin | Username for the initial admin account. Only used on first run. | | DEFAULT_PASSWORD | admin | Password for the initial admin account. Change after first login. | ## Storage | Variable | Default | Description | |---|---|---| | STORAGE_MODE | local | local or s3. Only local is currently implemented. | | DB_PATH | ./data/stirling.db | Path to the SQLite database file | | WORKSPACE_PATH | ./tmp/workspace | Directory for temporary files during processing | | FILES_STORAGE_PATH | ./data/files | Directory for persistent user files | ## Processing limits | Variable | Default | Description | |---|---|---| | MAX_UPLOAD_SIZE_MB | 100 | Maximum file size per upload in megabytes | | MAX_BATCH_SIZE | 200 | Maximum number of files in a single batch request | | CONCURRENT_JOBS | 3 | Number of batch jobs that run in parallel | | MAX_MEGAPIXELS | 100 | Maximum image resolution allowed | ## Cleanup | Variable | Default | Description | |---|---|---| | FILE_MAX_AGE_HOURS | 24 | How long temporary files are kept before deletion | | CLEANUP_INTERVAL_MINUTES | 30 | How often the cleanup job runs | ## Appearance | Variable | Default | Description | |---|---|---| | APP_NAME | Stirling Image | Display name shown in the UI | | DEFAULT_THEME | light | Default theme (light or dark) | | DEFAULT_LOCALE | en | Default interface language | ## Volumes - `/data` — Persistent storage for SQLite database and user files. Mount to keep data across restarts. - `/tmp/workspace` — Temporary storage for images being processed. Optional but prevents container layer growth. --- # Database SQLite with Drizzle ORM. Database file at DB_PATH (default: ./data/stirling.db). ## Tables ### users | Column | Type | Notes | |---|---|---| | id | integer | Primary key, auto-increment | | username | text | Unique, required | | passwordHash | text | bcrypt hash | | role | text | admin or user | | mustChangePassword | integer | Boolean flag for forced password reset | | createdAt | text | ISO timestamp | ### sessions | Column | Type | Notes | |---|---|---| | id | text | Primary key (session token) | | userId | integer | Foreign key to users.id | | expiresAt | text | ISO timestamp | ### api_keys | Column | Type | Notes | |---|---|---| | id | integer | Primary key | | userId | integer | Foreign key to users.id | | keyHash | text | SHA-256 hash of the key | | name | text | User-provided label | | createdAt | text | ISO timestamp | | lastUsedAt | text | Updated on each request | Keys are prefixed with `si_` followed by 96 hex characters. ### pipelines | Column | Type | Notes | |---|---|---| | id | integer | Primary key | | name | text | Pipeline name | | description | text | Optional | | steps | text | JSON array of { toolId, settings } | | createdAt | text | ISO timestamp | ### settings | Column | Type | Notes | |---|---|---| | key | text | Primary key | | value | text | Setting value | --- # Deployment ## Docker Compose (recommended) ```yaml services: stirling-image: image: stirlingimage/stirling-image:latest container_name: stirling-image ports: - "1349:1349" volumes: - stirling-data:/data - stirling-workspace:/tmp/workspace environment: - AUTH_ENABLED=true - DEFAULT_USERNAME=admin - DEFAULT_PASSWORD=admin restart: unless-stopped volumes: stirling-data: stirling-workspace: ``` ## What's inside the container Multi-stage Docker build: 1. Build stage — installs Node.js dependencies, builds React frontend with Vite 2. Production stage — Node 22 image with Python 3, ImageMagick, Tesseract, potrace, ML packages, pre-downloaded model weights Python packages: rembg (BiRefNet-Lite), RealESRGAN, PaddleOCR, MediaPipe, LaMa Cleaner, onnxruntime, opencv-python, Pillow, numpy. ## Health check ``` GET /api/v1/health ``` Returns 200 OK if the server is running. Used by Docker HEALTHCHECK. ## Reverse proxy (nginx example) ```nginx server { listen 80; server_name images.example.com; client_max_body_size 200M; 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; } } ``` --- # REST API Base URL: `http://localhost:1349` Interactive API docs on running instance: `/api/docs` OpenAPI 3.1 spec: `/api/v1/openapi.yaml` ## Authentication 1. Session token — POST /api/auth/login with {username, password}. Returns {token}. Pass as `Authorization: Bearer `. 2. API key — Generate via POST /api/v1/api-keys. Prefixed with `si_`. Pass as `Authorization: Bearer si_...`. ## Tools ### Execute a tool ``` POST /api/v1/tools/:toolId Content-Type: multipart/form-data ``` Fields: `file` (image), `settings` (JSON string with tool-specific options). Response: `{ jobId, downloadUrl, originalSize, processedSize }` ### Available tool IDs Image operations: resize, crop, rotate, convert, compress, strip-metadata, border Color: brightness-contrast, saturation, color-channels, color-effects, replace-color Text/codes: watermark-text, watermark-image, text-overlay, qr-generate, barcode-read, ocr Composition: compose, collage, split, image-to-pdf Analysis: info, compare, find-duplicates, color-palette Conversion: svg-to-raster, vectorize, favicon, gif-tools AI-powered: remove-background, upscale, blur-faces, erase-object, smart-crop Utility: bulk-rename ### Batch processing ``` POST /api/v1/tools/:toolId/batch Content-Type: multipart/form-data ``` Multiple files with same settings. Returns ZIP file. X-Job-Id header for progress tracking. ## Pipelines - Execute: POST /api/v1/pipeline/execute (multipart: file + steps JSON array) - Save: POST /api/v1/pipeline/save (JSON: {name, description, steps}) - List: GET /api/v1/pipeline/list - Delete: DELETE /api/v1/pipeline/:id ## Files - Upload: POST /api/v1/upload - Download: GET /api/v1/download/:jobId/:filename (public, no auth) - List saved: GET /api/v1/files - Save to library: POST /api/v1/files/upload - Save result: POST /api/v1/files/save-result (JSON: {jobId, filename}) - Get metadata: GET /api/v1/files/:id - Download saved: GET /api/v1/files/:id/download - Thumbnail: GET /api/v1/files/:id/thumbnail - Delete: DELETE /api/v1/files (JSON: {ids: [...]}) ## API Keys - Create: POST /api/v1/api-keys (JSON: {name}) — returns full key once - List: GET /api/v1/api-keys — metadata only - Delete: DELETE /api/v1/api-keys/:id ## Settings - Get all: GET /api/v1/settings - Get one: GET /api/v1/settings/:key - Update: PUT /api/v1/settings (admin only, JSON key-value object) ## Teams - List: GET /api/v1/teams - Create: POST /api/v1/teams (JSON: {name}) - Rename: PUT /api/v1/teams/:id (JSON: {name}) - Delete: DELETE /api/v1/teams/:id ## Auth endpoints - Login: POST /api/auth/login - Logout: POST /api/auth/logout - Session: GET /api/auth/session - Change password: POST /api/auth/change-password - List users (admin): GET /api/auth/users - Create user (admin): POST /api/auth/register - Update user (admin): PUT /api/auth/users/:id - Delete user (admin): DELETE /api/auth/users/:id - Reset password (admin): POST /api/auth/users/:id/reset-password ## Branding - Get logo: GET /api/v1/settings/logo (public) - Upload logo (admin): POST /api/v1/settings/logo - Delete logo (admin): DELETE /api/v1/settings/logo ## Progress tracking (SSE) ``` GET /api/v1/jobs/:jobId/progress ``` Server-Sent Events stream: {status, progress (0-100), completedFiles, failedFiles} ## Error responses ```json { "statusCode": 400, "error": "Bad Request", "message": "Invalid image format" } ``` Status codes: 400 (invalid input), 401 (not authenticated), 403 (not authorized), 413 (file too large), 429 (rate limited), 500 (server error) --- # Image Engine The @stirling-image/image-engine package handles all non-AI image operations via Sharp. ## Operations ### resize width (number), height (number), fit (cover/contain/fill/inside/outside), withoutEnlargement (boolean), percentage (number) ### crop left (number), top (number), width (number), height (number) ### rotate angle (number, degrees) ### flip direction: horizontal or vertical ### convert format (jpeg/png/webp/avif/tiff/gif), quality (1-100) ### compress quality (1-100), format (optional override) ### strip-metadata Removes EXIF, IPTC, XMP metadata. No parameters. ### Color adjustments brightness (-100 to 100), contrast (-100 to 100), saturation (-100 to 100) ### Color filters grayscale, sepia, invert — no parameters ### Color channels red (-100 to 100), green (-100 to 100), blue (-100 to 100) Supported input formats: JPEG, PNG, WebP, AVIF, TIFF, GIF, SVG, RAW (via libraw) --- # AI Engine The @stirling-image/ai package wraps Python ML models. Each operation spawns a Python subprocess. All model weights are bundled in the Docker image. ## Background removal Model: BiRefNet-Lite via rembg. Parameters: model (string), alphaMatting (boolean), alphaMattingForegroundThreshold (0-255), alphaMattingBackgroundThreshold (0-255). ## Upscaling Model: RealESRGAN. Parameters: scale (2 or 4). ## OCR Model: PaddleOCR. Parameters: language (en/ch/fr/de/etc). Returns text with bounding boxes and confidence scores. ## Face detection and blurring Model: MediaPipe. Parameters: blurStrength (number). Returns blurred image with face region metadata. ## Object erasing (inpainting) Model: LaMa. Input: image + mask (white = erase, black = keep). Returns inpainted image. ## Bridge mechanism TypeScript bridge spawns Python subprocess, writes input to temp file, parses JSON progress from stderr for SSE streaming, reads output image, cleans up. Timeout: 5 minutes.