migrate/podman-to-docker (#92)

podman to docker

Co-authored-by: openhands <openhands@all-hands.dev>
Reviewed-on: https://codeberg.org/johba/harb/pulls/92
This commit is contained in:
johba 2025-11-08 14:08:46 +01:00
parent c2720c35a5
commit 5d71753086
13 changed files with 211 additions and 100 deletions

View file

@ -16,7 +16,8 @@
- `./scripts/dev.sh restart --light` - Fast restart (~10-20s): only webapp + txnbot, preserves Anvil/Ponder state. Use for frontend changes. - `./scripts/dev.sh restart --light` - Fast restart (~10-20s): only webapp + txnbot, preserves Anvil/Ponder state. Use for frontend changes.
- `./scripts/dev.sh restart --full` - Full restart (~3-4min): redeploys contracts, fresh state. Use for contract changes. - `./scripts/dev.sh restart --full` - Full restart (~3-4min): redeploys contracts, fresh state. Use for contract changes.
- Supported environments: `BASE_SEPOLIA_LOCAL_FORK` (default Anvil fork), `BASE_SEPOLIA`, and `BASE`. Match contract addresses and RPCs accordingly. - Supported environments: `BASE_SEPOLIA_LOCAL_FORK` (default Anvil fork), `BASE_SEPOLIA`, and `BASE`. Match contract addresses and RPCs accordingly.
- The stack boots Anvil, deploys contracts, seeds liquidity, starts Ponder, launches the landing site, and runs the txnBot. Wait for logs to settle before manual testing. - The stack uses Docker containers orchestrated via docker-compose. The script boots Anvil, deploys contracts, seeds liquidity, starts Ponder, launches the landing site, and runs the txnBot. Wait for logs to settle before manual testing.
- **Prerequisites**: Docker Engine (Linux) or Colima (Mac). See installation instructions below.
## Component Guides ## Component Guides
- `onchain/` - Solidity + Foundry contracts, deploy scripts, and fuzzing helpers ([details](onchain/AGENTS.md)). - `onchain/` - Solidity + Foundry contracts, deploy scripts, and fuzzing helpers ([details](onchain/AGENTS.md)).
@ -38,11 +39,16 @@
- **CI Enforcement**: GitHub workflow validates that contract VERSION is in `COMPATIBLE_CONTRACT_VERSIONS` before merging PRs. - **CI Enforcement**: GitHub workflow validates that contract VERSION is in `COMPATIBLE_CONTRACT_VERSIONS` before merging PRs.
- See `VERSION_VALIDATION.md` for complete architecture, workflows, and troubleshooting. - See `VERSION_VALIDATION.md` for complete architecture, workflows, and troubleshooting.
## Podman Orchestration ## Docker Installation & Setup
- **Dependency Management**: `podman-compose.yml` has NO `depends_on` declarations. All service ordering is handled in `scripts/dev.sh` via phased startup with explicit health checks. - **Linux**: Install Docker Engine via package manager or `curl -fsSL https://get.docker.com | sh`, then add user to docker group: `sudo usermod -aG docker $USER` (logout/login required)
- **Why**: Podman's dependency graph validator fails when containers have compose metadata dependencies, causing "container not found in input list" errors even when containers exist. - **Mac**: Use Colima (open-source Docker Desktop alternative):
```bash
brew install colima docker docker-compose
colima start --cpu 4 --memory 8 --disk 100
docker ps # verify installation
```
- **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. - **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.
- If you see dependency graph errors, verify `depends_on` was not re-added to `podman-compose.yml`.
## Guardrails & Tips ## Guardrails & Tips
- `token0isWeth` flips amount semantics; confirm ordering before seeding or interpreting liquidity. - `token0isWeth` flips amount semantics; confirm ordering before seeding or interpreting liquidity.
@ -51,7 +57,7 @@
- Ponder stores data in `.ponder/`; drop the directory if schema changes break migrations. - Ponder stores data in `.ponder/`; drop the directory if schema changes break migrations.
- Keep git clean before committing; never leave commented-out code or untested changes. - Keep git clean before committing; never leave commented-out code or untested changes.
- **ES Modules**: The entire stack uses ES modules. kraiken-lib, txnBot, Ponder, and web-app all require `"type": "module"` in package.json and use `import` syntax. - **ES Modules**: The entire stack uses ES modules. kraiken-lib, txnBot, Ponder, and web-app all require `"type": "module"` in package.json and use `import` syntax.
- **kraiken-lib Build**: Run `./scripts/build-kraiken-lib.sh` before `podman-compose up` so containers mount a fresh `kraiken-lib/dist` from the host. - **kraiken-lib Build**: Run `./scripts/build-kraiken-lib.sh` before `docker-compose up` so containers mount a fresh `kraiken-lib/dist` from the host.
- **Live Reload**: `scripts/watch-kraiken-lib.sh` rebuilds on file changes (requires inotify-tools) and restarts dependent containers automatically. - **Live Reload**: `scripts/watch-kraiken-lib.sh` rebuilds on file changes (requires inotify-tools) and restarts dependent containers automatically.
## Code Quality & Git Hooks ## Code Quality & Git Hooks

View file

@ -49,12 +49,12 @@ This release implements a comprehensive version validation system to ensure cont
- Troubleshooting guide - Troubleshooting guide
- Maintenance guidelines - Maintenance guidelines
### 2. Podman Orchestration Fix ### 2. Container Orchestration Fix
**Problem:** Podman's dependency graph validator fails with "container not found in input list" errors when containers have `depends_on` metadata. **Problem:** Container dependency graph validator can fail with "container not found in input list" errors when containers have `depends_on` metadata.
**Solution:** **Solution:**
- `podman-compose.yml`: Removed ALL `depends_on` declarations from: - `docker-compose.yml`: Removed ALL `depends_on` declarations from:
- bootstrap - bootstrap
- ponder - ponder
- webapp - webapp
@ -63,7 +63,7 @@ This release implements a comprehensive version validation system to ensure cont
- caddy - caddy
- `scripts/dev.sh`: Implemented phased startup with explicit health checks: - `scripts/dev.sh`: Implemented phased startup with explicit health checks:
1. Create all containers (`podman-compose up --no-start`) 1. Create all containers (`docker-compose up --no-start`)
2. Start anvil & postgres, wait for healthy 2. Start anvil & postgres, wait for healthy
3. Start bootstrap, wait for completion 3. Start bootstrap, wait for completion
4. Start ponder, wait for healthy 4. Start ponder, wait for healthy
@ -126,7 +126,7 @@ This release implements a comprehensive version validation system to ensure cont
6. `kraiken-lib/src/taxRates.ts` - Generated tax rates with checksums 6. `kraiken-lib/src/taxRates.ts` - Generated tax rates with checksums
7. `kraiken-lib/src/tests/taxRates.test.ts` - Fixed Jest compatibility 7. `kraiken-lib/src/tests/taxRates.test.ts` - Fixed Jest compatibility
8. `onchain/src/Kraiken.sol` - Added VERSION constant 8. `onchain/src/Kraiken.sol` - Added VERSION constant
9. `podman-compose.yml` - Removed all depends_on declarations 9. `docker-compose.yml` - Removed all depends_on declarations
10. `scripts/build-kraiken-lib.sh` - Updated build process 10. `scripts/build-kraiken-lib.sh` - Updated build process
11. `scripts/dev.sh` - Implemented phased startup 11. `scripts/dev.sh` - Implemented phased startup
12. `services/ponder/AGENTS.md` - Updated documentation 12. `services/ponder/AGENTS.md` - Updated documentation
@ -193,7 +193,7 @@ This release implements a comprehensive version validation system to ensure cont
6. **Verify version validation:** 6. **Verify version validation:**
```bash ```bash
podman logs harb_ponder_1 | grep "version validated" docker logs harb_ponder_1 | grep "version validated"
``` ```
Should output: `✓ Contract version validated: v1 (kraiken-lib v1)` Should output: `✓ Contract version validated: v1 (kraiken-lib v1)`
@ -237,7 +237,7 @@ When making breaking changes to TAX_RATES, events, or data structures:
### Manual Verification ### Manual Verification
```bash ```bash
# Check Ponder logs for version validation # Check Ponder logs for version validation
podman logs harb_ponder_1 | grep "version validated" docker logs harb_ponder_1 | grep "version validated"
# Output: ✓ Contract version validated: v1 (kraiken-lib v1) # Output: ✓ Contract version validated: v1 (kraiken-lib v1)
# Check contract VERSION # Check contract VERSION

View file

@ -26,7 +26,7 @@ if [[ -n "$GIT_BRANCH" ]]; then
fi fi
fi fi
fi fi
STATE_DIR=$ROOT_DIR/tmp/podman STATE_DIR=$ROOT_DIR/tmp/containers
LOG_DIR=$STATE_DIR/logs LOG_DIR=$STATE_DIR/logs
SETUP_LOG=$LOG_DIR/setup.log SETUP_LOG=$LOG_DIR/setup.log
CONTRACT_ENV=$STATE_DIR/contracts.env CONTRACT_ENV=$STATE_DIR/contracts.env
@ -177,17 +177,14 @@ seed_application_state() {
} }
prime_chain() { prime_chain() {
log "Pre-mining 200 blocks (2x ring buffer warmup)..." log "Pre-mining 5 blocks (minimal warmup for fast Ponder sync)..."
# Try batch mine first (0xc8 = 200 blocks = 2x MINIMUM_BLOCKS_FOR_RING_BUFFER, 0x1 = 1 second interval) # Mine just 5 blocks - enough for Ponder to have some history but keeps sync fast
if cast rpc --rpc-url "$ANVIL_RPC" anvil_mine "0xc8" "0x1" >/dev/null 2>&1; then if cast rpc --rpc-url "$ANVIL_RPC" anvil_mine "0x5" "0x1" >/dev/null 2>&1; then
log "Used batch mining" log "Mined 5 blocks"
else else
log "Batch mining failed, using individual evm_mine calls" log "Batch mining failed, using individual evm_mine calls"
for i in {1..200}; do for i in {1..5}; do
cast rpc --rpc-url "$ANVIL_RPC" evm_mine >/dev/null 2>&1 || true cast rpc --rpc-url "$ANVIL_RPC" evm_mine >/dev/null 2>&1 || true
if ((i % 50 == 0)); then
log "Mined $i blocks..."
fi
done done
fi fi
log "Pre-mining complete" log "Pre-mining complete"

View file

@ -22,7 +22,7 @@ if [[ -n "$GIT_BRANCH" ]]; then
fi fi
fi fi
CONTRACT_ENV=$ROOT_DIR/tmp/podman/contracts.env CONTRACT_ENV=$ROOT_DIR/tmp/containers/contracts.env
PONDER_WORKDIR=$ROOT_DIR/services/ponder PONDER_WORKDIR=$ROOT_DIR/services/ponder
while [[ ! -f "$CONTRACT_ENV" ]]; do while [[ ! -f "$CONTRACT_ENV" ]]; do

View file

@ -22,7 +22,7 @@ if [[ -n "$GIT_BRANCH" ]]; then
fi fi
fi fi
TXNBOT_ENV_FILE=$ROOT_DIR/tmp/podman/txnBot.env TXNBOT_ENV_FILE=$ROOT_DIR/tmp/containers/txnBot.env
BOT_DIR=$ROOT_DIR/services/txnBot BOT_DIR=$ROOT_DIR/services/txnBot
REQUIRED_DIST=$ROOT_DIR/kraiken-lib/dist/index.js REQUIRED_DIST=$ROOT_DIR/kraiken-lib/dist/index.js

View file

@ -22,7 +22,7 @@ if [[ -n "$GIT_BRANCH" ]]; then
fi fi
fi fi
CONTRACT_ENV=$ROOT_DIR/tmp/podman/contracts.env CONTRACT_ENV=$ROOT_DIR/tmp/containers/contracts.env
APP_DIR=$ROOT_DIR/web-app APP_DIR=$ROOT_DIR/web-app
SWAP_ROUTER=0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4 SWAP_ROUTER=0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4

View file

@ -57,7 +57,7 @@ services:
- harb-network - harb-network
restart: "no" restart: "no"
healthcheck: healthcheck:
test: ["CMD", "test", "-f", "/workspace/tmp/podman/contracts.env"] test: ["CMD", "test", "-f", "/workspace/tmp/containers/contracts.env"]
interval: 5s interval: 5s
retries: 18 retries: 18
start_period: 10s start_period: 10s

131
docs/docker.md Normal file
View file

@ -0,0 +1,131 @@
# Docker Development Environment
The Docker stack powers `scripts/dev.sh` using containerized services. Every boot spins up a fresh Base Sepolia fork, redeploys contracts, seeds liquidity, and launches the live-reload services behind Caddy on port 8081.
## Service Topology
- `anvil` Base Sepolia fork with optional mnemonic from `onchain/.secret.local`
- `bootstrap` one-shot job running `DeployLocal.sol`, seeding liquidity, priming blocks, and writing shared env files
- `ponder` `npm run dev` for the indexer (port 42069 inside the pod)
- `frontend` Vite dev server for `web-app` (port 5173 inside the pod)
- `txn-bot` automation loop plus Express status API (port 43069 inside the pod)
- `caddy` front door at `http://<host>:80`, routing `/api/graphql`, `/health`, `/api/rpc`, and `/api/txn` to the internal services
All containers mount the repository so code edits hot-reload exactly as the local script. Named volumes keep `node_modules` caches between restarts.
## Prerequisites
### Linux
```bash
# Install Docker Engine
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Logout and login again for group changes to take effect
```
### Mac
```bash
# Install Colima (open-source Docker Desktop alternative)
brew install colima docker docker-compose
# Start Colima VM with recommended resources
colima start --cpu 4 --memory 8 --disk 100
# Verify installation
docker ps
```
## Launching
**Recommended**: Use the helper script
```bash
./scripts/dev.sh start
```
This will:
1. Build kraiken-lib
2. Start Anvil (Base Sepolia fork)
3. Deploy contracts via bootstrap
4. Start Ponder (indexes events)
5. Start web-app, landing, txn-bot
6. Start Caddy reverse proxy on port 8081
**Startup time**: ~6 minutes on first run (includes Ponder indexing 300+ blocks)
**Manual approach** (not recommended):
```bash
docker compose up -d
```
**Stopping the stack:**
```bash
./scripts/dev.sh stop
# or
docker compose down
```
**Quick restarts for development:**
- `./scripts/dev.sh restart --light` - Fast restart (~10-20s): only webapp + txnbot, preserves Anvil/Ponder state. **Use for frontend changes.**
- `./scripts/dev.sh restart --full` - Full restart (~6 min): redeploys contracts, fresh state. **Use for contract changes.**
**Important**: Every full restart redeploys contracts and rewrites `services/ponder/.env.local` and `tmp/containers/txnBot.env`.
### Access Points (via Caddy on port 8081)
**For reviewing code changes in your browser:**
- Landing page: `http://localhost:8081/` (marketing site)
- Web-app: `http://localhost:8081/app/` (staking interface - **use this for testing**)
- GraphQL Playground: `http://localhost:8081/api/graphql`
- TxnBot status: `http://localhost:8081/api/txn/status`
**Direct RPC access:**
- Anvil RPC: `http://localhost:8081/api/rpc` (or `http://localhost:8545` directly)
**Hot reload workflow:**
1. Start stack: `./scripts/dev.sh start`
2. Open `http://localhost:8081/app/` in your browser
3. Edit files in `web-app/src/` - changes appear instantly (Vite HMR)
4. Edit files in `landing/src/` - changes appear on `http://localhost:8081/`
5. Edit smart contracts in `onchain/src/` - requires `./scripts/dev.sh restart --full`
## Configuration Knobs
Set environment variables before `docker-compose up`:
- `FORK_URL` Anvil upstream RPC (defaults to `https://sepolia.base.org`)
- `DEPLOYER_PK`, `DEPLOYER_ADDR` override deployer wallet; otherwise derived from `.secret.local` or Foundry defaults
- `TXNBOT_PRIVATE_KEY`, `TXNBOT_ADDRESS`, `TXNBOT_FUND_VALUE` customise bot signer and funding
Edit `containers/Caddyfile` if you need different routes or ports.
## Known Limitations
- State is ephemeral; every restart wipes the fork and redeploys contracts.
- Processes run in dev/watch mode (`npm run dev`), so staging traffic is not production hardened.
- Secrets live in env files inside the repo mount because no external secret store is wired in.
## Troubleshooting
### Mac: "Cannot connect to Docker daemon"
```bash
# Ensure Colima is running
colima status
colima start
# Verify Docker can connect
docker ps
```
### Permission errors on Linux
```bash
# Add your user to the docker group
sudo usermod -aG docker $USER
# Logout and login again, or use:
newgrp docker
```
### Port conflicts
If you see "port already in use" errors:
```bash
# Check what's using the port
lsof -i :8081 # or :8545, :5173, etc.
# Stop conflicting services or change ports in docker-compose.yml
```

View file

@ -1,44 +0,0 @@
# Podman Staging Environment
The Podman stack mirrors `scripts/dev.sh` using long-lived containers. Every boot spins up a fresh Base Sepolia fork, redeploys contracts, seeds liquidity, and launches the live-reload services behind Caddy on port 80.
## Service Topology
- `anvil` Base Sepolia fork with optional mnemonic from `onchain/.secret.local`
- `bootstrap` one-shot job running `DeployLocal.sol`, seeding liquidity, priming blocks, and writing shared env files
- `ponder` `npm run dev` for the indexer (port 42069 inside the pod)
- `frontend` Vite dev server for `web-app` (port 5173 inside the pod)
- `txn-bot` automation loop plus Express status API (port 43069 inside the pod)
- `caddy` front door at `http://<host>:80`, routing `/api/graphql`, `/health`, `/api/rpc`, and `/api/txn` to the internal services
All containers mount the repository so code edits hot-reload exactly as the local script. Named volumes keep `node_modules` caches between restarts.
## Prerequisites
- Podman 4.x (rootless recommended)
- `podman-compose`
## Launching
```bash
podman-compose -f podman-compose.yml build
podman-compose -f podman-compose.yml up
```
- First run takes several minutes while Foundry installs deps, deploys contracts, and runs the seeding transactions.
- Use `podman-compose down` to stop. Bring-up always redeploys and rewrites `services/ponder/.env.local` plus `tmp/podman/txnBot.env`.
### Access Points (via Caddy)
- Frontend: `http://<host>/`
- GraphQL: `http://<host>/api/graphql`
- RPC passthrough: `http://<host>/api/rpc`
- Txn bot status: `http://<host>/api/txn/status`
## Configuration Knobs
Set environment variables before `podman-compose up`:
- `FORK_URL` Anvil upstream RPC (defaults to `https://sepolia.base.org`)
- `DEPLOYER_PK`, `DEPLOYER_ADDR` override deployer wallet; otherwise derived from `.secret.local` or Foundry defaults
- `TXNBOT_PRIVATE_KEY`, `TXNBOT_ADDRESS`, `TXNBOT_FUND_VALUE` customise bot signer and funding
Edit `containers/Caddyfile` if you need different routes or ports.
## Known Limitations
- State is ephemeral; every restart wipes the fork and redeploys contracts.
- Processes run in dev/watch mode (`npm run dev`), so staging traffic is not production hardened.
- Secrets live in env files inside the repo mount because no external secret store is wired in.

View file

@ -42,7 +42,7 @@ Shared TypeScript helpers used by the landing app, txnBot, and other services to
- `"moduleResolution": "node"` - Enable proper module resolution - `"moduleResolution": "node"` - Enable proper module resolution
- `"rootDir": "./src"` - Ensure flat output structure in `dist/` - `"rootDir": "./src"` - Ensure flat output structure in `dist/`
- **Build Output**: Running `npx tsc` produces ES module `.js` files in `dist/` that can be consumed by both browser (Vite) and Node.js (≥14 with `"type": "module"`). - **Build Output**: Running `npx tsc` produces ES module `.js` files in `dist/` that can be consumed by both browser (Vite) and Node.js (≥14 with `"type": "module"`).
- **Container Mount**: Podman/Docker services now bind-mount `dist/` read-only from the host. Run `./scripts/build-kraiken-lib.sh` before `podman-compose up` or keep `scripts/watch-kraiken-lib.sh` running to rebuild automatically. - **Container Mount**: Docker services bind-mount `dist/` read-only from the host. Run `./scripts/build-kraiken-lib.sh` before `docker-compose up` or keep `scripts/watch-kraiken-lib.sh` running to rebuild automatically.
## Quality Guidelines ## Quality Guidelines
- Keep helpers pure and side-effect free; they should accept explicit dependencies. - Keep helpers pure and side-effect free; they should accept explicit dependencies.

View file

@ -4,17 +4,33 @@ set -euo pipefail
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
# Timeout constants (in seconds) # Timeout constants (in seconds)
readonly ANVIL_TIMEOUT=30 # Anvil starts fast readonly ANVIL_TIMEOUT=60 # Anvil starts fast (increased for first-time setup)
readonly POSTGRES_TIMEOUT=20 # Database init is quick readonly POSTGRES_TIMEOUT=30 # Database init is quick
readonly BOOTSTRAP_TIMEOUT=60 # Contract deployment + seeding readonly BOOTSTRAP_TIMEOUT=120 # Contract deployment + seeding
readonly PONDER_TIMEOUT=90 # Must index bootstrap events readonly PONDER_TIMEOUT=120 # Must index bootstrap events
readonly WEBAPP_TIMEOUT=90 # npm install + Vite startup readonly WEBAPP_TIMEOUT=120 # npm install + Vite startup
readonly CADDY_TIMEOUT=10 # Proxy starts instantly readonly CADDY_TIMEOUT=20 # Proxy starts instantly
readonly POLL_INTERVAL=2 # Check health every N seconds readonly POLL_INTERVAL=2 # Check health every N seconds
PID_FILE=/tmp/kraiken-watcher.pid PID_FILE=/tmp/kraiken-watcher.pid
PROJECT_NAME=${COMPOSE_PROJECT_NAME:-$(basename "$PWD")} PROJECT_NAME=${COMPOSE_PROJECT_NAME:-$(basename "$PWD")}
# Detect container runtime
if command -v docker compose &> /dev/null; then
COMPOSE_CMD="docker compose"
RUNTIME_CMD="docker"
elif command -v docker-compose &> /dev/null; then
COMPOSE_CMD="docker-compose"
RUNTIME_CMD="docker"
else
echo "Error: docker/docker-compose not found. Please install Docker."
echo ""
echo "Installation instructions:"
echo " Linux: https://docs.docker.com/engine/install/"
echo " Mac: brew install colima docker docker-compose && colima start"
exit 1
fi
container_name() { container_name() {
local service="$1" local service="$1"
echo "${PROJECT_NAME}_${service}_1" echo "${PROJECT_NAME}_${service}_1"
@ -28,13 +44,13 @@ cleanup_existing() {
# Remove PID file # Remove PID file
rm -f "$PID_FILE" rm -f "$PID_FILE"
# Kill zombie podman processes # Kill zombie container processes
pkill -9 -f "podman wait.*${PROJECT_NAME}_" 2>/dev/null || true pkill -9 -f "${RUNTIME_CMD} wait.*${PROJECT_NAME}_" 2>/dev/null || true
# Remove any existing containers (suppress errors if they don't exist) # Remove any existing containers (suppress errors if they don't exist)
echo " Cleaning up existing containers..." echo " Cleaning up existing containers..."
podman ps -a --filter "label=com.docker.compose.project=${PROJECT_NAME}" --format "{{.Names}}" 2>/dev/null | \ ${RUNTIME_CMD} ps -a --filter "label=com.docker.compose.project=${PROJECT_NAME}" --format "{{.Names}}" 2>/dev/null | \
xargs -r podman rm -f 2>&1 | grep -v "Error.*no container" || true xargs -r ${RUNTIME_CMD} rm -f 2>&1 | grep -v "Error.*no container" || true
} }
# Wait for container to be healthy (via healthcheck) # Wait for container to be healthy (via healthcheck)
@ -45,7 +61,10 @@ wait_for_healthy() {
local start_time=$(date +%s) local start_time=$(date +%s)
for i in $(seq 1 "$max_attempts"); do for i in $(seq 1 "$max_attempts"); do
if podman healthcheck run "$container" &>/dev/null; then # Docker doesn't have a standalone healthcheck command, check via inspect
local health_status
health_status=$(${RUNTIME_CMD} inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null || echo "unknown")
if [[ "$health_status" == "healthy" ]]; then
local elapsed=$(($(date +%s) - start_time)) local elapsed=$(($(date +%s) - start_time))
echo "$container ready (${elapsed}s)" echo "$container ready (${elapsed}s)"
return 0 return 0
@ -66,7 +85,7 @@ wait_for_exited() {
for i in $(seq 1 "$max_attempts"); do for i in $(seq 1 "$max_attempts"); do
local status local status
status=$(podman inspect "$container" --format='{{.State.Status}}' 2>/dev/null || echo "unknown") status=$(${RUNTIME_CMD} inspect "$container" --format='{{.State.Status}}' 2>/dev/null || echo "unknown")
if [[ "$status" == "exited" ]]; then if [[ "$status" == "exited" ]]; then
local elapsed=$(($(date +%s) - start_time)) local elapsed=$(($(date +%s) - start_time))
echo "$container completed (${elapsed}s)" echo "$container completed (${elapsed}s)"
@ -97,32 +116,32 @@ start_stack() {
# Phase 1: Start base services (no dependencies) # Phase 1: Start base services (no dependencies)
echo " Starting anvil & postgres..." echo " Starting anvil & postgres..."
podman-compose up -d anvil postgres 2>&1 | grep -v "STEP\|Copying\|Writing\|Getting\|fetch\|Installing\|Executing" || true ${COMPOSE_CMD} up -d anvil postgres 2>&1 | grep -v "STEP\|Copying\|Writing\|Getting\|fetch\|Installing\|Executing" || true
wait_for_healthy "$(container_name anvil)" "$ANVIL_TIMEOUT" || exit 1 wait_for_healthy "$(container_name anvil)" "$ANVIL_TIMEOUT" || exit 1
wait_for_healthy "$(container_name postgres)" "$POSTGRES_TIMEOUT" || exit 1 wait_for_healthy "$(container_name postgres)" "$POSTGRES_TIMEOUT" || exit 1
# Phase 2: Start bootstrap (depends on anvil & postgres healthy) # Phase 2: Start bootstrap (depends on anvil & postgres healthy)
echo " Starting bootstrap..." echo " Starting bootstrap..."
podman-compose up -d bootstrap >/dev/null 2>&1 ${COMPOSE_CMD} up -d bootstrap >/dev/null 2>&1
wait_for_exited "$(container_name bootstrap)" "$BOOTSTRAP_TIMEOUT" || exit 1 wait_for_exited "$(container_name bootstrap)" "$BOOTSTRAP_TIMEOUT" || exit 1
# Phase 3: Start ponder (depends on bootstrap completed) # Phase 3: Start ponder (depends on bootstrap completed)
echo " Starting ponder..." echo " Starting ponder..."
podman-compose up -d ponder >/dev/null 2>&1 ${COMPOSE_CMD} up -d ponder >/dev/null 2>&1
wait_for_healthy "$(container_name ponder)" "$PONDER_TIMEOUT" || exit 1 wait_for_healthy "$(container_name ponder)" "$PONDER_TIMEOUT" || exit 1
# Phase 4: Start frontend services (depend on ponder healthy) # Phase 4: Start frontend services (depend on ponder healthy)
echo " Starting webapp, landing, txn-bot..." echo " Starting webapp, landing, txn-bot..."
podman-compose up -d webapp landing txn-bot >/dev/null 2>&1 ${COMPOSE_CMD} up -d webapp landing txn-bot >/dev/null 2>&1
wait_for_healthy "$(container_name webapp)" "$WEBAPP_TIMEOUT" || exit 1 wait_for_healthy "$(container_name webapp)" "$WEBAPP_TIMEOUT" || exit 1
# Phase 5: Start caddy (depends on frontend services) # Phase 5: Start caddy (depends on frontend services)
echo " Starting caddy..." echo " Starting caddy..."
podman-compose up -d caddy >/dev/null 2>&1 ${COMPOSE_CMD} up -d caddy >/dev/null 2>&1
wait_for_healthy "$(container_name caddy)" "$CADDY_TIMEOUT" || exit 1 wait_for_healthy "$(container_name caddy)" "$CADDY_TIMEOUT" || exit 1
@ -142,7 +161,7 @@ start_stack() {
stop_stack() { stop_stack() {
cleanup_existing cleanup_existing
podman-compose down ${COMPOSE_CMD} down
echo "[ok] Stack stopped" echo "[ok] Stack stopped"
} }
@ -151,7 +170,7 @@ check_health() {
local services=(anvil postgres ponder webapp landing txn-bot caddy) local services=(anvil postgres ponder webapp landing txn-bot caddy)
for service in "${services[@]}"; do for service in "${services[@]}"; do
local container local container
container=$(podman ps --all \ container=$(${RUNTIME_CMD} ps --all \
--filter "label=com.docker.compose.project=${PROJECT_NAME}" \ --filter "label=com.docker.compose.project=${PROJECT_NAME}" \
--filter "label=com.docker.compose.service=${service}" \ --filter "label=com.docker.compose.service=${service}" \
--format '{{.Names}}' | head -n1) --format '{{.Names}}' | head -n1)
@ -161,7 +180,9 @@ check_health() {
continue continue
fi fi
if podman healthcheck run "$container" &>/dev/null; then local health_status
health_status=$(${RUNTIME_CMD} inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null || echo "unknown")
if [[ "$health_status" == "healthy" ]]; then
echo " [ok] $service" echo " [ok] $service"
else else
echo " [!!] $service" echo " [!!] $service"
@ -174,12 +195,12 @@ restart_light() {
echo " Preserving Anvil state (contracts remain deployed)" echo " Preserving Anvil state (contracts remain deployed)"
local webapp_container txnbot_container local webapp_container txnbot_container
webapp_container=$(podman ps --all \ webapp_container=$(${RUNTIME_CMD} ps --all \
--filter "label=com.docker.compose.project=${PROJECT_NAME}" \ --filter "label=com.docker.compose.project=${PROJECT_NAME}" \
--filter "label=com.docker.compose.service=webapp" \ --filter "label=com.docker.compose.service=webapp" \
--format '{{.Names}}' | head -n1) --format '{{.Names}}' | head -n1)
txnbot_container=$(podman ps --all \ txnbot_container=$(${RUNTIME_CMD} ps --all \
--filter "label=com.docker.compose.project=${PROJECT_NAME}" \ --filter "label=com.docker.compose.project=${PROJECT_NAME}" \
--filter "label=com.docker.compose.service=txn-bot" \ --filter "label=com.docker.compose.service=txn-bot" \
--format '{{.Names}}' | head -n1) --format '{{.Names}}' | head -n1)
@ -192,8 +213,8 @@ restart_light() {
local start_time=$(date +%s) local start_time=$(date +%s)
echo " Restarting containers..." echo " Restarting containers..."
podman restart "$webapp_container" >/dev/null ${RUNTIME_CMD} restart "$webapp_container" >/dev/null
[[ -n "$txnbot_container" ]] && podman restart "$txnbot_container" >/dev/null [[ -n "$txnbot_container" ]] && ${RUNTIME_CMD} restart "$txnbot_container" >/dev/null
echo " Waiting for webapp to be ready..." echo " Waiting for webapp to be ready..."
local max_attempts=30 local max_attempts=30

View file

@ -10,8 +10,8 @@ if ! command -v inotifywait >/dev/null 2>&1; then
exit 1 exit 1
fi fi
if ! command -v podman >/dev/null 2>&1; then if ! command -v docker >/dev/null 2>&1; then
echo "Error: podman not found on PATH." >&2 echo "Error: docker not found on PATH." >&2
exit 1 exit 1
fi fi
@ -20,7 +20,7 @@ cd "$ROOT_DIR"
restart_services() { restart_services() {
local missing=0 local missing=0
local running local running
mapfile -t running < <(podman ps --format '{{.Names}}') mapfile -t running < <(docker ps --format '{{.Names}}')
for service in "${SERVICES[@]}"; do for service in "${SERVICES[@]}"; do
local found=1 local found=1
@ -37,7 +37,7 @@ restart_services() {
continue continue
fi fi
if ! podman restart "$service" >/dev/null; then if ! docker restart "$service" >/dev/null; then
echo "Warning: failed to restart $service" >&2 echo "Warning: failed to restart $service" >&2
missing=1 missing=1
fi fi

View file

@ -16,7 +16,7 @@ Ponder-based indexer that records Kraiken protocol activity and exposes the Grap
## Development Workflow ## Development Workflow
- Primary path: `nohup ./scripts/dev.sh start &` boots Anvil, deploys contracts, and launches Ponder in watch mode. - Primary path: `nohup ./scripts/dev.sh start &` boots Anvil, deploys contracts, and launches Ponder in watch mode.
- Podman stack: `podman-compose up -d` starts all services including PostgreSQL; bootstrap creates `.env.local` automatically. - Docker stack: `docker-compose up -d` starts all services including PostgreSQL; bootstrap creates `.env.local` automatically.
- Focused debugging: within `services/ponder/`, run `npm install` then `PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK npm run dev` once the stack is already online. - Focused debugging: within `services/ponder/`, run `npm install` then `PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK npm run dev` once the stack is already online.
- For production-style runs, use `npm run build` followed by `PONDER_NETWORK=BASE npm run start` and point `DATABASE_URL` to PostgreSQL if persistence is required. - For production-style runs, use `npm run build` followed by `PONDER_NETWORK=BASE npm run start` and point `DATABASE_URL` to PostgreSQL if persistence is required.