Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions .github/actions/trigger-coolify-deploy/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ inputs:
preview's generated fqdn.
required: false
default: ""
health_check_origin_ip:
description: >-
Origin server IP to connect the health check directly to (via curl --resolve), bypassing
any reverse proxy in front of the health-check hostname. Required for hostnames proxied
through Cloudflare: Cloudflare's Bot Fight Mode issues an instant JS challenge (HTTP 403)
to GitHub Actions runner IPs on any proxied hostname, and it runs outside the Ruleset
Engine, so no Cloudflare Custom Rule can exempt it — the only way to reach the app's real
health check is to skip the proxy for this one request. TLS still validates normally
since the hostname (SNI/Host) is unchanged, only the connect-to address is overridden.
required: false
default: ""

runs:
using: composite
Expand All @@ -75,6 +86,7 @@ runs:
PREVIEW_NOT_FOUND_TIMEOUT_SECONDS: ${{ inputs.preview_not_found_timeout_seconds }}
HEALTH_CHECK_PATH: ${{ inputs.health_check_path }}
HEALTH_CHECK_BASE_URL_OVERRIDE: ${{ inputs.health_check_base_url_override }}
HEALTH_CHECK_ORIGIN_IP: ${{ inputs.health_check_origin_ip }}
run: |
set -euo pipefail

Expand Down Expand Up @@ -207,10 +219,19 @@ runs:
fi

health_url="${base_url%/}${HEALTH_CHECK_PATH}"
echo "Health-checking ${health_url} (timeout ${POLL_TIMEOUT_SECONDS}s)..."

resolve_opts=()
if [ -n "$HEALTH_CHECK_ORIGIN_IP" ]; then
health_host=$(printf '%s' "$base_url" | sed -E 's#^[a-zA-Z]+://##; s#[/:].*##')
resolve_opts=(--resolve "${health_host}:443:${HEALTH_CHECK_ORIGIN_IP}")
echo "Health-checking ${health_url} directly via ${HEALTH_CHECK_ORIGIN_IP} (bypassing any proxy in front of ${health_host}), timeout ${POLL_TIMEOUT_SECONDS}s..."
else
echo "Health-checking ${health_url} (timeout ${POLL_TIMEOUT_SECONDS}s)..."
fi
Comment on lines +223 to +230

start=$(date +%s)
while :; do
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$health_url" || true)
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "${resolve_opts[@]}" "$health_url" || true)
if [ "$code" = "200" ]; then
echo "Health check passed."
break
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ All contributors (including maintainers) should update `CHANGELOG.md` when creat

## [Unreleased]

### Fixed

- **trigger-coolify-deploy**: added `health_check_origin_ip` input so the health check can `curl --resolve` the app's origin IP directly instead of going through Cloudflare. Cloudflare's Bot Fight Mode issues an instant JS challenge (HTTP 403) to GitHub Actions runner IPs on any Cloudflare-proxied hostname, and it runs outside the Ruleset Engine so no Custom Rule can exempt it — without this, health checks against proxied staging/prod hostnames always fail even though the deployment itself succeeded.

## [4.2.0] - 2026-08-08


Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ App-agnostic composite action: trigger a Coolify application deploy via API, pol
| `preview_not_found_timeout_seconds`| No | Max seconds to retry a PR-preview deploy trigger while Coolify hasn't created the preview yet. Default `180` |
| `health_check_path` | No | Path (e.g. `/health`) to poll for HTTP 200 after the deployment finishes |
| `health_check_base_url_override` | No | Base URL to health-check instead of the app's own fqdn. Required when both `pr_number` and `health_check_path` are set (Coolify's API doesn't expose a preview's fqdn) |
| `health_check_origin_ip` | No | Origin server IP to `curl --resolve` the health check directly to, bypassing any reverse proxy in front of the hostname. Needed for Cloudflare-proxied hostnames: Bot Fight Mode 403s GitHub Actions runner IPs on any proxied hostname and runs outside the Ruleset Engine, so no Custom Rule can exempt it |

```yaml
- name: Trigger tmd-admin-api deploy
Expand All @@ -122,6 +123,7 @@ App-agnostic composite action: trigger a Coolify application deploy via API, pol
domain: ${{ vars.DOMAIN_NAME }}
coolify_api_token: ${{ secrets.COOLIFY_API_TOKEN }}
health_check_path: /health
health_check_origin_ip: ${{ vars.SERVER_HOST }} # bypass Cloudflare proxy (Bot Fight Mode 403s CI runners)
```

### Deploy App Env File
Expand Down