Ephemeral Environments for Pull Requests: Architecture, Costs, and Cleanup Patterns
Learn how to create a temporary environment for every pull request, estimate its running cost with a repeatable model, and prevent unused environments from consuming infrastructure or creating security risk.
Overview
An ephemeral environment is a short-lived application environment created for a specific pull request, branch, or review. It usually includes an application build, supporting services, a temporary URL, and enough configuration for developers, testers, or product stakeholders to validate a change before it reaches a shared staging or preproduction environment.
The central design principle is isolation without unnecessary duplication. A pull request environment should be independent enough that one change cannot overwrite another reviewer's data or deployment, but small enough to create quickly and remove automatically. Kubernetes namespaces, isolated cloud resources, container platforms, and infrastructure as code can all support this model. The right choice depends on the application's dependencies, data sensitivity, startup time, and expected number of concurrent pull requests.
A typical workflow looks like this:
- A pull request event starts a CI/CD workflow.
- The workflow builds and tests the application image.
- Infrastructure or deployment manifests create a uniquely named environment.
- Ingress, DNS, or a preview service assigns a review URL.
- Secrets and test data are supplied through controlled mechanisms.
- Automated checks run against the deployed application.
- Closing or merging the pull request triggers teardown.
Keep the preview environment's purpose clear. It is generally a review and validation space, not a substitute for production-like preproduction testing. For a broader promotion model, compare staging, preproduction, and production responsibilities before deciding which tests belong in each environment.
How to estimate
Estimate ephemeral-environment cost from usage rather than from a single infrastructure quote. A simple monthly model is:
Estimated monthly cost = active environments × cost per environment per hour × average active hours per month + shared platform cost + data, network, and tooling cost
For a more useful estimate, split the calculation into components:
- Compute: application containers, workers, jobs, virtual machines, or serverless execution used by each environment.
- Storage: temporary databases, object storage, persistent volumes, logs, caches, and backups.
- Network: ingress, egress, load balancing, NAT, private connectivity, and traffic between managed services.
- Shared platform: the portion of a cluster, control plane, registry, observability stack, or gateway that supports preview environments.
- Automation: CI/CD minutes, image builds, infrastructure operations, and any hosted preview service charges.
Use two scenarios instead of one. The baseline scenario reflects normal concurrency and average lifetime. The capacity scenario uses the maximum number of simultaneously active pull requests and a longer-than-usual lifetime. This prevents a low average from hiding a costly queue of abandoned or long-running reviews.
You can also estimate the operational outcome with a small set of measures:
- Median time from pull request creation to a usable preview URL.
- Percentage of pull requests with a successfully created environment.
- Percentage of environments removed after closure or expiry.
- Number and age of orphaned resources found during cleanup.
- Cost per active environment and cost per reviewed pull request.
These measures make the decision more practical than comparing infrastructure totals alone. A design that is inexpensive but unreliable may increase manual testing and delay reviews; a larger design may be justified if it removes a repeated bottleneck.
Inputs and assumptions
Before choosing an implementation, record the inputs that drive both architecture and cost. Treat each value as an explicit assumption that can be changed when your team or cloud configuration changes.
Concurrency and lifetime
Start with the number of pull requests that are likely to need environments at the same time. Then estimate the average active lifetime in hours. Include time spent waiting for review, not just CI execution time. If a pull request remains open for several days, an environment that runs continuously may cost more than one that pauses outside working hours.
Decide whether environments should be always on, automatically suspended after inactivity, or recreated on demand. Suspension can reduce compute use, but it may add wake-up time and complexity. Rebuilding from a known image and data seed can be preferable when state is disposable.
Isolation model
Common patterns include a namespace per pull request in a shared Kubernetes cluster, a separate application stack on a container platform, or a dedicated set of cloud resources provisioned through infrastructure as code. A shared cluster can improve utilization, while dedicated resources may provide stronger fault or quota boundaries. Neither model automatically provides complete isolation: review network policies, service accounts, quotas, ingress rules, and access to shared dependencies.
Use predictable identifiers such as pr-482 or a sanitized branch slug. Apply the identifier consistently to namespaces, labels, DNS records, secrets, logs, and cloud resource tags. This makes ownership and cleanup traceable.
Data and dependencies
Decide whether each environment receives a fresh database, a sanitized snapshot, a shared read-only dataset, or synthetic fixtures. Production data should not be copied into a preview environment without an approved handling process. A shared database is cheaper but can create test interference and migration conflicts.
List dependencies that cannot be duplicated easily, such as payment gateways, email delivery, identity providers, queues, and third-party APIs. Prefer test accounts, mocks, sandboxes, or controlled service endpoints. Define what happens when a dependency is unavailable; a preview should fail clearly rather than silently sending real notifications or modifying shared records.
Secrets, access, and URLs
Preview URLs should be unique, discoverable from the pull request, and protected when the application contains private code or data. Options include single sign-on, identity-aware access, temporary credentials, or a controlled network path. Do not place long-lived secrets in pull request metadata, build logs, container images, or client-side configuration.
Use short-lived credentials where possible and scope them to the environment. Make sure the teardown process revokes or removes them even if the deployment itself fails.
Automation and ownership
Provisioning should be idempotent: rerunning the workflow should converge on one environment rather than create duplicates. Store deployment state in a location that the CI system and cleanup process can both access. If you use Terraform or another infrastructure as code tool, define lifecycle rules, tags, and ownership as part of the module rather than relying on a manual convention. This Terraform workflow guide provides useful context for structuring reusable infrastructure changes.
Worked examples
Example 1: Shared Kubernetes cluster
Assume a team has 12 active pull requests at peak, an average preview lifetime of 30 hours, and a workload composed of two small application deployments and one short-lived worker. The team assigns a notional hourly compute-and-platform rate of R per environment, a storage allowance of S per environment-month, and a shared observability and ingress allocation of G per month.
The variable portion can be modeled as:
12 environments × 30 hours × R + 12 × S + G
If previews are suspended after 10 hours without activity, replace the 30-hour lifetime with the estimated active hours. Recalculate storage separately if suspended environments retain persistent volumes. This example demonstrates why the number of open pull requests is not enough: lifetime, storage retention, and shared services affect the result differently.
Example 2: Dedicated resources per pull request
Suppose each environment includes an application service, a database, a queue, and a private network path. The same concurrency may now require substantially more resources than the shared-cluster design. Model each component independently:
Monthly estimate = concurrency × lifetime × (application rate + database rate + queue rate + network rate) + retained storage + shared CI/CD cost
This pattern can be appropriate when integration behavior is important, but it increases the number of failure points and cleanup obligations. Before adopting it, test whether a shared service with isolated schemas or disposable fixtures can provide the required confidence at lower operational cost.
Example 3: A cleanup failure budget
Assume the team expects 40 preview environments to be created during a review period and wants fewer than two to remain after their pull requests close. Track creation, closure, and deletion events using the same environment identifier. If cleanup misses the target, inspect whether the problem comes from deleted branches, failed CI jobs, expired credentials, lost state, or resources created outside the main deployment workflow.
Use scheduled reconciliation as a second line of defense. It can find resources tagged with a pull request identifier, compare them with currently open pull requests, and remove or quarantine items that exceed a defined age. Set an approval or grace period where accidental deletion would be costly.
When to recalculate
Recalculate the estimate whenever pricing inputs change, but also whenever the workflow changes. Review it after adding a database, worker, browser-testing service, observability destination, or private network connection. A new dependency can change both per-environment cost and teardown behavior.
Revisit the model when pull request volume, average review time, or peak concurrency moves materially. Teams often begin with a few previews and later add integration tests, larger fixtures, or longer-lived environments. Update the baseline and capacity scenarios rather than changing only the average.
Review cleanup after incidents involving orphaned namespaces, stale DNS records, unused volumes, exposed preview URLs, or credentials that outlived their environment. Also test cleanup when a pull request is closed before provisioning finishes, when a deployment fails halfway through, and when the CI provider is unavailable.
For a practical operating routine, keep a small monthly review checklist:
- Compare active-environment count and lifetime with the assumptions in the calculator.
- Inspect resources grouped by pull request, owner, and age.
- Review failed provisioning and teardown jobs.
- Check storage, logs, DNS, secrets, and external service usage for leftovers.
- Update resource limits, expiry rules, and access policies where evidence supports a change.
- Validate a complete pull request workflow from creation through merge, closure, and cleanup.
Finally, connect preview checks to existing release controls rather than treating them as an isolated platform project. A preview can run focused tests before a shared environment, while preproduction remains the place for broader promotion validation and release readiness. Use the CI/CD quality-gates guide and preproduction release checklist to define which evidence belongs at each stage.