name: Reusable Deploy on: workflow_call: inputs: image_ref: required: true type: string service_name: required: true type: string port: required: false type: string default: '' timeout: required: false type: string default: '120' secrets: SSH_HOST: required: true SSH_USER: required: true SSH_PRIVATE_KEY: required: true jobs: deploy: runs-on: docker-build steps: - name: Deploy uses: appleboy/ssh-action@v1.2.0 with: host: ${{ secrets.SSH_HOST }} username: ${{ secrets.SSH_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} command_timeout: ${{ inputs.timeout }}m script: | set -u SERVICE="${{ inputs.service_name }}" IMAGE="${{ inputs.image_ref }}" echo "🚀 Updating service $SERVICE with image $IMAGE..." # Blocks until converged; auto-rolls back if the new tasks fail if ! timeout "${{ inputs.timeout }}" docker service update \ --force \ --with-registry-auth \ --update-failure-action rollback \ --update-monitor 15s \ --image "$IMAGE" \ "$SERVICE"; then echo "❌ docker service update failed or timed out after ${{ inputs.timeout }}s" docker service ps "$SERVICE" --no-trunc docker service logs --tail 50 "$SERVICE" || true exit 1 fi # Did THIS rollout succeed? (ignores old failed tasks in history) STATE=$(docker service inspect "$SERVICE" \ --format '{{if .UpdateStatus}}{{.UpdateStatus.State}}{{end}}') echo " Update state: ${STATE:-none}" NEW_FAILED=$(docker service ps "$SERVICE" \ --format '{{.Image}} {{.CurrentState}}' \ | grep "^$IMAGE" | grep -c "Failed" || true) if [ "$STATE" = "rollback_completed" ] || [ "$STATE" = "rollback_started" ] \ || [ "$STATE" = "paused" ] || [ "$NEW_FAILED" -gt 0 ]; then echo "❌ Rollout failed (state=$STATE, failed tasks on new image=$NEW_FAILED)" docker service ps "$SERVICE" --no-trunc docker service logs --tail 50 "$SERVICE" || true exit 1 fi REPLICAS=$(docker service ls --filter "name=$SERVICE" --format '{{.Replicas}}') echo "✅ $SERVICE running ($REPLICAS) on $IMAGE" echo "🎉 Deployment successful!"