Webhook-Triggered Environment Updates: Keep Test Environments in Sync With Every Push
GuideSeptember 24, 202611 min read

Webhook-Triggered Environment Updates: Keep Test Environments in Sync With Every Push

Why "Redeploy on Every Push" Matters More Than It Sounds

An E2E test suite is only trustworthy if it is testing the code that is actually on the branch right now. That sounds obvious, but it is the single most common way ephemeral environment setups quietly go stale: someone opens a PR, the environment deploys once, they push three more commits fixing review feedback, and the E2E suite keeps running against the first commit's environment because nothing told the environment to update.

The fix is mechanical, not procedural — it should not depend on a human remembering to redeploy. This is what webhook-triggered updates do: every push to the PR's branch fires a Git provider webhook, Bunnyshell receives it, matches it to the existing ephemeral environment, and redeploys automatically. By the time your CI job starts the E2E suite, the environment already reflects the latest commit.

How the Trigger Chain Actually Works

Text
1git push origin feature-branch
2        │
3        ▼
4GitHub/GitLab/Bitbucket sends a push webhook to Bunnyshell
5        │
6        ▼
7Bunnyshell matches the webhook to the ephemeral environment
8tracking that branch/PR
9        │
10        ▼
11A new deploy pipeline is queued automatically
12        │
13        ▼
14Only components whose source changed are rebuilt
15        │
16        ▼
17Environment URL stays the same; content behind it is updated

The critical detail in that last step: the environment's URL does not change on redeploy. If your CI job or a reviewer bookmarked https://web-jfq6ir.bunnyenv.com/, that link keeps working after every push — only what's served behind it changes. This is the same property that makes ChatOps-triggered stop/start safe (see ChatOps for QA Teams): the environment's identity is stable even as its content and running state change.

Enabling It

Webhook-triggered updates are controlled by the autoDeployEphemeral setting on the primary environment that ephemeral copies are spawned from:

Bash
1bns environments update-settings --id <PRIMARY_ENV_ID> \
2  --create-ephemeral-on-pr \
3  --auto-deploy-ephemerals \
4  --destroy-ephemeral-on-pr-close

With --auto-deploy-ephemerals set, no CI configuration is required on your side at all — the redeploy is triggered by Bunnyshell's own webhook listener reacting to the Git provider event, not by anything your pipeline yaml has to do. This is a meaningful difference from a DIY approach where someone has to add a "redeploy preview" step to every workflow file in every repo.

Partial Rebuilds: Why This Is Fast Enough to Not Get Disabled

A legitimate objection to "redeploy on every push" is: if every commit triggers a full environment rebuild, and your team pushes ten times during a review cycle, you are looking at ten full deploys — slow, and expensive if it burns build minutes on unchanged components.

Bunnyshell's deploy pipeline diffs component sources and only rebuilds what changed. A commit that only touches frontend code does not trigger a backend container rebuild or a database re-provision. Here's the deploy job breakdown from a live pipeline — a full initial deploy of a simple app ran through 8 distinct jobs in a single stage:

Bash
1$ bns pipeline show --id OG7nYbkwM3
2
3Stages | Name  | Duration | Jobs | JobsDone | Status
4       | Stage | 1m36s    | 8    | 8        | success

A follow-up push that only changes one component's source triggers a pipeline with a subset of those jobs — the ones for the changed component plus any dependents — not all eight again. In practice this means webhook-triggered redeploys on a typical single-service change land in tens of seconds, not minutes, which is fast enough that teams don't reach for "disable auto-redeploy, it's too slow" as a workaround.

What Happens to a Test Run That's Already in Progress

If your CI kicks off an E2E run and then a new push arrives mid-run, you now have two things happening against the same environment: the old test run finishing, and a new deploy overwriting what it's testing. This is a race condition worth handling explicitly, not something to leave implicit.

The standard fix is CI-level concurrency control, cancelling in-flight jobs on new pushes to the same ref:

YAML
1# .github/workflows/e2e.yml
2concurrency:
3  group: e2e-${{ github.event.pull_request.number }}
4  cancel-in-progress: true

This is not a Bunnyshell-specific setting — it is standard GitHub Actions (and equivalent GitLab/CircleCI) behavior — but it is the piece teams forget to add when they first wire up webhook-triggered environments, and then wonder why E2E results occasionally look inconsistent with the final pushed code.

Debugging a Push That Didn't Trigger a Redeploy

