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:
Ashim
2026-04-21 10:19:08 +08:00
committed by GitHub
parent fa35f57813
commit 4c9dc6e38e
11 changed files with 427 additions and 184 deletions
+34 -4
View File
@@ -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
View File
@@ -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 \
+12 -10
View File
@@ -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:
+6 -6
View File
@@ -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"]
+38
View File
@@ -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