Deploy the scanned digest, and roll back when it never goes healthy - #141
Merged
Conversation
Three problems in the deploy step, all of which only show up on the day something goes wrong. **It deployed a moving tag.** `docker compose pull` fetched whatever `:main` pointed at, not the image CI had just built and scanned with Trivy. A push landing between the scan and the rollout ships something nobody checked. The digest is now passed through from the docker job and exported as SECRET_IMAGE, so the rollout is pinned to the exact artifact that passed the gates. **A failed deploy left the failure running.** The health loop waited 60s and exited 1, which turned the workflow red but left the broken container serving traffic — and `docker image prune -f`, unfiltered, had already deleted the previous image, so there was nothing to go back to. The loop now records what was running before, waits up to 200s (60s is short for a cold start with a WAL replay), prints the last 50 log lines on failure, and restores the previous image. The prune keeps a week of history. **Nothing described any of this.** docs/self-hosting.md gains a "Deploying from CI" section explaining why the digest matters and why a green `docker compose up` says nothing about whether the app works, plus a reference docker-compose.prod.yml showing SECRET_IMAGE, the health check the rollout depends on, memory limits and log rotation — all generic, nothing tied to a particular instance. The script cannot be exercised without deploying, so its logic was replayed locally against stubbed docker commands: healthy (pull, up, prune, exit 0), unhealthy with a previous image (rollback, exit 1), unhealthy without one (warn, exit 1), and stuck in "starting" (rollback, exit 1). Also removed a duplicate health loop this had introduced alongside the existing one.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three problems in the deploy step. None of them are visible until the day something goes wrong.
It deployed a moving tag
docker compose pullfetched whatever:mainpointed at — not the image CI had just built and scanned with Trivy. A push landing between the scan and the rollout ships something nobody checked, and once the tag has moved there is no record of what was running.The digest now flows from the
dockerjob todeployand is exported asSECRET_IMAGE, so the rollout is pinned to the exact artifact that passed the gates.A failed deploy left the failure running
The existing health loop waited 60s and exited 1. That turned the workflow red — and left the broken container serving traffic. Worse,
docker image prune -fhad already run unfiltered, deleting the previous image, so there was nothing to roll back to even by hand.Now: record what is running, wait up to 200s (60s is short for a cold start with a WAL replay), and on failure print the last 50 log lines and restore the previous image. The prune keeps a week of history.
Nothing described any of it
docs/self-hosting.mdgains a Deploying from CI section — why the digest matters, and why a greendocker compose upsays nothing about whether the app actually works — plus a referencedocs/examples/docker-compose.prod.ymlshowingSECRET_IMAGE, the health check the rollout depends on, memory limits and log rotation.All generic. Nothing tied to a particular instance, so anyone self-hosting can lift it.
How this was verified
The script cannot run without deploying, so its logic was replayed locally against stubbed
dockercommands:exit 0exit 1exit 1startingexit 1Validated with actionlint. Also removed a duplicate health loop I had introduced alongside the existing one — worth a look in review, since two loops with different timeouts would have been a confusing thing to inherit.
One thing to do after merging
Your production compose needs to accept the variable, otherwise the digest is ignored and the deploy behaves exactly as before (no breakage, just no pinning):
I can apply that on the server once this is merged.