#!/usr/bin/env bash # # Wait until the named compose services report a healthy healthcheck. # # Portable across `docker compose` (CI) and `podman-compose` (local dev): it uses # plain `docker ps` + `docker inspect`, so it needs neither `docker compose # up --wait` (podman-compose doesn't implement that flag) nor host port access # (the containerized CI runner can't reach published ports). It also sidesteps the # `--wait`-fails-when-a-one-shot-exits issue, since we only poll long-running # services that declare a healthcheck. See docs/runbooks/gitea-actions-gotchas.md. # # Usage: WAIT_TIMEOUT=420 wait-healthy.sh [ ...] set -euo pipefail timeout="${WAIT_TIMEOUT:-420}" deadline=$(( $(date +%s) + timeout )) # compose service name -> container id. `--filter name=` is a substring match, so it is anchored on # the compose replica suffix — otherwise 'objecten' also matches objecten-db / objecten-redis / # objecten-celery, and 'objecttypen' matches objecttypen-db. Whichever docker listed first won, so a # service with a sibling that has no healthcheck timed out with status=none while it was in fact # healthy. The pattern matches both docker compose ("infra-objecten-1") and podman-compose # ("infra_objecten_1") naming; the same anchoring the verify check scripts use. cid_for() { docker ps -aq --filter "name=$1[-_][0-9]+\$" | head -1; } for svc in "$@"; do echo "waiting for '$svc' to be healthy (timeout ${timeout}s)..." while :; do cid="$(cid_for "$svc")" status="" [ -n "$cid" ] && status="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$cid" 2>/dev/null || true)" [ "$status" = "healthy" ] && { echo " '$svc' is healthy"; break; } if [ "$(date +%s)" -ge "$deadline" ]; then echo "TIMEOUT: '$svc' not healthy (status=${status:-no-container})" >&2 docker ps -a --filter "name=$svc" >&2 || true exit 1 fi sleep 3 done done