Auto-Destroy After Merge: Stop Paying for Test Environments Nobody Is Using
The Cost Problem Nobody Notices Until the Bill Arrives
Automated PR environments (see Automated PR Environments for E2E Testing) solve the "where do E2E tests run" problem cleanly. But they introduce a new failure mode if the teardown half isn't as automatic as the creation half: environments that quietly keep running long after anyone needs them.
This is a well-documented pattern in teams that build homegrown preview environment tooling. The "create an environment on PR open" script gets written and maintained enthusiastically, because someone is actively blocked without it. The "destroy the environment on merge" script gets written once, then breaks silently three months later — a webhook payload format changes, a script assumes a field that stopped being sent, whatever — and nobody notices, because nobody is blocked by environments not being deleted. They just sit there, accumulating cost, until someone audits the cloud bill and finds forty running namespaces for PRs that merged in Q1.
Auto-destroy-on-merge closes this gap by making teardown exactly as first-class as creation — both are settings on the primary environment, not scripts someone has to maintain.
Configuring It
1bns environments update-settings --id <PRIMARY_ENV_ID> \
2 --create-ephemeral-on-pr \
3 --destroy-ephemeral-on-pr-close--destroy-ephemeral-on-pr-close covers both terminal states of a PR — merged, and closed without merging. Both are legitimate "this environment's job is done" signals, and both should trigger the same teardown.
What Deletion Actually Does (Real Output)
It's worth being precise about what "destroy" removes, since the word can mean different things across tools. Here's a real deletion run against a live environment that had a running component, a deployed ingress, and an encrypted secret in its configuration:
1$ bns environments delete --id 5bDn8MrEgm
2
3Environment 5bDn8MrEgm scheduled to delete with EventID V2GY16VBGR
4EventID DVbvWRj3Jd generated delete pipeline GqWe1wDRW8
5Pipeline details: https://environments.bunnyshell.com/b/pipeline/GqWe1wDRW8
6Environment 5bDn8MrEgm successfully deletedDeletion removes the Kubernetes namespace and everything provisioned in it — pods, services, ingress rules, persistent volumes, the DNS-routed hostname. It runs as a real pipeline (with its own ID and log, same as a deploy), not an instantaneous API flag flip, because tearing down networking and storage cleanly takes real, sequenced steps. For a single-component demo this completed in a few seconds; for a multi-service environment with databases and persistent volumes, expect it to take longer, proportional to how much was provisioned.
The practical implication: after destroy-ephemeral-on-pr-close fires, the environment's URL stops resolving and its compute cost stops accruing — there is no "stopped but still billed" intermediate state the way there would be with a merely-scaled-down deployment.
Auto-Destroy vs. Stop: Choosing the Right Tool
Auto-destroy is not the only lifecycle lever available, and using it where a stop would do is sometimes the wrong call. The distinction matters:
| Destroy | Stop | |
|---|---|---|
| Compute cost | Zero afterward | Zero afterward (scaled to 0) |
| Storage/data | Deleted | Preserved |
| Environment identity (URL) | Gone | Preserved |
| Resume time | Full redeploy | Fast restart |
| Right for... | "This PR is done, permanently" | "Pause overnight, resume tomorrow" |
Auto-destroy-on-merge is correctly a destroy, not a stop — a merged PR's environment has no reason to exist again under that identity. But if your team also pauses environments overnight for cost control on PRs still under active review, that's a stop/start pattern, covered in Stop and Start Environments On-Demand, and should not be confused with or substituted by auto-destroy.
The Gap Auto-Destroy-on-Close Doesn't Cover: Stale, Never-Closed PRs
destroy-ephemeral-on-pr-close is triggered by a Git provider event — merge or close. A PR that simply sits open, unreviewed, for six weeks generates neither event, so its environment keeps running the entire time. This is a real and common gap, not a hypothetical one: "draft PR opened for a spike, then abandoned" is one of the most reliable sources of forgotten running environments in any team, automated teardown or not.
The fix is a stale-environment policy layered on top of merge/close-triggered destroy — an inactivity or maximum-age window after which an environment is destroyed regardless of PR state. If your team has PRs that legitimately stay open for a long time (a large feature broken into a tracking PR, for example), pair the stale policy with explicit termination protection on the environments that should be exempt:
bns environments create ... --termination-protectionBuilding a Cost-Control Checklist Around This
Auto-destroy-on-merge is necessary but not sufficient for keeping ephemeral environment costs under control. A complete checklist:
-
destroy-ephemeral-on-pr-closeenabled on every primary environment that spawns ephemerals - A stale-environment maximum age configured as a backstop for PRs that never formally close
- Termination protection applied to the small number of long-lived environments that should be exempt
- A recurring audit (monthly is usually enough) of
bns environments listfiltered to ephemeral type, sorted by age, to catch anything that slipped through
bns environments list --output json | \
python3 -c "import json,sys; d=json.load(sys.stdin); [print(e) for e in d if e.get('type')=='ephemeral']"This kind of audit is the cheap insurance policy against the exact failure mode described at the top of this guide — automation quietly breaking without anyone being blocked by it.
Why This Matters More as PR Volume Grows
The cost of one forgotten environment is noise. The cost of a systematic gap in teardown automation compounds with team size and PR velocity — a team merging thirty PRs a week that fails to destroy 10% of them accumulates three running-but-abandoned environments every week, indefinitely, until someone notices. At that point the fix is not "write a better cleanup script," it's "stop relying on a script at all" — which is the entire argument for making destroy-on-merge a declarative setting on the environment rather than a CI step that can silently rot.
The Cost Math, Worked Through
It's worth putting actual numbers to "forgotten environments accumulate cost," since the abstract version is easy to nod along to and then not act on. Assume a mid-sized team merges 40 pull requests a week, each ephemeral environment costing roughly $3/day to run (a reasonable figure for a modest multi-service app on a small node pool). If teardown automation has even a 10% failure rate — four environments a week that don't get destroyed on merge, for whatever reason — those four environments don't just cost $12/day once. They compound: four more the following week, four more the week after, each one still running because nothing is telling it to stop. After eight weeks of a silent 10% teardown failure rate, that's 32 forgotten environments running simultaneously, at roughly $96/day, or close to $3,000/month, for infrastructure that provides zero value to anyone. This is not a hypothetical shape of the problem — it is the specific, compounding mechanism by which "our cloud bill grew and nobody knows why" actually happens in teams with imperfect teardown automation.
The fix is not spending more time investigating after the bill arrives — it's the audit script shown above, run on a schedule, catching the leak within days instead of months.
Handling Merge Queues and Squash Merges
Teams using a merge queue (where PRs are batched and tested together before landing) or squash-merge workflows sometimes worry these change how or whether the merge event fires correctly. In practice, both still produce a standard merge event from the Git provider's perspective — a merge queue's final merge into the target branch and a squash merge both close the originating PR in the same way a normal merge does, and destroy-ephemeral-on-pr-close reacts to that PR-closed state, not to the specific merge strategy used to get there. The one workflow that does need explicit attention is a PR that gets closed and immediately reopened (common when someone wants to reset a stalled review) — this fires a close event (triggering destroy) followed by a reopen event (which should trigger environment recreation, if your primary environment's createEphemeralOnPr setting is active for reopened PRs, not just newly-opened ones). Worth testing this specific sequence once during setup rather than assuming it works.
Recovering an Accidentally Destroyed Environment
Because destroy is a real deletion (not a soft-delete with a recovery window), the practical recovery path when an environment is destroyed by mistake — a premature manual /bns:delete, or a misconfigured stale-environment policy catching something it shouldn't have — is recreating it from the same source that created it originally, not "undoing" the deletion. If the environment was ephemeral and spawned from a PR, simply pushing a new commit (or closing and reopening the PR, per the note above) triggers Bunnyshell to spawn a fresh ephemeral environment from the primary blueprint and the PR's current branch state. If it was a manually-created clone with configuration changes that only existed in that one instance and were never captured in a tracked file, those specific changes are genuinely not recoverable — which is itself a strong argument for keeping meaningful configuration in a git-tracked bunnyshell.yaml rather than as untracked, manual edits to a single running environment, precisely so that "recreate from source" is always a viable recovery path.
Auto-Destroy as Part of a Broader FinOps Practice
For teams with a dedicated FinOps or platform cost-management function, auto-destroy-on-merge is worth explicitly reporting as a control, not just quietly relying on. A monthly report showing "N ephemeral environments created, N destroyed, average lifetime X hours" gives a concrete, auditable answer to "are we actually controlling ephemeral environment cost" that goes beyond a general sense that "the automation is on." This data is already available from pipeline history (creation and deletion events per environment) — the only additional work is aggregating it into a periodic report rather than leaving it as raw event data nobody looks at unless something goes wrong.
bns environments list --output json | \
jq -r '[.[] | select(.type=="ephemeral")] | length'Tracking this number over time — not just checking it once — is what turns "we have auto-destroy enabled" into "we know auto-destroy is actually working," the same distinction that matters for any control a team wants to be able to stand behind in a cost review, not just assume is functioning.
Auto-Destroy Doesn't Mean "No Trace Left"
Worth being clear that destroying an environment removes its running infrastructure, not the historical record of it having existed. Pipeline history — every deploy, every action, every failure — remains queryable after the environment itself is gone, via bns pipeline list filtered by the (now-deleted) environment's ID, since pipeline records are retained independently of the environment's lifecycle. This matters for retrospectives: "did this PR's environment ever fail to deploy cleanly before it was merged" is answerable weeks after that environment's teardown, without needing to have kept the environment itself running just in case someone wanted to check.
Auto-Destroy in Regulated or Cost-Sensitive Contexts
For organizations in regulated industries where infrastructure spend and data lifecycle both face internal or external scrutiny, auto-destroy-on-merge has a secondary benefit beyond cost: it minimizes the window during which any test data (even synthetic or anonymized data resembling production) exists at all. An environment that lives for the duration of a PR's review — hours to a few days, typically — and is then fully and automatically deleted has a meaningfully smaller data-retention footprint than an environment that lingers indefinitely because teardown depends on someone remembering. This is worth surfacing explicitly to a compliance or data-governance stakeholder if they're evaluating the ephemeral-environment pattern for the first time — the automatic, non-optional teardown is itself a data-minimization control, not merely a cost-control one, and can be a useful point in favor of the pattern when the conversation is about risk rather than budget.
A Quick Reference for the Three Related Lifecycle Guides
Because this pillar covers several closely related lifecycle actions, it's worth a quick side-by-side reminder of which guide to reach for depending on the situation: use this guide's auto-destroy pattern for PRs reaching a terminal state; use Stop and Start Environments On-Demand for pausing something you'll come back to; and use Managing Environment Lifecycle via CLI as the general reference covering every lifecycle command, including the ones this guide focuses on specifically.
Related: What Happens on the Ephemeral Side vs. the Primary
One clarification worth making explicit: destroy-ephemeral-on-pr-close only affects ephemeral environments spawned from a primary — it does not touch the primary environment itself, which is your persistent staging-like reference environment. Auto-destroy is scoped correctly to the thing that should be temporary; the blueprint it was spawned from is not affected by any individual PR's lifecycle. See One Staging Blueprint, Infinite Ephemeral Copies for how that relationship is structured.
Stop auditing cloud bills for forgotten test environments.
14-day full-feature trial. No credit card required. Pay-as-you-go from $0.007/min per environment.
Frequently Asked Questions
What triggers auto-destroy on an ephemeral environment?
Two Git provider events: the pull request merging, or the pull request being closed without merging. Both are configured via the destroyEphemeralOnPrClose setting on the primary environment that spawns the ephemeral copies.
Does auto-destroy delete data I might need later, like test results or logs?
It deletes the running environment's compute and storage resources. If you need to retain test artifacts (screenshots, videos, coverage reports) from an E2E run, those should be uploaded as CI artifacts before the environment is destroyed, not read back from the environment after the fact.
What if a PR sits open for weeks without being merged or closed?
Auto-destroy on merge/close alone will not clean that up, since neither event has fired. A stale-environment policy (a maximum age or inactivity window after which an environment is destroyed regardless of PR state) is the standard backstop for this case.
Is there a risk of auto-destroy deleting an environment mid-test-run?
Only if the merge itself happens while E2E tests are still running against the pre-merge environment, which is a workflow ordering issue (tests should gate the merge, not run after it) rather than an auto-destroy problem. Configuring merge as a required-check-passes gate avoids this entirely.
Can auto-destroy be turned off for specific long-lived PRs?
Yes — termination protection can be set on an individual environment to exempt it from auto-destroy, useful for a long-running feature branch environment that several people are actively using and that should not disappear just because of a stale-environment timer.