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
85 changes: 85 additions & 0 deletions .github/workflows/images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,88 @@ jobs:
image="ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}:${{ github.sha }}"
docker buildx imagetools inspect "$image" > /dev/null
echo "verified pullable: $image"

# ── Deploy ──────────────────────────────────────────────────────────────────
#
# Triggers the Coolify deployment of the freshly published images. Gated on
# `vars.TRACKER_DEPLOY_ENABLED` so it stays silent until somebody decides to turn it on, and it
# runs only from `main`.
#
# DELIBERATE DIFFERENCE FROM THE CORE'S EQUIVALENT JOB, which this otherwise mirrors: there, an
# unset hook warns and exits 0 for every service independently. That is right for "not configured
# yet" and wrong for "half configured" — set the token, typo one hook, and CD reports success
# while that service never deploys. Here the two states are separated: nothing configured is a
# skip, PARTIALLY configured is a FAILURE. A deploy everybody believes is wired and silently is
# not is the exact shape of the defects this repository has spent the week finding.
deploy:
name: Deploy to Coolify
needs: build-push
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && vars.TRACKER_DEPLOY_ENABLED == 'true'
steps:
- name: Classify the deploy configuration
id: cfg
env:
TOKEN: ${{ secrets.COOLIFY_API_TOKEN }}
HOOK_API: ${{ secrets.COOLIFY_TRACKER_API_DEPLOY_HOOK }}
HOOK_GATEWAY: ${{ secrets.COOLIFY_TRACKER_GATEWAY_DEPLOY_HOOK }}
HOOK_WEB: ${{ secrets.COOLIFY_TRACKER_WEB_DEPLOY_HOOK }}
run: |
set=0; unset=0
for v in "$TOKEN" "$HOOK_API" "$HOOK_GATEWAY" "$HOOK_WEB"; do
if [ -n "$v" ]; then set=$((set+1)); else unset=$((unset+1)); fi
done

if [ "$set" -eq 0 ]; then
echo "::warning::Coolify is not configured (no token, no hooks). Skipping the deploy. Set COOLIFY_API_TOKEN and the three hooks to enable CD."
echo "state=absent" >> "$GITHUB_OUTPUT"
exit 0
fi
if [ "$unset" -gt 0 ]; then
echo "::error::Coolify is PARTIALLY configured: $set of 4 values present. A half-wired deploy reports success while some services never ship, so this fails instead of warning. Set all of COOLIFY_API_TOKEN, COOLIFY_TRACKER_API_DEPLOY_HOOK, COOLIFY_TRACKER_GATEWAY_DEPLOY_HOOK and COOLIFY_TRACKER_WEB_DEPLOY_HOOK, or unset them all."
exit 1
fi
echo "state=complete" >> "$GITHUB_OUTPUT"

- name: Trigger the deploys
if: steps.cfg.outputs.state == 'complete'
env:
TOKEN: ${{ secrets.COOLIFY_API_TOKEN }}
HOOK_API: ${{ secrets.COOLIFY_TRACKER_API_DEPLOY_HOOK }}
HOOK_GATEWAY: ${{ secrets.COOLIFY_TRACKER_GATEWAY_DEPLOY_HOOK }}
HOOK_WEB: ${{ secrets.COOLIFY_TRACKER_WEB_DEPLOY_HOOK }}
run: |
failed=0
trigger() {
# `--fail` matters: without it curl exits 0 on a 4xx and the deploy would report
# success for a hook that was rejected.
if curl --fail --silent --show-error -X GET "$2" -H "Authorization: Bearer $TOKEN" -o /dev/null; then
echo "$1 deploy triggered"
else
echo "::error::$1 deploy hook did not accept the request"
failed=1
fi
}
# All three are attempted before failing, so one bad hook does not hide the state of the
# other two — the same reason the build matrix does not fail fast.
trigger tracker-api "$HOOK_API"
trigger tracker-gateway "$HOOK_GATEWAY"
trigger tracker-web "$HOOK_WEB"
exit $failed

# Triggering is not deploying. Coolify accepts the webhook and works asynchronously, so a
# green step here means "the request was accepted", never "the new image is serving". Said
# out loud because GT-448 requires a deploy evidenced by a recorded run rather than by a job
# exiting zero, and this job cannot provide that evidence.
- name: What this job did and did not prove
if: steps.cfg.outputs.state == 'complete'
run: |
{
echo '### Deploy triggered'
echo ''
echo 'Coolify accepted the webhook for all three services at `${{ github.sha }}`.'
echo ''
echo '> This proves the REQUEST was accepted. It does not prove the new image is'
echo '> serving traffic: Coolify deploys asynchronously. GT-448 asks for a recorded'
echo '> run against the live service, which has to be taken there, not here.'
} >> "$GITHUB_STEP_SUMMARY"
24 changes: 24 additions & 0 deletions product/infra/helm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,30 @@ actually been exercised.
`check-deployable-images.mjs` runs in CI and fails when a chart names an image no workflow
publishes, a local-only tag, or a semver with no git tag behind it.

### Turning the deploy on

`images.yml` also carries a `deploy` job. It is gated on the repository variable
`TRACKER_DEPLOY_ENABLED` and runs only from `main`, so it stays silent until somebody decides to
turn it on.

| what | where |
|---|---|
| `TRACKER_DEPLOY_ENABLED=true` | repository **variable** |
| `COOLIFY_API_TOKEN` | repository **secret** |
| `COOLIFY_TRACKER_API_DEPLOY_HOOK` | repository secret |
| `COOLIFY_TRACKER_GATEWAY_DEPLOY_HOOK` | repository secret |
| `COOLIFY_TRACKER_WEB_DEPLOY_HOOK` | repository secret |

**Nothing configured is a skip; PARTIALLY configured is a failure.** That is a deliberate
difference from the Core's equivalent job, where an unset hook warns and exits zero per service:
set the token, typo one hook, and CD reports success while that service never deploys. Half-wired
is not a state worth being quiet about.

**And triggering is not deploying.** Coolify accepts the webhook and works asynchronously, so a
green job means the *request* was accepted — never that the new image is serving. `GT-448` asks for
a deploy evidenced by a recorded run against the live service, and that evidence has to be taken
there, not here. The job says so in its own step summary rather than letting the tick imply it.

## Notes

- **Connection string.** The .NET config system cannot concatenate a password
Expand Down
Loading