Files
SnapOtter/apps/docs/guide/deployment.md
T
AshimandGitHub 4c9dc6e38e fix: Docker hardening, security, and deployment readiness for V1 (#82)
Phase 1 — Docker Artifact Optimization:
- Replace broad `COPY . .` with targeted frontend source copies (API/Python
  changes no longer bust the frontend build cache)
- Replace build-essential with gcc/g++ (leaner runtime)
- Fix LOG_LEVEL=debug → info for production
- Harden .dockerignore (exclude worktrees, IDE, CI, test artifacts)

Phase 2 — State & Persistence:
- Add PUID/PGID support in entrypoint.sh for bind mount compatibility
- Guard against PUID=0/PGID=0 to prevent accidental root execution
- Evict conflicting system users (e.g. node:1000) before UID remap

Phase 3 — Security:
- Always register @fastify/rate-limit so login brute-force protection
  works even when global rate limit is disabled (RATE_LIMIT_PER_MIN=0)
- Add trustProxy support (TRUST_PROXY env var, default true) so rate
  limiting and audit logs use real client IPs behind reverse proxies
- Strip stack traces from 500 error responses in production
- Fix FSTDEP022 deprecation: maxParamLength → routerOptions
- Add multi-file guard on single-file tool endpoint with clear error
  message pointing to the /batch endpoint

Phase 4 — Graceful Degradation:
- Add consolidated hardware detection startup banner (GPU, rate limit,
  upload limit, proxy status)
- Add ConnectionMonitor component with health polling and reconnecting
  overlay that auto-dismisses when the server comes back

Phase 5 — Deployment Docs:
- Rewrite deployment.md with copy-paste CPU and GPU compose templates
- Add hardware requirements table (minimum, recommended, heavy workloads)
- Add PUID/PGID bind mount documentation
- Add complete env var reference table
- Add reverse proxy guides for Nginx, Nginx Proxy Manager, Traefik,
  and Cloudflare Tunnels
2026-04-21 10:19:08 +08:00

9.8 KiB

Deployment

ashim ships as a single Docker container. The image supports linux/amd64 (with NVIDIA CUDA) and linux/arm64 (CPU), so it runs natively on Intel/AMD servers, Apple Silicon Macs, and ARM devices like the Raspberry Pi 4/5.

See Docker Image for GPU setup, Docker Compose examples, and version pinning.

Quick Start (CPU)

# docker-compose.yml — Copy this file and run: docker compose up -d
services:
  ashim:
    image: ashimhq/ashim:latest    # or ghcr.io/ashim-hq/ashim:latest
    container_name: ashim
    ports:
      - "1349:1349"                # Web UI + API
    volumes:
      - ashim-data:/data           # Database, AI models, user files (PERSISTENT)
      - ashim-workspace:/tmp/workspace  # Temp processing files (can be tmpfs)
    environment:
      # --- Authentication ---
      - AUTH_ENABLED=true          # Set to false to disable login entirely
      - DEFAULT_USERNAME=admin     # First-run admin username
      - DEFAULT_PASSWORD=admin     # First-run admin password (you'll be forced to change it)

      # --- Limits (0 = unlimited) ---
      # - MAX_UPLOAD_SIZE_MB=0     # Per-file upload limit in MB
      # - MAX_BATCH_SIZE=0         # Max files per batch request
      # - RATE_LIMIT_PER_MIN=0     # API rate limit (0 = disabled, 100 = recommended for public)
      # - MAX_USERS=0              # Max user accounts

      # --- Networking ---
      # - TRUST_PROXY=true         # Trust X-Forwarded-For headers (set false if not behind a proxy)

      # --- Bind mount permissions ---
      # - PUID=1000                # Match your host user's UID (run: id -u)
      # - PGID=1000                # Match your host user's GID (run: id -g)
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:1349/api/v1/health"]
      interval: 30s
      timeout: 5s
      start_period: 60s
      retries: 3
    shm_size: "2gb"            # Needed for Python ML shared memory
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  ashim-data:       # Named volume — Docker manages permissions automatically
  ashim-workspace:
docker compose up -d

The app is then available at http://localhost:1349.

Docker Hub rate limits? Replace ashimhq/ashim:latest with ghcr.io/ashim-hq/ashim:latest to pull from GitHub Container Registry instead. Both registries receive the same image on every release.

Quick Start (GPU)

For NVIDIA GPU acceleration on AI tools (background removal, upscaling, face enhancement, OCR):

# docker-compose-gpu.yml — Requires: NVIDIA GPU + nvidia-container-toolkit
# Install toolkit: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
services:
  ashim:
    image: ashimhq/ashim:latest
    container_name: ashim
    ports:
      - "1349:1349"
    volumes:
      - ashim-data:/data
      - ashim-workspace:/tmp/workspace
    environment:
      - AUTH_ENABLED=true
      - DEFAULT_USERNAME=admin
      - DEFAULT_PASSWORD=admin
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:1349/api/v1/health"]
      interval: 30s
      timeout: 5s
      start_period: 60s
      retries: 3
    shm_size: "2gb"                # Required for PyTorch CUDA shared memory
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all           # Or set to 1 for a specific GPU
              capabilities: [gpu]
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  ashim-data:
  ashim-workspace:
docker compose -f docker-compose-gpu.yml up -d

Check GPU detection in the logs:

docker logs ashim 2>&1 | head -20
# Look for: [INFO] GPU detected — AI tools will use CUDA acceleration

Hardware Requirements

Minimum (basic image tools only)

Resource Requirement
CPU 2 cores
RAM 1 GB
Disk 3 GB (image) + 1 GB (data volume)
GPU Not required

Basic tools (resize, crop, rotate, convert, watermark, border, etc.) work on any hardware. They use Sharp (libvips) and complete in milliseconds.

Resource Requirement
CPU 4+ cores
RAM 4 GB minimum, 8 GB recommended
Disk 3 GB (image) + 10-25 GB (AI models, downloaded on first use)
GPU NVIDIA with 4+ GB VRAM (optional but 5-20x faster)

AI tools (background removal, upscaling, face enhancement, OCR, object erasing) download models on first use. Model sizes:

Feature Model Size VRAM Usage
Background removal ~200 MB ~1 GB
Face detection ~10 MB ~500 MB
Upscale + Face enhance ~1.5 GB ~4 GB
OCR ~200 MB ~1 GB
Object eraser + Colorize ~500 MB ~2 GB

Heavy workloads (upscale + GFPGAN)

Resource Requirement
CPU 8+ cores
RAM 16 GB
GPU NVIDIA with 8+ GB VRAM (RTX 3070 or better)
Disk 30 GB total

Upscaling a 4K image with face enhancement at 4x scale uses ~6 GB VRAM peak. Without a GPU, the same operation takes 5-10 minutes on CPU vs. 10-30 seconds on GPU.

Volumes

Mount Purpose Required?
/data SQLite database, AI models, Python venv, user files Yes — data loss without it
/tmp/workspace Temporary processing files (auto-cleaned) Recommended

Bind mounts vs. named volumes

Named volumes (recommended) — Docker manages permissions automatically:

volumes:
  - ashim-data:/data

Bind mounts — You manage permissions. Set PUID/PGID to match your host user:

volumes:
  - ./ashim-data:/data
environment:
  - PUID=1000    # Your host UID (run: id -u)
  - PGID=1000    # Your host GID (run: id -g)

Environment Variables

Variable Default Description
AUTH_ENABLED true Enable/disable login requirement
DEFAULT_USERNAME admin Initial admin username
DEFAULT_PASSWORD admin Initial admin password (forced change on first login)
MAX_UPLOAD_SIZE_MB 0 (unlimited) Per-file upload limit
MAX_BATCH_SIZE 0 (unlimited) Max files per batch request
RATE_LIMIT_PER_MIN 0 (disabled) API requests per minute per IP
MAX_USERS 0 (unlimited) Maximum user accounts
TRUST_PROXY true Trust X-Forwarded-For headers from reverse proxy
PUID 999 Run as this UID (for bind mount permissions)
PGID 999 Run as this GID (for bind mount permissions)
LOG_LEVEL info Log verbosity: fatal, error, warn, info, debug, trace
CONCURRENT_JOBS 0 (auto) Max parallel AI processing jobs
SESSION_DURATION_HOURS 168 Login session lifetime (7 days)
CORS_ORIGIN (empty) Comma-separated allowed origins, or empty for same-origin

Health Check

The container includes a built-in health check:

# Check container health status
docker inspect --format='{{.State.Health.Status}}' ashim

# Manual health check
curl http://localhost:1349/api/v1/health
# {"status":"healthy","version":"1.15.9"}

Reverse Proxy

ashim sets TRUST_PROXY=true by default so rate limiting and logging use the real client IP from X-Forwarded-For headers.

Nginx

server {
    listen 80;
    server_name images.example.com;

    # Match MAX_UPLOAD_SIZE_MB (0 = nginx default 1M, so set high for unlimited)
    client_max_body_size 500M;

    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;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # SSE support (batch progress, feature install progress)
        proxy_buffering off;
        proxy_read_timeout 300s;
    }
}

