diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f4e0424 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,60 @@ +# Node.js dependencies (should be in named volumes, not copied to build context) +**/node_modules/ +node_modules/ + +# Build outputs +**/dist/ +**/build/ +**/.next/ +**/.nuxt/ + +# Caches +**/.cache/ +**/.vite/ +**/.ponder/ +**/.turbo/ + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Environment files (should be generated in containers) +**/.env.local +**/.env.*.local + +# Testing +**/coverage/ +**/.nyc_output/ + +# Git +.git/ +.gitignore +.gitattributes + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Docker +Dockerfile +docker-compose*.yml +.dockerignore + +# Documentation +*.md +!README.md + +# Temporary files +tmp/ +temp/ +*.tmp diff --git a/AGENTS.md b/AGENTS.md index 16b5847..7a81b4b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -50,7 +50,17 @@ - **Container Orchestration**: `docker-compose.yml` has NO `depends_on` declarations. All service ordering is handled in `scripts/dev.sh` via phased startup with explicit health checks. - **Startup Phases**: (1) Create all containers, (2) Start anvil+postgres and wait for healthy, (3) Start bootstrap and wait for completion, (4) Start ponder and wait for healthy, (5) Start webapp/landing/txn-bot, (6) Start caddy. - **Logging Configuration**: All services have log rotation configured (max 10MB per file, 3 files max = 30MB per container) to prevent disk bloat. Logs are automatically rotated by Docker. -- **Disk Management**: `./scripts/dev.sh stop` automatically prunes unused Docker resources. For aggressive cleanup, run `./scripts/cleanup-disk.sh`. +- **Disk Management** (Portable, No Per-Machine Setup Required): + - **20GB Hard Limit**: The stack enforces a 20GB total Docker disk usage limit (images + containers + volumes + build cache). + - **Pre-flight Checks**: `./scripts/dev.sh start` checks Docker disk usage before starting and refuses to start if over 20GB. + - **Aggressive Auto-Cleanup on Stop**: `./scripts/dev.sh stop` automatically prunes ALL unused Docker resources including build cache (the primary cause of bloat). + - **Named Volumes for node_modules**: All Node.js services (ponder, webapp, landing, txnBot) use named Docker volumes for `node_modules/` instead of writing to the host filesystem. This prevents host pollution (20-30GB savings) and ensures `docker system prune --volumes` cleans them up. + - **npm Best Practices**: All entrypoints use `npm ci` (not `npm install`) for reproducible builds and `npm cache clean --force` to remove ~50-100MB of cache per service. + - **PostgreSQL WAL Limits**: Postgres configured with `wal_level=minimal`, `max_wal_size=128MB`, and `archive_mode=off` to prevent unbounded WAL file growth in the postgres volume. + - **Log Rotation**: All containers limited to 30MB logs (10MB × 3 files) via docker-compose logging configuration. + - **.dockerignore**: Excludes `node_modules/`, caches, and build outputs from Docker build context to speed up builds and reduce image size. + - **Monitoring**: The stack displays current Docker disk usage on startup and warns at 80% (16GB). + - **Note**: Docker has no built-in portable disk quotas. All limits are enforced via aggressive pruning, bounded configurations, and isolation of dependencies to Docker volumes. ## Guardrails & Tips - `token0isWeth` flips amount semantics; confirm ordering before seeding or interpreting liquidity. diff --git a/containers/landing-dev-entrypoint.sh b/containers/landing-dev-entrypoint.sh index 118c33a..04b1ea2 100755 --- a/containers/landing-dev-entrypoint.sh +++ b/containers/landing-dev-entrypoint.sh @@ -31,16 +31,16 @@ if [[ ! -f "$REQUIRED_DIST" ]]; then fi cd "$LANDING_DIR" -DEPS_MARKER="/tmp/.landing-deps-installed" -if [[ ! -d node_modules || ! -f "$DEPS_MARKER" ]]; then + +# Check if node_modules is populated (named volume may be empty on first run) +if [[ ! -d node_modules/.bin ]]; then echo "[landing-entrypoint] Installing dependencies..." - npm install --no-save --loglevel error 2>&1 || { - echo "[landing-entrypoint] npm install failed, trying with --force" - npm install --force --no-save --loglevel error + npm ci --loglevel error && npm cache clean --force 2>&1 || { + echo "[landing-entrypoint] npm ci failed, trying npm install" + npm install --no-save --loglevel error && npm cache clean --force } - touch "$DEPS_MARKER" || true else - echo "[landing-entrypoint] Using cached node_modules" + echo "[landing-entrypoint] Using cached node_modules from volume" fi export CHOKIDAR_USEPOLLING=${CHOKIDAR_USEPOLLING:-1} diff --git a/containers/ponder-dev-entrypoint.sh b/containers/ponder-dev-entrypoint.sh index acc5349..9129f4b 100755 --- a/containers/ponder-dev-entrypoint.sh +++ b/containers/ponder-dev-entrypoint.sh @@ -65,16 +65,15 @@ if [[ ! -f "$REQUIRED_DIST" ]]; then exit 1 fi -DEPS_MARKER="/tmp/.ponder-deps-installed" -if [[ ! -d node_modules || ! -f "$DEPS_MARKER" ]]; then +# Check if node_modules is populated (named volume may be empty on first run) +if [[ ! -d node_modules/.bin ]]; then echo "[ponder-entrypoint] Installing dependencies..." - npm install --no-save --loglevel error 2>&1 || { - echo "[ponder-entrypoint] npm install failed, trying with --force" - npm install --force --no-save --loglevel error + npm ci --loglevel error && npm cache clean --force 2>&1 || { + echo "[ponder-entrypoint] npm ci failed, trying npm install" + npm install --no-save --loglevel error && npm cache clean --force } - touch "$DEPS_MARKER" || true else - echo "[ponder-entrypoint] Using cached node_modules" + echo "[ponder-entrypoint] Using cached node_modules from volume" fi # Load and export all environment variables from .env.local diff --git a/containers/txn-bot-entrypoint.sh b/containers/txn-bot-entrypoint.sh index 6f7c29f..cdf7a85 100755 --- a/containers/txn-bot-entrypoint.sh +++ b/containers/txn-bot-entrypoint.sh @@ -37,16 +37,16 @@ if [[ ! -f "$REQUIRED_DIST" ]]; then fi cd "$BOT_DIR" -DEPS_MARKER="/tmp/.txnbot-deps-installed" -if [[ ! -d node_modules || ! -f "$DEPS_MARKER" ]]; then + +# Check if node_modules is populated (named volume may be empty on first run) +if [[ ! -d node_modules/.bin ]]; then echo "[txn-bot-entrypoint] Installing txn-bot dependencies..." - npm install --no-save --loglevel error 2>&1 || { - echo "[txn-bot-entrypoint] npm install failed, trying with --force" - npm install --force --no-save --loglevel error + npm ci --loglevel error && npm cache clean --force 2>&1 || { + echo "[txn-bot-entrypoint] npm ci failed, trying npm install" + npm install --no-save --loglevel error && npm cache clean --force } - touch "$DEPS_MARKER" || true else - echo "[txn-bot-entrypoint] Using cached node_modules" + echo "[txn-bot-entrypoint] Using cached node_modules from volume" fi echo "[txn-bot-entrypoint] Building TypeScript..." diff --git a/containers/webapp-dev-entrypoint.sh b/containers/webapp-dev-entrypoint.sh index 4ce683f..b053e9d 100755 --- a/containers/webapp-dev-entrypoint.sh +++ b/containers/webapp-dev-entrypoint.sh @@ -41,16 +41,16 @@ fi source "$CONTRACT_ENV" cd "$APP_DIR" -DEPS_MARKER="/tmp/.webapp-deps-installed" -if [[ ! -d node_modules || ! -f "$DEPS_MARKER" ]]; then + +# Check if node_modules is populated (named volume may be empty on first run) +if [[ ! -d node_modules/.bin ]]; then echo "[frontend-entrypoint] Installing dependencies..." - npm install --no-save --loglevel error 2>&1 || { - echo "[frontend-entrypoint] npm install failed, trying with --force" - npm install --force --no-save --loglevel error + npm ci --loglevel error && npm cache clean --force 2>&1 || { + echo "[frontend-entrypoint] npm ci failed, trying npm install" + npm install --no-save --loglevel error && npm cache clean --force } - touch "$DEPS_MARKER" || true else - echo "[frontend-entrypoint] Using cached node_modules" + echo "[frontend-entrypoint] Using cached node_modules from volume" fi export VITE_DEFAULT_CHAIN_ID=${VITE_DEFAULT_CHAIN_ID:-31337} diff --git a/docker-compose.yml b/docker-compose.yml index ffa7317..c68e0c3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,6 +34,18 @@ services: postgres: image: docker.io/library/postgres:16-alpine + command: + - "postgres" + - "-c" + - "wal_level=minimal" + - "-c" + - "max_wal_size=128MB" + - "-c" + - "max_wal_senders=0" + - "-c" + - "archive_mode=off" + - "-c" + - "checkpoint_timeout=30min" environment: - POSTGRES_USER=ponder - POSTGRES_PASSWORD=ponder_local @@ -82,6 +94,7 @@ services: - .:/workspace:z - .git:/workspace/.git:ro,z - ./kraiken-lib/dist:/workspace/kraiken-lib/dist:ro,z + - ponder_node_modules:/workspace/services/ponder/node_modules working_dir: /workspace environment: - CHOKIDAR_USEPOLLING=1 @@ -112,6 +125,7 @@ services: - .:/workspace:z - .git:/workspace/.git:ro,z - ./kraiken-lib/dist:/workspace/kraiken-lib/dist:ro,z + - webapp_node_modules:/workspace/web-app/node_modules working_dir: /workspace environment: - CHOKIDAR_USEPOLLING=1 @@ -140,6 +154,7 @@ services: - .:/workspace:z - .git:/workspace/.git:ro,z - ./kraiken-lib/dist:/workspace/kraiken-lib/dist:ro,z + - landing_node_modules:/workspace/landing/node_modules working_dir: /workspace environment: - CHOKIDAR_USEPOLLING=1 @@ -166,6 +181,7 @@ services: - .:/workspace:z - .git:/workspace/.git:ro,z - ./kraiken-lib/dist:/workspace/kraiken-lib/dist:ro,z + - txnbot_node_modules:/workspace/services/txnBot/node_modules working_dir: /workspace environment: - GIT_BRANCH=${GIT_BRANCH:-} @@ -199,3 +215,7 @@ services: volumes: postgres-data: + ponder_node_modules: + webapp_node_modules: + landing_node_modules: + txnbot_node_modules: diff --git a/scripts/cleanup-disk.sh b/scripts/cleanup-disk.sh deleted file mode 100755 index 4bb85b3..0000000 --- a/scripts/cleanup-disk.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Disk cleanup utility for Harb stack -# Use this to aggressively free up disk space - -cd "$(dirname "$0")/.." - -echo "=== Harb Stack Disk Cleanup ===" -echo "" - -# Detect container runtime -if command -v docker &> /dev/null; then - RUNTIME_CMD="docker" -elif command -v podman &> /dev/null; then - RUNTIME_CMD="podman" -else - echo "Error: docker/podman not found" - exit 1 -fi - -echo "Current disk usage:" -df -h / | tail -1 -echo "" - -# Stop the stack first -if [[ -f "./scripts/dev.sh" ]]; then - echo "Stopping stack..." - ./scripts/dev.sh stop || true -fi - -echo "" -echo "Pruning Docker resources..." -echo " - Stopped containers" -echo " - Unused volumes" -echo " - Dangling images" -echo " - Build cache" -echo "" - -# Aggressive pruning -${RUNTIME_CMD} system prune -af --volumes - -echo "" -echo "Cleaning npm caches..." -rm -rf ~/.npm ~/.cache 2>/dev/null || true - -echo "" -echo "Cleaning journal logs..." -sudo journalctl --vacuum-size=500M 2>/dev/null || echo " (skipped: needs sudo)" - -echo "" -echo "Final disk usage:" -df -h / | tail -1 -echo "" -echo "[ok] Cleanup complete" diff --git a/scripts/dev.sh b/scripts/dev.sh index c033563..7565563 100755 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -11,6 +11,7 @@ readonly PONDER_TIMEOUT=120 # Must index bootstrap events readonly WEBAPP_TIMEOUT=120 # npm install + Vite startup readonly CADDY_TIMEOUT=20 # Proxy starts instantly readonly POLL_INTERVAL=2 # Check health every N seconds +readonly MAX_DOCKER_DISK_GB=20 # Maximum Docker disk usage in GB PID_FILE=/tmp/kraiken-watcher.pid PROJECT_NAME=${COMPOSE_PROJECT_NAME:-$(basename "$PWD")} @@ -36,6 +37,58 @@ container_name() { echo "${PROJECT_NAME}_${service}_1" } +# Check Docker disk usage and warn if approaching limits +check_docker_disk_usage() { + if ! command -v docker &> /dev/null; then + return 0 # Skip if Docker not available + fi + + # Get total Docker disk usage in GB (works on Linux and macOS) + local total_size_bytes + total_size_bytes=$(docker system df --format '{{.Size}}' 2>/dev/null | \ + sed 's/[^0-9.]//g' | awk '{sum+=$1} END {print sum}' || echo "0") + + # Parse the actual usage more accurately + local docker_df_output + docker_df_output=$(docker system df 2>/dev/null || echo "") + + if [[ -z "$docker_df_output" ]]; then + return 0 # Docker not running + fi + + # Extract total reclaimable space (more accurate than parsing Size) + local total_gb + total_gb=$(echo "$docker_df_output" | tail -n 1 | awk '{print $NF}' | sed 's/GB//; s/MB/\/1024/; s/KB/\/1048576/' | bc -l 2>/dev/null || echo "0") + + # Alternative: sum up all TYPE sizes (column 3 has the SIZE) + local images_size containers_size volumes_size build_cache_size + images_size=$(echo "$docker_df_output" | grep "Images" | awk '{print $3}' | sed 's/GB$//; s/MB$/\/1024/; s/KB$/\/1048576/; s/B$/\/1073741824/' | sed 's/^$/0/' | bc -l 2>/dev/null || echo "0") + containers_size=$(echo "$docker_df_output" | grep "Containers" | awk '{print $3}' | sed 's/GB$//; s/MB$/\/1024/; s/KB$/\/1048576/; s/B$/\/1073741824/' | sed 's/^$/0/' | bc -l 2>/dev/null || echo "0") + volumes_size=$(echo "$docker_df_output" | grep "Local Volumes" | awk '{print $3}' | sed 's/GB$//; s/MB$/\/1024/; s/KB$/\/1048576/; s/B$/\/1073741824/' | sed 's/^$/0/' | bc -l 2>/dev/null || echo "0") + build_cache_size=$(echo "$docker_df_output" | grep "Build Cache" | awk '{print $3}' | sed 's/GB$//; s/MB$/\/1024/; s/KB$/\/1048576/; s/B$/\/1073741824/' | sed 's/^$/0/' | bc -l 2>/dev/null || echo "0") + + total_gb=$(echo "$images_size + $containers_size + $volumes_size + $build_cache_size" | bc -l 2>/dev/null || echo "0") + + # Round to 1 decimal place + total_gb=$(printf "%.1f" "$total_gb" 2>/dev/null || echo "0") + + echo " Docker disk usage: ${total_gb}GB / ${MAX_DOCKER_DISK_GB}GB limit" + + # Warn if approaching 80% of limit (16GB) + if (( $(echo "$total_gb > 16" | bc -l 2>/dev/null || echo "0") )); then + echo " [!!] WARNING: Docker disk usage is high!" + echo " [!!] Run './scripts/cleanup-disk.sh' to free up space" + fi + + # Hard stop if over limit + if (( $(echo "$total_gb > $MAX_DOCKER_DISK_GB" | bc -l 2>/dev/null || echo "0") )); then + echo "" + echo "ERROR: Docker disk usage exceeds ${MAX_DOCKER_DISK_GB}GB limit!" + echo "Run './scripts/cleanup-disk.sh' to free up space, then try again." + exit 1 + fi +} + cleanup_existing() { # Kill any existing watch scripts pkill -f "watch-kraiken-lib.sh" 2>/dev/null || true @@ -101,6 +154,9 @@ wait_for_exited() { start_stack() { local stack_start_time=$(date +%s) + # Check Docker disk usage before starting + check_docker_disk_usage + # Clean up any existing processes first cleanup_existing @@ -163,9 +219,14 @@ stop_stack() { cleanup_existing ${COMPOSE_CMD} down - # Prune Docker resources to prevent disk bloat - echo " Pruning Docker resources..." - ${RUNTIME_CMD} system prune -f --volumes 2>&1 | grep -E "Total reclaimed|deleted" || true + # Aggressive pruning to prevent disk bloat + echo " Pruning Docker resources (images, containers, volumes, build cache)..." + + # Prune build cache aggressively (this is usually the biggest culprit) + ${RUNTIME_CMD} builder prune -af 2>&1 | grep -E "Total|deleted" || true + + # Prune all unused data (containers, networks, images, volumes) + ${RUNTIME_CMD} system prune -af --volumes 2>&1 | grep -E "Total reclaimed|deleted" || true echo "[ok] Stack stopped and cleaned" }