#!/bin/sh # Zero-downtime rolling deploy for the pawfeed-1/2/3 replicas. # # Builds the shared image once, then restarts each replica one at a time, # waiting for the Docker healthcheck (GET /api/health, see # docker-compose.yml) to report "healthy" before moving to the next one. # At every point at least 2 of 3 replicas are serving traffic, so NPM's # least_conn upstream (docker/npm-upstream.conf) never has fewer than 2 # healthy backends. `docker compose up -d --build` (start.sh) still works # for a first-time deploy or when downtime doesn't matter — use this # script for routine deploys against a live site instead. set -e cd "$(dirname "$0")" if [ ! -f .env ]; then echo "ERROR: docker/.env not found." echo "Copy .env.example to docker/.env and fill in your values." exit 1 fi export SENTRY_RELEASE="$(git -C .. rev-parse --short HEAD)" REPLICAS="pawfeed-1 pawfeed-2 pawfeed-3" TIMEOUT_SECS=60 echo "Building shared image..." docker compose build pawfeed-1 wait_healthy() { replica="$1" elapsed=0 while [ "$elapsed" -lt "$TIMEOUT_SECS" ]; do status="$(docker inspect --format='{{.State.Health.Status}}' "$replica" 2>/dev/null || echo "starting")" if [ "$status" = "healthy" ]; then return 0 fi sleep 2 elapsed=$((elapsed + 2)) done return 1 } for replica in $REPLICAS; do echo "--- Redeploying $replica ---" docker compose up -d --no-deps "$replica" echo "Waiting for $replica to report healthy (up to ${TIMEOUT_SECS}s)..." if wait_healthy "$replica"; then echo "$replica is healthy." else echo "ERROR: $replica did not become healthy within ${TIMEOUT_SECS}s." echo "Check: docker logs $replica" echo "Aborting before touching the remaining replicas — the ones already" echo "redeployed keep running, the rest are untouched and still on the old image." exit 1 fi done echo "Rolling deploy complete — all 3 replicas updated with zero downtime."