Repository navigation
Push to deploy with GitHub: webhooks, previews and what happens on every push #1244
thegdsks
started this conversation in
Show and tell
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
"Git push and it deploys" is easy to demo and hard to trust. GitHub redelivers an event. Someone pushes to the wrong branch, or pushes while a build is already running, or fixes a typo in a monorepo's README. A platform that handles these badly leaves you staring at a green push and an unchanged site.
Levelrail answers every push with a specific reason, and records it. This post covers what happens on each push, what every response means, and how to find out in about ten seconds why a push did nothing. The messages and status codes below are quoted from the Git integrations docs and the webhook handler source.
Contents
Two ways to connect a repo
There are two routes. One trades control for convenience, the other the reverse.
use-as-sourceWith the App, Levelrail mints a fresh installation token on every call, so there is no clone credential to rotate by hand. The alternative is a deploy key or personal token, which lives until someone remembers to replace it.
Connecting the App is a browser flow (GitHub's redirects cannot be scripted), done at
/settings/github-appin the dashboard. After that, one command wires a repo to an app and registers the webhook:If registration fails, the repo is still connected and you get
webhook_registered: falsewith awebhook_error. The usual cause is an App installed before it had therepository_hooks:writepermission.The per-repo path
For an existing app, point it at a repo yourself, then print what GitHub wants to see:
levelrail-cli apps git-source set storefront \ --repo-url https://github.com/acme/storefront.git \ --branch main \ --build-type dockerfile levelrail-cli apps git-source webhook storefrontwebhookprints the absolute payload URL, the content type (application/json) and the events to subscribe to (pushandpull_request). It never shows the secret on a plain call. To get one, mint a new secret with--rotate:Warning
--rotatetakes effect immediately. The old secret stops verifying the moment the call returns, so update the webhook in GitHub before the next push.In GitHub, add the webhook under Settings, Webhooks, with the printed URL,
application/json, the secret and the two events. GitHub sends apingstraight away and Levelrail answers200 ping event acknowledged, so the first delivery in GitHub's UI is green instead of a confusing 400.Which pushes deploy: branch, tags, paths
In the default
pushtrigger mode, a git source deploys only on pushes to its configured branch. Everything else is accepted and ignored, with the reason written down.For monorepos, filter by what changed. Each flag replaces the whole list, and an empty value clears it:
Globs are doublestar style and comma separated, and tag pushes are never path filtered. If every changed file falls outside the filter, the response is
ignored: skipped: no changed path matched paths. When GitHub's payload has no file list (a push of more than 20 commits), the control plane asks the provider for it. If that lookup fails, the push deploys anyway. Skipping a real change silently is the worse mistake.Build location is a separate setting:
--base-directorysets the Docker build context, and--dockerfileis relative to the repo root. To deploy only on releases, set--trigger-mode release. Branch pushes then never deploy, and a tag push or a published GitHub release does.The life of one push
sequenceDiagram autonumber participant GH as GitHub participant WH as Webhook endpoint participant F as Filters participant Q as Deploy queue participant B as Builder participant R as Readiness gate GH->>WH: POST /api/v1/webhooks/github/{app} (X-Hub-Signature-256) WH->>WH: Verify HMAC, check delivery id WH->>F: Repo, branch or tag, path filter F->>Q: Record queued attempt Q-->>GH: 202 deploy queued Q->>B: Clone commit, build image B->>R: Start new container R->>R: Probe passes, stable window holds R->>R: Cut traffic, drain old release R-->>GH: Deployment status and PR commentStep by step:
/api/v1/webhooks/github/{name}. The path saysgithubfor historical reasons, and the provider is detected from headers. The route takes no session or token, since GitHub cannot send one. The per-app secret is the credential.X-Hub-Signature-256is checked against that secret, which is what GitHub's own guidance asks every receiver to do. Requests are also rate limited per client IP and app (APP_WEBHOOK_RATE_LIMIT_RPM, default 60 per minute), so someone hammering the URL without the secret gets a cheap429.202. Clone and build happen afterwards, in the background.The last step matters. GitHub wants a
2xxwithin 10 seconds or it marks the delivery failed, and a build takes minutes. So Levelrail never builds inside the request, and the delivery log gets the real outcome when the deploy ends.Every outcome, in one table
Nothing a push can do is silent. Each row below is a real response, and
<sha12>is the first 12 characters of the commit.401 invalid signature400 malformed payload429withRetry-Afterpingfrom GitHub200 ping event acknowledgedpushmode)200,ignored: push to "refs/heads/dev", deploys trigger on "refs/heads/main" only200,ignored: push is for <other>, this app tracks <this>--paths200,ignored: skipped: no changed path matched pathsduplicate delivery of <id>200,ignored: commit <sha12> is already deployed200,ignored: commit <sha12> is already in flight202,queued: deploy <id> will run when the app's current deploy finishes202,deploy queued: <id>422 commit not foundThe delivery log and replay
Every inbound request is recorded, valid signature or not, matched or not. That log is the reason the debugging table further down is short. Read it from the API with a token that has the
readability:curl -s -H "Authorization: Bearer $TOKEN" \ https://levelrail.example.com/api/v1/apps/storefront/webhook-deliveriesor from the CLI:
The same data is in the dashboard, on the app's Source tab under "Recent webhook deliveries", with a replay action per row.
What replay does and does not do
Replay runs the same processing against the stored payload and headers, so it can start a real build and deploy once you have fixed the underlying problem. It does not re-verify the original signature, since you are already authenticated with the
deployability, and it does not write a second delivery row, because the replay lands in the audit log instead. It also skips the "already deployed" check, so you can redeploy a commit that is live.Rows are kept 30 days by default (
APP_WEBHOOK_DELIVERY_RETENTION_DAYS). Deliveries that fail signature verification are capped separately: the newest 20 per app, with at most 2048 body bytes each.GitHub also lets you redeliver from the repo's webhook settings. The delivery id stays the same on a redelivery, so Levelrail calls it a duplicate and returns the original result. When you want the push to run again, use Levelrail's replay.
What shows up in deploy history
A push-triggered deploy is an ordinary deploy attempt, so deploy history records its trigger, branch, author and commit message. The deployments page shows one row per deploy with those fields plus status, duration, environment, commit and age, and failed, held, queued and superseded rows carry the reason underneath. The deploy timeline shows trigger and actor on hover. If a push never became a deploy, start with the delivery log. If it did and the deploy failed, go to deploy failures.
Previews per pull request
Previews are opt in per app. Once enabled, every pull request against the target branch gets its own copy of the app, named
<app-name>-pr-<number>.levelrail-cli apps previews enable storefront levelrail-cli apps previews list storefront levelrail-cli apps previews extend storefront 42 --hours 48The lifecycle:
pr-<number>.<app-name>.<primary-domain>. A domain collision does not fail the preview. It deploys without a domain and says why.APP_PREVIEW_TTL, default 7 days) removes it.PREVIEW_URL,PREVIEW_PR_NUMBERandPREVIEW_BRANCH.Caps keep a busy repo from eating your server. The control plane limits live previews per app (
APP_PREVIEW_ENV_MAX_PER_APP, default 10) and in total (APP_PREVIEW_ENV_MAX_TOTAL, default 50). At the cap, the app's policy either evicts the oldest preview or rejects the new one.Important
A pull request from a fork gets no preview unless you opt in with
allow_fork_previews, and even then it waits for approval. Fork previews never see your environment variables or secrets unlessallow_fork_secretsis also on. Approving runs the fork's code on your server, so the CLI requires--yes:Preview details live in the previews docs.
Statuses and PR comments
Levelrail reports back to GitHub in two ways, both on by default through
report_status.production) and each preview (environmentpreview) gets a Deployments API entry that moves fromin_progresstosuccessorfailure. A newer successful deploy marks earlier onesinactive, and closing a pull request marks its preview deploymentinactive.levelrail-cli apps previews pr-status enable storefrontEach preview deploy then posts a
levelrail/previewcommit status on the head commit:pendingwhile building,successonce live (with the preview URL),failureif it failed. One comment per pull request is edited in place, showing building, ready with the URL, failed with a short reason, or removed.Reporting never blocks a deploy. A failed post logs a warning and the deploy carries on. A GitHub App registered before these permissions existed needs
statuses: writeanddeployments: writeadded and accepted. To switch reporting off for one app:GitHub Actions as an alternative
Webhooks build on the control plane. If you would rather build in CI, to reuse your test pipeline, cache layers in Actions or push to a registry first, deploy a prebuilt image from a workflow. The bundled composite action wraps
apps deployandapps wait:The action does not build your image, so build and push it in an earlier step. It waits for the rollout to converge (
timeoutdefaults to5m), and the token needs only thedeployability. Pick one trigger per app. A webhook source and a CI deploy on the same app will fight over which commit is live. Full inputs are in the GitHub Actions docs.Safe rollout and rollback
A queued attempt is not a live deploy. Before traffic moves, the new container has to pass the readiness gate: its probe must pass
APP_DEPLOY_SUCCESS_THRESHOLDtimes in a row (default 2), and it must stay up forAPP_DEPLOY_STABLE_WINDOW(default 10s) without exiting, restarting or getting OOM-killed. If it fails, the previous release never stopped serving.After cutover the old release drains for at least
APP_DEPLOY_DRAIN_TIMEOUT(default 15s). ForAPP_DEPLOY_WATCH_WINDOW(default 5 minutes) a post-deploy watch looks for OOM kills, restart loops and a rising 5xx share, and withAPP_DEPLOY_AUTO_ROLLBACK=true(the default) it rolls back to the newest pinned good release. The newestAPP_DEPLOY_ROLLBACK_PINS(default 3) releases are pinned so image cleanup cannot remove a rollback target.Follow the newest deploy and roll back by hand if you need to:
deploy statusexits7when a release failed its gate, tripped its watch or was rolled back, which makes it usable in scripts.Debugging a push that did nothing
Open the delivery log, find the row for your push, and match the symptom. Because every outcome above is recorded with its reason, most of these take one look.
apps git-source webhook <app>, compare URL and events with GitHub's Recent Deliveries401429APP_WEBHOOK_RATE_LIMIT_RPM, fix the senderignored: push to ...releasetrigger modeapps git-source get <app>for branch and trigger modeignored: push is for ...--repo-urlignored: skipped: no changed path ...--pathsor--paths-ignoreexcluded the changeignored: commit ... already deployedduplicate delivery422 commit not found202but no new releaseFor a private GitLab, Bitbucket or Gitea repository, you paste a deploy token into the git source yourself. Only GitHub mints its clone token automatically.
Checklist
application/json, secret,pushandpull_requestapps git-source setpoints at the right repo, branch and build type202 deploy queuedrow in the delivery logIf you have a push that did nothing and the delivery log did not explain it, post the row here. That is a bug worth fixing.
All reactions