GitHub Actions or GitLab CI deploys over SSH
The second-largest segment on Hetzner deploys with a CI job over SSH. The webhook adapter keeps that job: Hetscale tells it where the new server is, the job deploys, the load balancer decides when the node may serve.
- Prerequisite work
- deploy to the IP Hetscale sends
- Time to set up
- 1–2 h
- Adapter
- Webhook
- Fit
- 8/10
Prerequisites, time and fit are the ADR-0028 row for this case. Hetscale's own setup is about 15 minutes in every case; the rest is work on your side.
Steps
- 01Receive node.created
Hetscale sends a signed node.created event with the node id, its IPs, the group and the current release when a server is booted and reachable. Deliveries are idempotent by node id, so a duplicate is safe.
- 02Run your deploy against that IP
Trigger the workflow you already have (for GitHub Actions, a workflow_dispatch recipe) with the IP as input. Your SSH key stays in your CI; Hetscale never holds it.
- 03Optionally report ready or failed
A ready callback speeds the decision up and a failed callback stops it at once, but traffic opens only when the load balancer health check passes; the callback is never the source of truth.
- 04Rebuild host lists from the inventory API
GET /groups/{id}/nodes returns the current nodes with phase, IPs and release. Use it instead of a hand-kept host list.
- 05Hold a deploy lock during your own rollouts
POST /groups/{id}/lock with a TTL pauses scaling while your pipeline rolls the fleet; the lock expires on its own if the pipeline dies.
- 06Observe, rehearse, go Live
Read-only trial first; then one Rehearsal, which exercises the webhook end to end; then Live with minimum 2.
FAQ for this case
What if my pipeline never reports back?
The load balancer decides anyway. A node that is not healthy within the deadline (default 15 minutes) is destroyed and logged with the load balancer’s reason; three consecutive failures pause the group.
We have no load balancer yet.
Then this is the “CI/SSH deploy, no LB” row of the fit table: add the Hetzner load balancer first (TLS to it, DNS to it) and teach the pipeline the IP. Two to three hours, fit 6.