When a redeploy doesn't fire after a push, the fastest diagnostic is checking whether Bunnyshell received the event at all, versus received it but failed to act on it. Pipeline history answers that immediately:

Bash
bns pipeline list --environment <EPHEMERAL_ENV_ID>

If no new pipeline appears around the time of the push, the webhook itself likely never arrived — check the Git provider's webhook delivery log (GitHub: repo Settings → Webhooks → Recent Deliveries) for a failed or missing delivery. If a pipeline did run but the environment still shows old content, the more likely cause is autoDeployEphemeral being disabled on the primary environment, meaning the ephemeral copy exists but isn't set to auto-update — in that case, either enable the setting or trigger the update manually with a ChatOps /bns:deploy comment.

Webhooks vs. ChatOps: Two Triggers, One Mechanism

It's worth being explicit that webhook-triggered updates and ChatOps commands (/bns:deploy) are two different triggers for the identical underlying action — a deploy pipeline against the ephemeral environment. Neither replaces the other:

TriggerWhen it firesUse case
Webhook (push)Automatically, on every commitThe default path — no one should have to think about it
ChatOps commentManually, when someone types itRecovering from drift, forcing an update after a non-code change (env var edit), pre-demo cleanup

Relying solely on webhooks is correct for the common case. Keeping ChatOps available as a manual override is what makes the system resilient to the cases webhooks don't cover — like a secret changed in the Bunnyshell UI that has no corresponding Git commit to trigger a webhook.

Multi-Branch and Force-Push Considerations

Two edge cases worth handling explicitly in your setup:

Force-pushes (common after an interactive rebase) still fire a push webhook and still trigger a redeploy — Bunnyshell does not distinguish a force-push from a normal push for this purpose, so a rebased branch redeploys cleanly rather than silently keeping stale history.

Branch renames or PR re-targeting can occasionally desynchronize which branch an ephemeral environment is tracking. If you rename a branch mid-review, verify the environment's tracked branch with bns environments show --id <ENV_ID> rather than assuming the rename was picked up automatically.

Webhook Payload Security: Verifying the Sender

Any system that reacts automatically to an inbound webhook needs to answer one security question before anything else: how do you know the request actually came from your Git provider, and not from someone who guessed or discovered the webhook URL? Every major Git provider signs its webhook payloads with a shared secret configured at registration time — GitHub uses an HMAC-SHA256 signature in the X-Hub-Signature-256 header, GitLab uses a static token in X-Gitlab-Token, and so on. Bunnyshell's webhook receiver validates this signature before treating any payload as legitimate, which means an attacker cannot trigger arbitrary redeploys or environment actions simply by sending a crafted POST request to a guessed endpoint. If you are ever debugging "why didn't my webhook fire" and suspect signature validation, the Git provider's own webhook delivery log (which shows the response code Bunnyshell returned) is the fastest way to confirm whether the request was rejected at that layer versus never arriving at all.

Idempotency: What Happens With Duplicate or Out-of-Order Deliveries

Webhooks are typically delivered at-least-once, not exactly-once — a Git provider may redeliver a webhook if it doesn't receive a timely acknowledgment, and network conditions can occasionally deliver events out of order. A redeploy pipeline triggered twice for the same commit should not meaningfully differ from being triggered once (the second deploy simply redeploys the same already-current state, a no-op in effect), but out-of-order delivery is worth understanding explicitly: if commit B's webhook arrives and triggers a deploy before commit A's webhook (sent slightly earlier but delayed in transit) is processed, the environment could briefly reflect commit A's state after B's deploy technically started. In practice this window is small and self-correcting — the next push (or a manual /bns:deploy) resolves it — but it's worth knowing this is an inherent property of asynchronous webhook delivery, not a bug specific to any one integration.

Monitoring Webhook Health Over Time

For a team relying on webhook-triggered updates as the default (not just occasional) path to keeping environments current, it's worth periodically confirming the mechanism is still healthy rather than assuming it silently keeps working forever. A simple health check: compare the timestamp of an environment's most recent pipeline against the timestamp of the most recent commit on its tracked branch.

Bash
LATEST_COMMIT_TIME=$(git log -1 --format=%cI origin/feature-branch)
LATEST_PIPELINE_TIME=$(bns pipeline list --environment <ENV_ID> -o json | jq -r '.[0].createdAt')

A persistent, growing gap between these two timestamps across multiple PRs is the signal that something in the webhook chain has quietly broken — a deleted webhook registration, a changed secret, a Git provider API change — and is worth investigating before someone notices from a stale E2E result rather than from monitoring.

