diff --git a/Dockerfile b/Dockerfile index ca8536b..8fc074c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -56,8 +56,9 @@ VOLUME /app/config RUN mkdir -p /app/data VOLUME /app/data -# Healthy when the server answers and can reach its database -HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --start-interval=2s --retries=3 \ +# Healthy when the server answers and can reach its database. +# (No --start-interval: it needs Docker 25, and deploy/rezervator.sh probes on its own.) +HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ CMD ["/app/rezervator", "-c", "/app/config/config.toml", "--healthcheck"] # Run the server diff --git a/deploy/README.md b/deploy/README.md index 1d11c1a..868aa99 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -20,7 +20,7 @@ The two settings are independent. [Setup with nginx and PostgreSQL on the host]( ## Requirements -- Docker with the Compose plugin, `bash` and `openssl` on the host +- Docker 20.10 or newer, `bash` and `openssl` on the host. The Compose plugin is needed only when Traefik or PostgreSQL run as containers of this stack. - The application image on the host: `docker build -t rezervator:latest .` in the repository root, or pulled from a registry. The image must match the CPU architecture of the host. - A DNS record for every customer domain pointing to the host diff --git a/deploy/rezervator.sh b/deploy/rezervator.sh index 8369e43..330e804 100755 --- a/deploy/rezervator.sh +++ b/deploy/rezervator.sh @@ -267,28 +267,55 @@ run_container() { docker run "${args[@]}" "$image" >/dev/null } -# Waits until the container reports healthy. Returns 1 when it stops or stays unhealthy. +# Waits until the application in the container is up and can reach its database. +# Returns 1 when the container stops or does not get healthy in time. wait_healthy() { - local container="$1" status - for _ in $(seq 1 90); do - status="$(docker inspect --format '{{.State.Status}} {{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$container" 2>/dev/null || echo "missing")" - case "$status" in - "running healthy") - # Traefik picks a new container up a moment after it turns healthy - if [ "$PROXY" = "traefik" ]; then sleep 3; fi - return 0 ;; - # Image without a health check: running for a while is the best we can tell - "running none") sleep 5 - [ "$(docker inspect --format '{{.State.Status}}' "$container" 2>/dev/null)" = "running" ] && return 0 - return 1 ;; - "running starting") ;; - "running unhealthy") return 1 ;; - running*) ;; + local container="$1" state has_check + has_check="$(docker inspect --format '{{if .Config.Healthcheck}}yes{{else}}no{{end}}' "$container" 2>/dev/null || echo "no")" + + if [ "$has_check" = "no" ]; then + # Image from before the health check: running for a while is the best we can tell + sleep 5 + [ "$(docker inspect --format '{{.State.Status}}' "$container" 2>/dev/null)" = "running" ] + return + fi + + # Probe directly instead of waiting for Docker: its first check comes only after the + # check interval, and the faster start interval needs Docker 25 + local ready="no" + for _ in $(seq 1 60); do + state="$(docker inspect --format '{{.State.Status}}' "$container" 2>/dev/null || echo "missing")" + case "$state" in + running) + if docker exec "$container" /app/rezervator -c /app/config/config.toml --healthcheck >/dev/null 2>&1; then + ready="yes" + break + fi ;; + restarting|created) ;; *) return 1 ;; esac + # A container that fails at start is restarted by Docker over and over + if [ "$(docker inspect --format '{{.RestartCount}}' "$container" 2>/dev/null || echo 0)" -ge 3 ]; then + return 1 + fi sleep 2 done - return 1 + [ "$ready" = "yes" ] || return 1 + + if [ "$PROXY" = "traefik" ]; then + # Traefik routes to the container only once Docker itself reports it healthy + for _ in $(seq 1 45); do + state="$(docker inspect --format '{{.State.Health.Status}}' "$container" 2>/dev/null || echo "missing")" + case "$state" in + healthy) sleep 3; return 0 ;; + starting) ;; + *) return 1 ;; + esac + sleep 2 + done + return 1 + fi + return 0 } cmd_init() {