mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
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
This commit is contained in:
+34
-4
@@ -1,14 +1,44 @@
|
||||
node_modules
|
||||
.git
|
||||
.gitignore
|
||||
.turbo
|
||||
.worktrees
|
||||
dist
|
||||
*.db
|
||||
*.db-journal
|
||||
*.db-wal
|
||||
*.db-shm
|
||||
.env
|
||||
.env.local
|
||||
.env.*
|
||||
!.env.example
|
||||
.DS_Store
|
||||
.playwright-mcp
|
||||
*.png
|
||||
*.jpg
|
||||
*.jpeg
|
||||
.mcp.json
|
||||
.superpowers
|
||||
docs/superpowers
|
||||
|
||||
# Test artifacts
|
||||
test-results
|
||||
playwright-report
|
||||
blob-report
|
||||
tests
|
||||
coverage
|
||||
*.tsbuildinfo
|
||||
MASTER_TEST_MATRIX.md
|
||||
|
||||
# IDE and editor files
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# CI/release files not needed in image
|
||||
.github
|
||||
.husky
|
||||
.releaserc.json
|
||||
scripts
|
||||
|
||||
# Large test images (favicons/logos in apps/web/public are fine)
|
||||
test-*.png
|
||||
*.heic
|
||||
*.heif
|
||||
|
||||
+11
-5
@@ -31,8 +31,10 @@ COPY packages/ai/package.json packages/ai/tsconfig.json ./packages/ai/
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store/v3 \
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
# Copy only frontend-relevant source (API/Python changes don't bust this cache)
|
||||
COPY packages/shared/src ./packages/shared/src
|
||||
COPY apps/web/src ./apps/web/src
|
||||
COPY apps/web/public ./apps/web/public
|
||||
|
||||
# Build only the web frontend (API runs from TS source via tsx)
|
||||
RUN --mount=type=cache,id=turbo-cache,target=/app/.turbo \
|
||||
@@ -106,6 +108,7 @@ RUN set -e; \
|
||||
|
||||
# ============================================
|
||||
# Stage 3: Platform-specific base images
|
||||
# Pin tags to specific major.minor for reproducible builds.
|
||||
# ============================================
|
||||
FROM node:22-bookworm AS base-linux-arm64
|
||||
FROM nvidia/cuda:12.6.3-cudnn-runtime-ubuntu24.04 AS base-linux-amd64
|
||||
@@ -139,6 +142,7 @@ RUN corepack enable && corepack prepare pnpm@9.15.4 --activate && \
|
||||
chmod -R a+rX /usr/local/share/corepack
|
||||
|
||||
# System dependencies (all platforms)
|
||||
# Split into runtime deps and build deps to minimize final image size.
|
||||
# Retry apt-get update with backoff — Ubuntu mirrors can be flaky on CI runners
|
||||
RUN for i in 1 2 3; do apt-get -o Acquire::Retries=3 update && break || sleep $((i * 15)); done && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
@@ -153,7 +157,7 @@ RUN for i in 1 2 3; do apt-get -o Acquire::Retries=3 update && break || sleep $(
|
||||
python3 python3-pip python3-venv python3-dev \
|
||||
tesseract-ocr tesseract-ocr-eng tesseract-ocr-deu tesseract-ocr-fra tesseract-ocr-spa \
|
||||
tesseract-ocr-chi-sim tesseract-ocr-jpn tesseract-ocr-kor \
|
||||
build-essential \
|
||||
gcc g++ \
|
||||
libgl1 libglib2.0-0 \
|
||||
libegl1 libwayland-egl1 libwayland-client0 libwayland-cursor0 \
|
||||
libxkbcommon-x11-0 libxkbcommon0 libxcursor1 \
|
||||
@@ -168,7 +172,8 @@ RUN for i in 1 2 3; do apt-get -o Acquire::Retries=3 update && break || sleep $(
|
||||
# Caire binary (content-aware seam carving)
|
||||
COPY --from=caire-builder /tmp/caire /usr/local/bin/caire
|
||||
|
||||
# Python venv - Layer 1: Base packages (rarely change, ~3 GB)
|
||||
# Python venv - Base packages (rarely change, cached aggressively)
|
||||
# Uses pre-built manylinux wheels where available; gcc/g++ above covers the rest.
|
||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
python3 -m venv /opt/venv && \
|
||||
/opt/venv/bin/pip install --upgrade pip && \
|
||||
@@ -246,7 +251,8 @@ ENV PORT=1349 \
|
||||
MAX_PDF_PAGES=0 \
|
||||
SESSION_DURATION_HOURS=168 \
|
||||
LOGIN_ATTEMPT_LIMIT=10 \
|
||||
LOG_LEVEL=debug
|
||||
LOG_LEVEL=info \
|
||||
TRUST_PROXY=true
|
||||
|
||||
# NVIDIA Container Toolkit env vars (harmless on non-GPU systems)
|
||||
ENV NVIDIA_VISIBLE_DEVICES=all \
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
name: ashim
|
||||
|
||||
# NVIDIA GPU version
|
||||
# NVIDIA GPU deployment — requires nvidia-container-toolkit.
|
||||
# Install: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
|
||||
# Usage: docker compose -f docker-compose-gpu.yml up -d
|
||||
# Requires: NVIDIA GPU + nvidia-container-toolkit (Linux/Windows WSL2 only)
|
||||
# Verify: docker logs ashim 2>&1 | grep GPU
|
||||
|
||||
services:
|
||||
ashim:
|
||||
@@ -14,8 +15,8 @@ services:
|
||||
ports:
|
||||
- "1349:1349"
|
||||
volumes:
|
||||
- ashim-data:/data
|
||||
- ashim-workspace:/tmp/workspace
|
||||
- ashim-data:/data # Database, AI models, user files
|
||||
- ashim-workspace:/tmp/workspace # Temp processing (auto-cleaned)
|
||||
environment:
|
||||
- AUTH_ENABLED=true
|
||||
- DEFAULT_USERNAME=admin
|
||||
@@ -31,6 +32,7 @@ services:
|
||||
- RATE_LIMIT_PER_MIN=${RATE_LIMIT_PER_MIN:-0}
|
||||
- MAX_USERS=${MAX_USERS:-0}
|
||||
- SESSION_DURATION_HOURS=${SESSION_DURATION_HOURS:-168}
|
||||
- TRUST_PROXY=${TRUST_PROXY:-true}
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:1349/api/v1/health"]
|
||||
@@ -38,12 +40,7 @@ services:
|
||||
timeout: 5s
|
||||
start_period: 60s
|
||||
retries: 3
|
||||
shm_size: '2gb'
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "5"
|
||||
shm_size: "2gb" # Required for PyTorch CUDA shared memory
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
@@ -51,6 +48,11 @@ services:
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities: [gpu]
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "5"
|
||||
|
||||
volumes:
|
||||
ashim-data:
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
name: ashim
|
||||
|
||||
# Usage:
|
||||
# CPU: docker compose up -d
|
||||
# GPU: docker compose -f docker-compose-gpu.yml up -d
|
||||
# (requires NVIDIA GPU + nvidia-container-toolkit; Linux/Windows WSL2 only)
|
||||
# CPU deployment — no GPU required.
|
||||
# Usage: docker compose up -d
|
||||
# GPU: docker compose -f docker-compose-gpu.yml up -d
|
||||
|
||||
services:
|
||||
ashim:
|
||||
@@ -15,8 +14,8 @@ services:
|
||||
ports:
|
||||
- "1349:1349"
|
||||
volumes:
|
||||
- ashim-data:/data
|
||||
- ashim-workspace:/tmp/workspace
|
||||
- ashim-data:/data # Database, AI models, user files
|
||||
- ashim-workspace:/tmp/workspace # Temp processing (auto-cleaned)
|
||||
environment:
|
||||
- AUTH_ENABLED=true
|
||||
- DEFAULT_USERNAME=admin
|
||||
@@ -32,6 +31,7 @@ services:
|
||||
- RATE_LIMIT_PER_MIN=${RATE_LIMIT_PER_MIN:-0}
|
||||
- MAX_USERS=${MAX_USERS:-0}
|
||||
- SESSION_DURATION_HOURS=${SESSION_DURATION_HOURS:-168}
|
||||
- TRUST_PROXY=${TRUST_PROXY:-true}
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:1349/api/v1/health"]
|
||||
|
||||
@@ -28,8 +28,46 @@ fi
|
||||
# Fix ownership of mounted volumes so the non-root ashim user can write.
|
||||
# This runs as root, fixes permissions, then drops to ashim via gosu.
|
||||
if [ "$(id -u)" = "0" ]; then
|
||||
# PUID/PGID support: remap the ashim user/group to match host UID/GID.
|
||||
# This prevents permission conflicts when using bind mounts.
|
||||
PUID="${PUID:-$(id -u ashim)}"
|
||||
PGID="${PGID:-$(id -g ashim)}"
|
||||
|
||||
if [ "$PUID" = "0" ] || [ "$PGID" = "0" ]; then
|
||||
echo "WARNING: PUID=0 or PGID=0 would run the app as root. Ignoring — using default ashim UID/GID." >&2
|
||||
PUID=$(id -u ashim)
|
||||
PGID=$(id -g ashim)
|
||||
fi
|
||||
|
||||
CUR_UID=$(id -u ashim)
|
||||
CUR_GID=$(id -g ashim)
|
||||
|
||||
if [ "$CUR_UID" != "$PUID" ] || [ "$CUR_GID" != "$PGID" ]; then
|
||||
# Evict any conflicting user/group that holds the target UID/GID.
|
||||
# Delete user first (may cascade-delete its primary group).
|
||||
if [ "$CUR_UID" != "$PUID" ]; then
|
||||
EXISTING_USER=$(getent passwd "$PUID" 2>/dev/null | cut -d: -f1 || true)
|
||||
if [ -n "$EXISTING_USER" ] && [ "$EXISTING_USER" != "ashim" ]; then
|
||||
deluser "$EXISTING_USER" 2>/dev/null || userdel "$EXISTING_USER" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
if [ "$CUR_GID" != "$PGID" ]; then
|
||||
EXISTING_GROUP=$(getent group "$PGID" 2>/dev/null | cut -d: -f1 || true)
|
||||
if [ -n "$EXISTING_GROUP" ] && [ "$EXISTING_GROUP" != "ashim" ]; then
|
||||
delgroup "$EXISTING_GROUP" 2>/dev/null || groupdel "$EXISTING_GROUP" 2>/dev/null || true
|
||||
fi
|
||||
groupmod -g "$PGID" ashim 2>/dev/null || true
|
||||
fi
|
||||
if [ "$CUR_UID" != "$PUID" ]; then
|
||||
usermod -u "$PUID" ashim 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Chown writable directories (/data is the persistent volume, /tmp/workspace is ephemeral).
|
||||
# /app and /opt/venv are read-only at runtime — no chown needed.
|
||||
chown -R ashim:ashim /data /tmp/workspace 2>&1 || \
|
||||
echo "WARNING: Could not fix volume permissions. Use named volumes (not Windows bind mounts) to avoid this. See docs for details." >&2
|
||||
|
||||
exec gosu ashim "$@"
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user