Nginx Proxy Manager

  1. Add a new Proxy Host
  2. Set Domain Name to your domain
  3. Set Scheme to http, Forward Hostname to ashim (or your container IP), Forward Port to 1349
  4. Enable WebSocket support
  5. Under Advanced, add: client_max_body_size 500M; and proxy_buffering off;

Traefik

# Add these labels to the ashim service in docker-compose.yml
labels:
  - "traefik.enable=true"
  - "traefik.http.routers.ashim.rule=Host(`images.example.com`)"
  - "traefik.http.routers.ashim.entrypoints=websecure"
  - "traefik.http.routers.ashim.tls.certresolver=letsencrypt"
  - "traefik.http.services.ashim.loadbalancer.server.port=1349"
  # Increase upload limit (default 2MB is too low)
  - "traefik.http.middlewares.ashim-body.buffering.maxRequestBodyBytes=524288000"
  - "traefik.http.routers.ashim.middlewares=ashim-body"

Cloudflare Tunnels

cloudflared tunnel --url http://localhost:1349

Note: Cloudflare has a 100 MB upload limit on free plans. Set MAX_UPLOAD_SIZE_MB=100 to match.

CI/CD

The GitHub repository has three workflows:

  • ci.yml -- Runs automatically on every push and PR. Lints, typechecks, tests, builds, and validates the Docker image (without pushing).
  • release.yml -- Triggered manually via workflow_dispatch. Runs semantic-release to create a version tag and GitHub release, then builds a multi-arch Docker image (amd64 + arm64) and pushes to Docker Hub (ashimhq/ashim) and GitHub Container Registry (ghcr.io/ashim-hq/ashim).
  • deploy-docs.yml -- Builds this documentation site and deploys it to GitHub Pages on push to main.

To create a release, go to Actions > Release > Run workflow in the GitHub UI, or run:

gh workflow run release.yml

Semantic-release determines the version from commit history. The latest Docker tag always points to the most recent release.