deploy-vps fails deployments that succeed: the health wait is 60s #22
Labels
No labels
priority/P0
priority/P1
priority/P2
priority/P3
size/L
size/M
size/S
size/XL
size/XS
state/done
state/in-progress
state/in-review
state/needs-refinement
state/ready
type/bug
type/chore
type/feature
type/refactor
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
amtronics/platform-actions#22
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
deploy-vpswaits sixty seconds for a container to report healthy —for attempt in $(seq 1 30)withsleep 2— and fails the deployment when it has not. On a loaded host that is shorter than a container takes to start, so the action reports a failure for a deployment that then completes successfully. The pipeline cries wolf, and the next red deploy is one nobody trusts.Observed on
ahmad/portfoliov0.15.1, 2 September 2026 (times UTC):https://ahmadelmasri.com/api/health{"status":"ok","version":"v0.15.1"}The deployment worked. The api came healthy a minute or two after the second job had already given up and marked the run red, and production has been serving the new version since. Two red runs, nothing wrong with the release.
The host is the fleet's I/O-saturated runner host — see
amtronics/infrastructure-vps#390— so "the container is slow to start" is the normal case here rather than the exception.Scope
deploy-vpswaits for a container's healthcheck, and what it does when the wait runs out.Acceptance criteria
unhealthyrather thanstarting, when the deployment runs, then it fails without waiting out the full budget — a crash-looping image should not cost the whole timeout.README.mdis read, then the new input and its default are documented alongside the other inputs.Notes
Waiting on Docker's own health status is right; only the budget is wrong. Distinguishing
startingfromunhealthyis what lets the budget grow without making a genuinely broken image slow to report — Docker setsunhealthyonly after the image's own retries are exhausted, so it is already a settled verdict when it appears.Taking this. It is a shared action, so I want the change and its blast radius stated before the diff exists.
What changes. The health wait becomes a budget in seconds rather than a fixed thirty attempts, exposed as a
health-timeoutinput with a default long enough for these hosts. The loop also distinguishes Docker's two negative answers:startingmeans keep waiting,unhealthymeans stop now — Docker only reportsunhealthyafter the image's own retries are exhausted, so it is a settled verdict and waiting out the rest of the budget on it would make a genuinely broken image slow to report.What does not change. Callers that set nothing keep working; the only difference they see is that a slow-starting container is no longer called a failure. A container that never becomes healthy still fails the deployment and still prints its logs.
Blast radius, stated plainly. Merging here moves the
v3tag, so every repository consumingdeploy-vps@v3picks this up on its next deployment. That is the mechanism working as designed, and it is why the default matters more than the input: I am choosing ten minutes, which is roughly six times the observed worst case onahmad/portfolioand still far short of a run that hangs.Ambiguity, and how I am resolving it. I cannot exercise this against a real host — the deploy key is in OpenBao and out of bounds for me — so I will test the loop as a shell function against a stubbed
docker inspect, covering healthy-immediately, healthy-late, never-healthy and unhealthy-early, and say plainly in the pull request that the first real deployment is the live proof.