fix(docker): revert healthcheck to wget for Alpine compatibility (#986)

## Problem

PR #906 changed healthcheck commands from `wget` to `curl`, but Alpine Linux
(our base image) does not include `curl` by default, causing all containers
to fail healthchecks with:

```
exec: "curl": executable file not found in $PATH
```

This creates a configuration mismatch:
- docker/Dockerfile.backend uses `wget` ( works)
- docker-compose.yml uses `curl` ( fails)

## Root Cause Analysis

Alpine Linux philosophy is minimalism:
-  `wget` is pre-installed (part of busybox)
-  `curl` requires `apk add curl` (~3MB extra)

The original PR #906 made two commits:
1. First commit (3af8760): `curl` → `wget` ( correct fix)
2. Second commit (333b2ef): `wget` → `curl` ( introduced bug)

The second commit was based on incorrect assumption that "curl is more
widely available than wget in Docker images". This is false for Alpine.

## Solution

Revert healthcheck commands back to `wget` to match:
1. Alpine's pre-installed tools
2. Dockerfile.backend healthcheck (line 68)
3. Docker and Kubernetes best practices

## Testing

 Verified `wget` exists in Alpine containers:
```bash
docker run --rm alpine:latest which wget
# Output: /usr/bin/wget
```

 Added new CI workflow to prevent regression:
- `.github/workflows/pr-docker-compose-healthcheck.yml`
- Validates healthcheck compatibility with Alpine
- Ensures containers reach healthy status

## Impact

- **Before**: Containers show (unhealthy), potential auto-restart loops
- **After**: Containers show (healthy), monitoring systems happy
- **Scope**: Only affects docker-compose deployments
- **Breaking**: None - this is a bug fix

## References

- PR #906: Original (incorrect) fix
- Alpine Linux: https://alpinelinux.org/about/
- Dockerfile.backend L68: Existing `wget` healthcheck

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: the-dev-z <the-dev-z@users.noreply.github.com>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
Co-authored-by: Shui <88711385+hzb1115@users.noreply.github.com>
This commit is contained in:
0xYYBB | ZYY | Bobo
2025-11-16 08:26:12 +08:00
committed by tangmengqiu
parent 11e4022867
commit b66fd5fb0a
2 changed files with 154 additions and 2 deletions

View File

@@ -0,0 +1,152 @@
name: PR Docker Compose Healthcheck
# 驗證 docker-compose.yml 的 healthcheck 配置在 Alpine 容器中正常工作
on:
pull_request:
branches:
- main
- dev
paths:
- 'docker-compose.yml'
- 'docker/Dockerfile.backend'
- 'docker/Dockerfile.frontend'
- '.github/workflows/pr-docker-compose-healthcheck.yml'
jobs:
healthcheck-test:
name: Test Docker Compose Healthcheck
runs-on: ubuntu-22.04
timeout-minutes: 10
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Create minimal .env for testing
run: |
cat > .env <<EOF
# Minimal config for healthcheck testing
NOFX_BACKEND_PORT=8080
NOFX_FRONTEND_PORT=3000
NOFX_TIMEZONE=UTC
DATA_ENCRYPTION_KEY=test-key-32-chars-minimum-length
JWT_SECRET=test-jwt-secret-minimum-32-chars
EOF
- name: Create minimal config.json
run: |
cat > config.json <<EOF
{
"ai_models": [],
"exchanges": [],
"traders": []
}
EOF
- name: Start services with docker compose
run: |
docker compose up -d
echo "✅ Services started, waiting for healthcheck..."
- name: Wait for healthcheck start_period
run: |
echo "⏳ Waiting 70 seconds for healthcheck start_period to complete..."
sleep 70
- name: Verify backend healthcheck
run: |
echo "🔍 Checking backend container health..."
BACKEND_HEALTH=$(docker inspect nofx-trading --format='{{.State.Health.Status}}')
echo "Backend health status: $BACKEND_HEALTH"
if [ "$BACKEND_HEALTH" != "healthy" ]; then
echo "❌ Backend container is not healthy!"
echo "Health status: $BACKEND_HEALTH"
echo ""
echo "Health logs:"
docker inspect nofx-trading --format='{{json .State.Health}}' | jq
echo ""
echo "Container logs:"
docker logs nofx-trading
exit 1
fi
echo "✅ Backend container is healthy"
- name: Verify frontend healthcheck
run: |
echo "🔍 Checking frontend container health..."
FRONTEND_HEALTH=$(docker inspect nofx-frontend --format='{{.State.Health.Status}}')
echo "Frontend health status: $FRONTEND_HEALTH"
if [ "$FRONTEND_HEALTH" != "healthy" ]; then
echo "❌ Frontend container is not healthy!"
echo "Health status: $FRONTEND_HEALTH"
echo ""
echo "Health logs:"
docker inspect nofx-frontend --format='{{json .State.Health}}' | jq
echo ""
echo "Container logs:"
docker logs nofx-frontend
exit 1
fi
echo "✅ Frontend container is healthy"
- name: Verify healthcheck commands are Alpine-compatible
run: |
echo "🔍 Verifying healthcheck commands use Alpine-compatible tools..."
# Check that docker-compose.yml uses wget (not curl)
if grep -q 'test:.*curl' docker-compose.yml; then
echo "❌ ERROR: docker-compose.yml uses 'curl' which doesn't exist in Alpine!"
echo ""
echo "Alpine Linux (used by our containers) includes 'wget' but not 'curl'."
echo "Please use 'wget --no-verbose --tries=1 --spider' instead."
exit 1
fi
if ! grep -q 'test:.*wget' docker-compose.yml; then
echo "⚠️ WARNING: No wget healthcheck found in docker-compose.yml"
else
echo "✅ Healthcheck uses Alpine-compatible 'wget' command"
fi
- name: Test healthcheck commands inside containers
run: |
echo "🧪 Testing healthcheck commands directly..."
# Test backend healthcheck command
echo "Testing backend healthcheck..."
docker exec nofx-trading wget --no-verbose --tries=1 --spider http://localhost:8080/api/health
echo "✅ Backend healthcheck command works"
# Test frontend healthcheck command
echo "Testing frontend healthcheck..."
docker exec nofx-frontend wget --no-verbose --tries=1 --spider http://127.0.0.1/health
echo "✅ Frontend healthcheck command works"
- name: Show container status
if: always()
run: |
echo "📊 Final container status:"
docker ps --format "table {{.Names}}\t{{.Status}}"
- name: Show logs on failure
if: failure()
run: |
echo "📋 Backend logs:"
docker logs nofx-trading
echo ""
echo "📋 Frontend logs:"
docker logs nofx-frontend
- name: Cleanup
if: always()
run: |
docker compose down -v
rm -f .env config.json

View File

@@ -25,7 +25,7 @@ services:
networks:
- nofx-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/api/health"]
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/api/health"]
interval: 30s
timeout: 10s
retries: 3
@@ -45,7 +45,7 @@ services:
depends_on:
- nofx
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1/health"]
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1/health"]
interval: 30s
timeout: 10s
retries: 3