fix/node-modules-named-volumes (#94)
Co-authored-by: openhands <openhands@all-hands.dev> Reviewed-on: https://codeberg.org/johba/harb/pulls/94
This commit is contained in:
parent
19bac420d0
commit
1c6f118f6b
9 changed files with 182 additions and 87 deletions
60
.dockerignore
Normal file
60
.dockerignore
Normal file
|
|
@ -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
|
||||
12
AGENTS.md
12
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.
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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..."
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue