#!/usr/bin/env bash # wait-healthy.sh — poll docker compose services until all are healthy/running. # Invoked by the compose-wait-healthy composite action. Configure via env vars: # WAIT_HEALTHY_SERVICES whitespace-separated list of service names (required) # WAIT_HEALTHY_DEADLINE seconds before the script fails (default: 600) # WAIT_HEALTHY_POLL seconds between polls (default: 5) # COMPOSE_PROJECT compose project name passed via -p (optional) set -euo pipefail compose() { if [[ -n "${COMPOSE_PROJECT:-}" ]]; then docker compose -p "${COMPOSE_PROJECT}" "$@" else docker compose "$@" fi } read -r -a services <<< "${WAIT_HEALTHY_SERVICES}" if [[ ${#services[@]} -eq 0 ]]; then echo "ERROR: WAIT_HEALTHY_SERVICES is empty" >&2 exit 64 fi deadline="${WAIT_HEALTHY_DEADLINE:-600}" poll="${WAIT_HEALTHY_POLL:-5}" start="$(date +%s)" while :; do pending=() for svc in "${services[@]}"; do cid="$(compose ps -q "${svc}" || true)" if [[ -z "${cid}" ]]; then pending+=("${svc}(no container)") continue fi status="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "${cid}" 2>/dev/null || echo unknown)" case "${status}" in healthy|running) ;; *) pending+=("${svc}=${status}") ;; esac done if [[ ${#pending[@]} -eq 0 ]]; then echo "all services healthy: ${services[*]}" exit 0 fi elapsed=$(( $(date +%s) - start )) if (( elapsed > deadline )); then echo "FAIL: timeout after ${elapsed}s waiting on: ${pending[*]}" >&2 for svc in "${services[@]}"; do echo "----- ${svc} (last 50 log lines) -----" >&2 compose logs --no-color --tail=50 "${svc}" >&2 || true done exit 1 fi printf '[%4ds] waiting on: %s\n' "${elapsed}" "${pending[*]}" sleep "${poll}" done