A deployment model where every push gets its own isolated slot on Kubernetes, the main deployment is only changed on purpose, and promotion is an explicit act.
Most pipelines I've worked with end in the same place: merge to main, CI builds, CI deploys to production. It works until the day a green build is a bad release, and the only way back is another build.
When we designed the deployment model for SeaGit, a Kubernetes platform that runs in your own AWS account, we made one rule non-negotiable: CI/CD can create and update preview deployments, but it can never touch the main one. This post explains the model, because the idea works whatever tooling you use.
Instances and deployments
Two concepts carry the whole design.
- An instance is an application's definition: image, resources, environment variables, DNS. It belongs to an environment (staging, production, and so on).
- A deployment is one running copy of that instance. Each is its own Helm release with its own subdomain, for example
pr-142.example.com.
An instance has several deployments. Exactly one is marked main (is_main: true). Main is the stable, production-facing slot. Every other deployment is an ephemeral slot: a preview, a test, a dark release.
instance: checkout-api (environment: production)
├── main → api.example.com ← only changed on purpose
├── slot-a1f3 → a1f3.api.example.com ← commit a1f3…
└── slot-9c2e → 9c2e.api.example.com ← commit 9c2e…
Because preview slots run in the same environment as main (same cluster, same add-ons, same network), a preview is a true dark release. It's production-like infrastructure with zero production traffic.
How a push picks a slot
When a GitHub push arrives, the target is resolved with three rules:
- Same commit, same slot. If a deployment already exists for this commit SHA, it's re-triggered instead of duplicated. Re-running a pipeline doesn't spawn a second copy.
-
Otherwise, a new ephemeral slot, as long as the instance is under the environment's
max_deployments_per_instancelimit. - Never main. No rule in the resolver can select the main deployment.
The limit matters more than it looks. Preview environments are cheap individually and expensive collectively. A hard per-environment cap forces a decision ("reuse a slot or delete one") instead of a cluster that slowly fills with forgotten previews.
Getting a change into production
If CI can't deploy to main, something else has to. There are two explicit paths:
- Promote. Copy the validated preview's configuration and run a fresh deploy into the main slot. Main keeps its name and URL, so nothing downstream changes.
-
Mark as main. Move the
is_mainflag to the preview itself. That slot becomes production, and future CI runs leave it alone and target the remaining slots.
Promote is the conservative choice: production stays where it is and gets a known-good configuration. Mark as main is the fast one: the exact pods you just tested become production, with no rebuild in between.
Both are deliberate actions, from the UI or the API. Neither happens because a build went green.
What this buys you
- A bad merge can't reach users on its own. The worst outcome of a broken pipeline is a broken preview.
- Testing happens on the real thing. Same environment, same add-ons, same network policy as production, not a lookalike staging cluster that drifts.
- Rollback is boring. The previous main still exists as a deployment until you remove it.
- Re-runs are idempotent. The commit-SHA rule makes "retry the pipeline" safe.
The trade-off is honest: someone has to press promote. For a team that wants every merge live within minutes, that's friction. For a team that has been paged at 2 a.m. by an auto-deploy, it's the point.
Doing this yourself
You don't need a platform to adopt the rule. With plain Kubernetes:
- Deploy each branch or commit as its own Helm release, named from the commit or PR, with its own host in the ingress.
- Keep production in a release your CI credentials cannot write to: a separate service account or a protected environment.
- Make promotion a separate, manually triggered job that copies a validated release's values into the production release.
- Cap the number of preview releases and delete old ones on a schedule.
Step 2 is the important one. "CI never deploys to production" only holds if it's enforced by permissions, not by convention.
Where SeaGit fits
SeaGit implements this model as the default: ephemeral preview deployments on managed Kubernetes in your own AWS account, built from GitHub pushes, with Promote and Mark as main in the UI and API. Your cloud provider bills you directly for compute, and there's a free plan with one cluster for trying it out (pricing). Azure and GCP support are on the roadmap.
Whether you use it or build the four steps above yourself, the rule is the same: let CI create as many previews as you like, and make production something a person chooses.
Top comments (0)