coolify/DOCKER_COMPOSE_EXAMPLES.md
Andras Bacsai fe27a99db2 feat: add docker-compose health check examples and github runner migration
This commit adds:
- Comprehensive docker-compose examples for health check testing
- GitHub runner sources database migration
- UI fix for service heading view

Files Added:
- DOCKER_COMPOSE_EXAMPLES.md - Documentation of health check test cases
- docker-compose.*.yml - Test files for various health check scenarios:
  - excluded.yml: Container with exclude_from_hc flag
  - healthy.yml: All containers healthy
  - unhealthy.yml: All containers unhealthy
  - unknown.yml: Container without healthcheck
  - mixed-healthy-unknown.yml: Mix of healthy and unknown
  - mixed-unhealthy-unknown.yml: Mix of unhealthy and unknown
- database/migrations/2025_11_19_115504_create_github_runner_sources_table.php

Files Modified:
- resources/views/livewire/project/service/heading.blade.php

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 13:24:02 +01:00

7.4 KiB

Docker Compose Examples for Testing Health Status Aggregation

These example docker-compose files demonstrate different container health status scenarios to test the "unknown" health state aggregation fix.

Prerequisites

# Make sure Docker is running
docker --version

Test Cases

1. Healthy - All containers with passing health checks

File: docker-compose.healthy.yml

docker-compose -f docker-compose.healthy.yml up -d
docker-compose -f docker-compose.healthy.yml ps
docker inspect $(docker-compose -f docker-compose.healthy.yml ps -q web) | grep -A 5 '"Health"'

Expected Status: running (healthy)

  • Container has healthcheck that successfully connects to nginx on port 80

Cleanup:

docker-compose -f docker-compose.healthy.yml down

2. Unknown - Container without health check

File: docker-compose.unknown.yml

docker-compose -f docker-compose.unknown.yml up -d
docker-compose -f docker-compose.unknown.yml ps
docker inspect $(docker-compose -f docker-compose.unknown.yml ps -q web) | grep -A 5 '"Health"'

Expected Status: running (unknown)

  • Container has NO healthcheck defined
  • State.Health key is missing from Docker inspect output

Cleanup:

docker-compose -f docker-compose.unknown.yml down

3. Unhealthy - Container with failing health check

File: docker-compose.unhealthy.yml

docker-compose -f docker-compose.unhealthy.yml up -d
# Wait 30 seconds for health check to fail
sleep 30
docker-compose -f docker-compose.unhealthy.yml ps
docker inspect $(docker-compose -f docker-compose.unhealthy.yml ps -q web) | grep -A 5 '"Health"'