Comparing Webhook-Triggered Updates to Polling-Based Alternatives

Some homegrown preview-environment systems, lacking real webhook integration, fall back to polling — a scheduled job that periodically checks each open PR's branch for new commits and triggers a redeploy if it finds one. It's worth being explicit about why this is a strictly worse design, not just an older one. Polling introduces an unavoidable latency floor equal to the polling interval — a 5-minute poll means up to 5 minutes between a push and the environment reflecting it, during which an E2E run could start against stale code without anyone realizing it. Polling also scales linearly with the number of open PRs being watched, meaning a busy repository with many open PRs multiplies API calls to the Git provider proportionally, running into rate limits that webhook-based delivery, which is push-driven and only fires on actual events, never encounters. Webhook-triggered updates have neither problem: latency is bounded by delivery and processing time (typically seconds), and load scales with actual push events, not with how many PRs happen to be open at once.

Testing the Full Round Trip Before Trusting It

Before relying on webhook-triggered updates as the default path for a team's E2E workflow, it's worth deliberately testing the full loop once, rather than assuming it works because the settings look correct. A practical test sequence: open a test PR against a repository with autoDeployEphemeral enabled, note the ephemeral environment's initial deployed content (a version string, a build timestamp, anything that visibly changes), push a trivial commit that changes that marker, and confirm the environment reflects the new marker within your expected latency window — typically under a minute for a small app — without any manual intervention. This concrete verification, done once at setup time, is worth far more than reading the configuration and assuming it's correct, since the actual failure modes (a misconfigured webhook secret, a Git provider App permission that's slightly too narrow) tend to fail silently rather than with an obvious error.

Draft PRs and Auto-Deploy: A Setting Worth Reviewing Explicitly

Many teams open draft PRs early — a work-in-progress branch, pushed frequently, not yet ready for review — and it's worth deciding deliberately whether every push to a draft PR should trigger a full redeploy the same as a ready-for-review PR, or whether that's wasted compute for work that isn't at the "needs a running environment" stage yet. Some Git providers expose a draft-status flag in their webhook payload that could, in principle, be used to gate whether autoDeployEphemeral fires — check your specific provider integration's support for this distinction rather than assuming every push to every PR, draft or not, should carry equal deploy priority. For teams with a high volume of long-lived draft PRs (a common pattern for large features developed incrementally), this is a real, addressable source of unnecessary redeploy volume.

Checklist

  • autoDeployEphemeral is enabled on the primary environment
  • CI concurrency/cancel-in-progress is configured so stale test runs don't report against overwritten environments
  • Webhook delivery is visible and healthy in the Git provider's settings
  • A ChatOps fallback (/bns:deploy) is documented for the team as the manual override
✦✦

Stop babysitting redeploys. Let pushes trigger them.

14-day full-feature trial. No credit card required. Pay-as-you-go from $0.007/min per environment.

Frequently Asked Questions

What triggers a webhook-triggered environment update?

A push event to the branch backing an ephemeral environment's pull request. The Git provider (GitHub, GitLab, Bitbucket, Azure DevOps) sends a webhook to Bunnyshell, which matches the event to the existing ephemeral environment and triggers a redeploy using the new commit.

Does every push redeploy the entire environment from scratch?

No. Bunnyshell rebuilds only the components whose source changed and reuses unchanged components, so a frontend-only commit does not force a database or backend redeploy. This keeps update latency close to a normal CI build time, not a full environment recreation.

What happens to in-flight E2E test runs when a new push triggers a redeploy mid-run?

The in-progress deployment and any test run against the pre-update environment state should be treated as superseded — recommended practice is to cancel in-flight CI jobs on new pushes to the same PR (a standard CI concurrency setting) so you are never reporting results against code that no longer exists on the branch.

Can I disable auto-redeploy on push for specific environments?

Yes, autoDeployEphemeral is a per-primary-environment setting. Turning it off means ephemeral environments are still created on PR open but require an explicit deploy (via CLI, UI, or a /bns:deploy ChatOps comment) rather than redeploying automatically on every push.

Why would a push not trigger a redeploy?

The most common causes are: the webhook was never registered or was deleted on the Git provider side, the branch is not the one associated with the tracked PR, or autoDeployEphemeral is disabled on the primary environment. Checking the pipeline list for the environment will show whether an event was received at all.