One Staging Blueprint, Infinite Ephemeral Copies: The Primary Environment Pattern
The Core Idea: One Source of Truth, Many Disposable Copies
Every pattern discussed elsewhere in this pillar — automated PR environments, self-service clones, ChatOps control — depends on one architectural decision working correctly: there has to be one clearly-defined environment that everything else is derived from. Without that, "spin up an environment for this PR" has no well-defined answer to "an environment that looks like what, exactly?"
Bunnyshell formalizes this as the primary environment — a persistent environment, distinct from the ephemeral ones spawned per-PR, that acts as the template every child environment inherits from. Understanding this relationship clearly is what makes the rest of the automation in this pillar predictable instead of mysterious.
Primary vs. Ephemeral: The Actual Distinction
1$ bns environments show --id z0EYP1QX4W
2
3EnvironmentID | z0EYP1QX4W
4Type | primary
5OperationStatus | running1$ bns environments show --id z7XPdNNXQ6
2
3EnvironmentID | z7XPdNNXQ6
4Type | ephemeral
5OperationStatus | draftThe Type field is the load-bearing distinction. A primary environment is meant to persist — it's your staging-equivalent, the thing a human intentionally created and maintains. An ephemeral environment is meant to be temporary by design — spawned automatically (typically per-PR), and destroyed automatically once its purpose (that PR's review cycle) ends.
Critically, ephemeral environments are not manually configured from scratch. When a PR opens against a repository connected to a primary environment with createEphemeralOnPr enabled, Bunnyshell takes the primary's component definitions, substitutes the PR's branch and any per-environment variables (like a unique hostname), and deploys that as a new, independent ephemeral environment. The primary is the blueprint; the ephemeral is a stamped-out copy with just enough per-instance customization to be uniquely addressable.
Why This Matters More Than It Sounds
Without a clearly designated primary, teams tend toward one of two bad patterns:
Pattern A: Every environment is configured independently. Someone sets up a PR-preview mechanism by hand-writing a Kubernetes manifest or Helm values file per environment. This works until the app's configuration needs to change — now that change has to be propagated to every place the config is duplicated, and inevitably some copies drift out of sync. This is precisely the "DIY scripts break every upgrade" failure mode that homegrown environment tooling accumulates over time.
Pattern B: The "primary" is whichever environment someone happened to configure most recently. Without an explicit designation, teams sometimes clone from whatever environment looks closest to correct at the moment, rather than a deliberately maintained source. This compounds drift across successive clones — clone A has an undocumented manual tweak, clone B is cloned from clone A and inherits the tweak plus a new one, and so on.
The primary-environment pattern solves both by making the blueprint an explicit, singular, intentionally-maintained thing — changes to how PR environments should look happen in exactly one place (the primary's bunnyshell.yaml), and every derived environment (ephemeral or manually cloned) traces back to that one source.
Structuring the Relationship
A minimal setup: one bunnyshell.yaml, deployed once as a primary environment, with ephemeral-spawning settings enabled:
1kind: Environment
2name: my-app-staging
3type: primary
4
5components:
6 - kind: Application
7 name: api
8 gitRepo: 'https://github.com/my-org/my-app.git'
9 gitBranch: main
10 gitApplicationPath: /
11 dockerCompose:
12 build:
13 context: .
14 hosts:
15 - hostname: 'api-{{ env.base_domain }}'
16 path: /
17 servicePort: 8080
18
19 - kind: Database
20 name: db1bns environments create --from-path bunnyshell.yaml \
2 --name "my-app-staging" --project <PROJECT_ID> --k8s <CLUSTER_ID> --deploy
3
4bns environments update-settings --id <PRIMARY_ENV_ID> \
5 --create-ephemeral-on-pr \
6 --auto-deploy-ephemerals \
7 --destroy-ephemeral-on-pr-closeFrom this point on, every PR against the connected repository spawns its own ephemeral environment using this exact definition, with the PR's branch substituted for main and a unique hostname substituted per environment. Nobody needs to touch the primary again for the normal PR flow to keep working — they only touch it when the shape of the application itself changes (a new service added, a new database dependency).
Isolation: What Ephemeral Copies Do and Don't Share With the Primary
By default, every ephemeral environment gets fully isolated instances of every component defined in the primary — its own database, its own cache, its own everything. This is a deliberate default, not a limitation: the entire value of per-PR environments is that one PR's testing cannot corrupt another's, and a shared database would reintroduce exactly the contention problem this pattern exists to eliminate.
There are legitimate exceptions — an expensive third-party sandbox account, a licensed component that's impractical to duplicate per-environment — where sharing a single instance across ephemerals is the pragmatic choice. That sharing is explicit and opt-in per component, never the default, so isolation stays the assumption unless a specific component genuinely warrants an exception.
Updating the Blueprint Without Breaking Existing Environments
A natural question: if I change the primary's bunnyshell.yaml today, what happens to the twelve ephemeral environments already running from yesterday's version?
They are not retroactively altered. An already-deployed ephemeral environment keeps running with whatever configuration it was deployed with. The updated primary configuration takes effect for new ephemeral environments (new PRs opened after the change) and for existing ephemerals on their next deploy — which, if webhook-triggered updates are enabled (see Webhook-Triggered Environment Updates), happens naturally on the PR's next push.
This means a breaking change to the primary's configuration is safe to make mid-sprint — it doesn't retroactively break every open PR's environment simultaneously, it rolls forward gradually as PRs get new commits or new PRs are opened.
When to Use More Than One Primary
Some teams maintain multiple primary environments deliberately — for example, one primary configured to spawn ephemeral PR environments for the main application repository, and a separate, independent primary serving as a stable demo environment for sales (see Sales Demo Environments) that is never touched by the PR automation at all. These are two different primaries with two different purposes; nothing requires a single organization to have only one. What matters is that each category of derived environment (PR ephemerals, self-service clones, demo environments) has a clearly designated, intentionally-maintained source it derives from — not that there is exactly one primary organization-wide.
Versioning the Blueprint: Git-Backed Definitions
The examples throughout this pillar show bunnyshell.yaml as a local file passed via --from-path, which is the simplest way to demonstrate the mechanics with a real CLI transcript. For a team's actual primary environment, the recommended pattern is keeping that file in the same Git repository as the application it defines, and using --from-git (or the equivalent template-repository configuration in the UI) rather than a local file that only exists on whoever's laptop last edited it.
1bns environments create --from-git \
2 --from-git-repo 'https://github.com/my-org/my-app.git' \
3 --from-git-branch main \
4 --from-git-path bunnyshell.yaml \
5 --name "my-app-staging" --project <PROJECT_ID> --k8s <CLUSTER_ID> --deployThis matters for the exact reason versioning your application's own code matters: a git blame on bunnyshell.yaml tells you who changed the primary environment's shape and when, a pull request reviewing a change to it gives your team a chance to catch a mistake before it propagates to every ephemeral environment spawned afterward, and a bad change can be reverted with git revert the same way a bad application code change can — rather than needing someone to remember what the previous configuration looked like and manually reconstruct it.
Ownership: Who Actually Maintains the Primary
Because every ephemeral environment and every self-service clone traces back to the primary, changes to it have outsized blast radius compared to changes to any individual derived environment — which is exactly why it should have clear, named ownership, not be "whoever touched it last." In practice this usually means the same team that owns the application's deployment configuration more broadly (a platform team, or the backend team lead, depending on organization size) reviews changes to the primary's bunnyshell.yaml the same way they'd review a change to a shared CI pipeline definition — a low-frequency but high-consequence artifact, not something edited casually.
Migrating From Ad Hoc Environments to the Blueprint Pattern
Teams that already have preview environments working, just not through a single designated primary, can migrate incrementally rather than needing a disruptive cutover:
- Pick the existing environment configuration closest to correct (usually whichever one is most actively used and most recently updated) as the candidate blueprint.
- Export its live definition (
bns environments definition --id <ID>) to capture its actual current state, including any manual drift, rather than starting from a stale template file. - Commit that exported definition to the application's repository as
bunnyshell.yaml, reviewed by the team that will own it going forward. - Designate that environment as the primary and enable
createEphemeralOnPr/destroyEphemeralOnPrCloseon it. - Let existing ad hoc preview mechanisms wind down naturally as PRs cycle through and start picking up the new, automated ephemeral environments instead of whatever manual process predated them.
This migration path avoids the common failure mode of trying to design the "perfect" primary configuration from scratch before adopting the pattern at all — starting from what already demonstrably works, then formalizing it, gets a team to the benefits of the pattern faster than a from-scratch redesign.
What Belongs in the Primary vs. What Belongs Per-Environment
A subtlety worth spelling out for teams designing their first primary environment: not every configuration value should live identically in the primary and propagate unchanged to every ephemeral copy. Configuration splits naturally into two categories. Structural configuration — which components exist, how they depend on each other, what ports they expose, what Docker images or build contexts they use — belongs in the primary and should be identical across every derived environment, since this is the actual shape of the application, and divergence here means ephemeral environments stop reliably testing what will actually ship. Per-instance configuration — a unique hostname, a uniquely-generated database name to avoid collision, a PR-specific feature flag — is deliberately not fixed in the primary, but computed per-environment using Bunnyshell's interpolation syntax ({{ env.unique }}, {{ env.base_domain }}), so each derived environment gets a value that's correct for its own identity without needing a human to set it individually.
Getting this split wrong in either direction causes real problems: hardcoding a per-instance value (a fixed hostname, say) into the primary makes every ephemeral environment collide on that value, which usually manifests as a mysterious "why do all my preview environments seem to interfere with each other" bug. Conversely, treating genuinely structural configuration as something that varies "per environment, just this once" (a developer manually editing one ephemeral environment's component definition directly, rather than fixing the primary) reintroduces the drift problem this whole pattern exists to prevent.
Recognizing When the Pattern Is Breaking Down
A few concrete warning signs that a team's primary-environment setup has drifted from the pattern described in this guide, worth watching for periodically rather than only discovering during an incident: ephemeral environments that behave meaningfully differently from each other despite being spawned from the same primary (a sign of undocumented manual edits creeping into individual ephemerals rather than the primary itself); a primary environment nobody can confidently say is the one PR automation actually derives from (a sign of an undocumented or informally-designated primary rather than an explicit one); and configuration changes that get applied by editing a running environment directly rather than through a reviewed change to the tracked bunnyshell.yaml (a sign the git-backed versioning discussed above has quietly stopped being followed in practice, even if it was set up correctly at the start). None of these are catastrophic on their own, but each is a small erosion of the exact property — one source of truth — that makes the pattern valuable, and each is far cheaper to correct the week it starts than the year after it's become the team's unquestioned normal.
A Final Word on Discipline Over Tooling
None of the mechanics described in this guide — type: primary, createEphemeralOnPr, git-backed versioning — enforce the discipline of treating the primary as a single source of truth on their own. A team can technically configure all of this correctly and still let drift creep in through manual edits nobody documents. The tooling makes the right pattern easy and the wrong pattern (ad hoc, undocumented, per-environment configuration) unnecessary — but it's still a team practice, reinforced through code review on changes to bunnyshell.yaml and a shared understanding of why the primary matters, not a constraint the platform can fully enforce on its own.
Where This Pattern Connects to the Rest of the Pillar
This blueprint relationship is the foundation everything else in this pillar builds on: Self-Service Staging Clones clone from a primary; Automated PR Environments for E2E Testing spawns ephemerals from it automatically; Auto-Destroy After Merge tears those ephemerals down without touching the primary itself. Getting this one relationship right early is what makes every other piece of automation in this pillar behave predictably rather than needing to be debugged individually.
Checklist
- Exactly one environment is designated (by convention, naming, or documentation) as the primary blueprint for PR-triggered ephemerals
- That primary's
bunnyshell.yamlis the one place configuration changes are made — no hand-edited exceptions on individual ephemeral copies -
createEphemeralOnPr,autoDeployEphemerals, anddestroyEphemeralOnPrCloseare all set deliberately, not left at defaults without review - Component isolation between ephemerals is the default; any shared components are an explicit, documented exception
Set up one staging blueprint that spawns every PR environment automatically.
14-day full-feature trial. No credit card required. Pay-as-you-go from $0.007/min per environment.
Frequently Asked Questions
What is a primary environment in Bunnyshell?
A primary environment is a persistent, long-lived environment (as opposed to an ephemeral one) that serves as the reference definition for a project — usually your staging-equivalent environment. Ephemeral environments spawned for pull requests inherit their configuration from a primary environment.
Does every PR ephemeral environment share the same database as the primary?
No, by default each ephemeral environment gets its own isolated instance of every component, including databases, unless explicitly configured to share an external resource. Sharing is possible for expensive or hard-to-duplicate resources, but the default and recommended pattern is full isolation.
What happens if I change the primary environment's configuration?
Existing ephemeral environments already deployed from the previous configuration are not retroactively changed. New ephemeral environments created after the change (new PRs opened) will use the updated primary configuration. Existing ephemerals update on their next deploy if the underlying template change is picked up.
Can I have more than one primary environment per project?
Yes — some teams maintain multiple primary environments for different purposes (a staging-equivalent for automatic PR ephemerals, plus a separate stable demo environment), each capable of spawning its own set of clones or ephemerals independently.
How is this different from an environment template?
A template is a saved, versioned definition typically stored in a Git repository, used explicitly via --from-template. A primary environment is a live, running instance that itself acts as the blueprint for ephemeral children — the two mechanisms can coexist, with a primary environment itself having been created from a template.