Expected Status: running (unhealthy)

  • Container has healthcheck that tries to connect to port 9999 (which doesn't exist)
  • Health check will fail after retries

Cleanup:

docker-compose -f docker-compose.unhealthy.yml down

4. Mixed: Healthy + Unknown → Should show "unknown"

File: docker-compose.mixed-healthy-unknown.yml

docker-compose -f docker-compose.mixed-healthy-unknown.yml up -d
docker-compose -f docker-compose.mixed-healthy-unknown.yml ps
docker inspect $(docker-compose -f docker-compose.mixed-healthy-unknown.yml ps -q web) | grep -A 5 '"Health"'
docker inspect $(docker-compose -f docker-compose.mixed-healthy-unknown.yml ps -q worker) | grep -A 5 '"Health"'

Expected Aggregated Status: running (unknown)This is the fix!

  • web container: running (healthy) - has passing healthcheck
  • worker container: running (unknown) - no healthcheck
  • Before fix: Would show running (healthy)
  • After fix: Shows running (unknown)

Cleanup:

docker-compose -f docker-compose.mixed-healthy-unknown.yml down

5. Mixed: Unhealthy + Unknown → Should show "unhealthy"

File: docker-compose.mixed-unhealthy-unknown.yml

docker-compose -f docker-compose.mixed-unhealthy-unknown.yml up -d
# Wait 30 seconds for health check to fail
sleep 30
docker-compose -f docker-compose.mixed-unhealthy-unknown.yml ps
docker inspect $(docker-compose -f docker-compose.mixed-unhealthy-unknown.yml ps -q web) | grep -A 5 '"Health"'
docker inspect $(docker-compose -f docker-compose.mixed-unhealthy-unknown.yml ps -q worker) | grep -A 5 '"Health"'

Expected Aggregated Status: running (unhealthy)

  • web container: running (unhealthy) - failing healthcheck
  • worker container: running (unknown) - no healthcheck
  • Unhealthy takes priority over unknown

Cleanup:

docker-compose -f docker-compose.mixed-unhealthy-unknown.yml down

6. Excluded Container - Unhealthy container excluded from health checks

File: docker-compose.excluded.yml

docker-compose -f docker-compose.excluded.yml up -d
# Wait 30 seconds for health check to fail
sleep 30
docker-compose -f docker-compose.excluded.yml ps
docker inspect $(docker-compose -f docker-compose.excluded.yml ps -q web) | grep -A 5 '"Health"'
docker inspect $(docker-compose -f docker-compose.excluded.yml ps -q backup) | grep -A 5 '"Health"'

Expected Aggregated Status: running (healthy)

  • web container: running (healthy) - passing healthcheck
  • backup container: running (unhealthy) - but has exclude_from_hc: true
  • Excluded containers don't affect aggregation

Cleanup:

docker-compose -f docker-compose.excluded.yml down

Quick Test All Cases

# Test healthy
echo "=== Testing HEALTHY ==="
docker-compose -f docker-compose.healthy.yml up -d && sleep 15
docker inspect $(docker-compose -f docker-compose.healthy.yml ps -q web) --format='{{.State.Status}} ({{if .State.Health}}{{.State.Health.Status}}{{else}}unknown{{end}})'
docker-compose -f docker-compose.healthy.yml down

# Test unknown
echo -e "\n=== Testing UNKNOWN ==="
docker-compose -f docker-compose.unknown.yml up -d && sleep 5
docker inspect $(docker-compose -f docker-compose.unknown.yml ps -q web) --format='{{.State.Status}} ({{if .State.Health}}{{.State.Health.Status}}{{else}}unknown{{end}})'
docker-compose -f docker-compose.unknown.yml down

# Test unhealthy
echo -e "\n=== Testing UNHEALTHY ==="
docker-compose -f docker-compose.unhealthy.yml up -d && sleep 35
docker inspect $(docker-compose -f docker-compose.unhealthy.yml ps -q web) --format='{{.State.Status}} ({{if .State.Health}}{{.State.Health.Status}}{{else}}unknown{{end}})'
docker-compose -f docker-compose.unhealthy.yml down

# Test mixed healthy + unknown
echo -e "\n=== Testing MIXED HEALTHY + UNKNOWN ==="
docker-compose -f docker-compose.mixed-healthy-unknown.yml up -d && sleep 15
echo "Web: $(docker inspect $(docker-compose -f docker-compose.mixed-healthy-unknown.yml ps -q web) --format='{{.State.Status}} ({{if .State.Health}}{{.State.Health.Status}}{{else}}unknown{{end}})')"
echo "Worker: $(docker inspect $(docker-compose -f docker-compose.mixed-healthy-unknown.yml ps -q worker) --format='{{.State.Status}} ({{if .State.Health}}{{.State.Health.Status}}{{else}}unknown{{end}})')"
echo "Expected Aggregated: running (unknown)"
docker-compose -f docker-compose.mixed-healthy-unknown.yml down

# Cleanup all
echo -e "\n=== Cleaning up ==="
docker-compose -f docker-compose.healthy.yml down 2>/dev/null
docker-compose -f docker-compose.unknown.yml down 2>/dev/null
docker-compose -f docker-compose.unhealthy.yml down 2>/dev/null
docker-compose -f docker-compose.mixed-healthy-unknown.yml down 2>/dev/null
docker-compose -f docker-compose.mixed-unhealthy-unknown.yml down 2>/dev/null
docker-compose -f docker-compose.excluded.yml down 2>/dev/null

Understanding the Output

Docker Inspect Health Status

"Health": {
    "Status": "healthy",    // or "unhealthy", "starting"
    "FailingStreak": 0,
    "Log": [...]
}

If "Health" key is missing → Container has no healthcheck → Shows as unknown

Coolify Status Format

Individual containers: "<status> (<health>)"

  • "running (healthy)" - Container running with passing healthcheck
  • "running (unhealthy)" - Container running with failing healthcheck
  • "running (unknown)" - Container running with no healthcheck
  • "running (starting)" - Container running, healthcheck in initial grace period

Aggregation Priority (after fix)

  1. Unhealthy (highest) - If ANY container is unhealthy
  2. Unknown (medium) - If no unhealthy, but ≥1 has no healthcheck
  3. Healthy (lowest) - Only when ALL containers explicitly healthy