int → test → acc → prod
Every releaseearns production.
Ring Promoter is a small control plane that moves application versions through deployment rings — health-gated at every hop, rolled back automatically on failure, and written to history every time. Kubernetes and VM apps, one Go binary.
int
Integration
v2.14.0
test
Test
v2.13.9
acc
Acceptance
v2.13.9
prod
Production
v2.13.2
▸ payments-api · v2.14.0 healthy in int
audit history
A scripted simulation of the real promotion protocol — try failing a health check.
Single Go binary
UI, API and promoter in one process
Kubernetes + VMs
kubectl and GitHub Actions, side by side
Safe across replicas
Postgres advisory locks serialize every op
Onboard in YAML
new apps are configuration, not code
The protocol
Promotion is a protocol, not a script.
Five rules, enforced by the control plane on every operation. They are what make a promotion safe to run at 5pm on a Friday.
01
One ring at a time
Promote always targets the next ring in the pipeline. Skipping is impossible by design — not by policy.
02
The source must prove it
A live health check on the source ring gates every promotion before anything deploys.
03
The target must earn it
After deploying, health checks run with configurable retries before the hop is called good.
04
Failure undoes itself
If the target stays unhealthy, it is rolled back to its previous version automatically.
05
Everything is written down
Every seed, promote and rollback lands in history — success or failure, per app, per ring.
The protocol
Promotion is a protocol, not a script.
Five rules, enforced by the control plane on every operation. They are what make a promotion safe to run at 5pm on a Friday.
- 01
One ring at a time
Promote always targets the next ring in the pipeline. Skipping is impossible by design — not by policy.
- 02
The source must prove it
A live health check on the source ring gates every promotion before anything deploys.
- 03
The target must earn it
After deploying, health checks run with configurable retries before the hop is called good.
- 04
Failure undoes itself
If the target stays unhealthy, it is rolled back to its previous version automatically.
- 05
Everything is written down
Every seed, promote and rollback lands in history — success or failure, per app, per ring.
protocol demo · payments-api
14:02:11✓ promote int → test · healthy
14:02:19✓ promote test → acc · checks 3/3
14:02:27✗ promote acc → prod · rolled back
14:02:27history updated · rolled_back=true
Flow while it’s safe. Stop where it matters.
Flag a ring for auto-promote and a healthy landing continues onward in the same operation — hop by hop, under the same lock, with the same gates and the same auto-rollback. The chain stops at the first ring with the flag off, so nothing reaches production without a human.
# a healthy int → test carries on to acc automatically; acc holds for a human before prod
Live operations
Watch every hop land.
Seed, promote and rollback run as live jobs. Each step reports status, logs and duration to the console as it happens — acquire the lock, check the source, deploy, verify, record.
A complete audit trail
Full history per application and ring — who moved what, where, when, and whether it held.
Step-level visibility
The console polls running jobs every second: per-step status, logs and durations, live.
AI diagnosis on failure
Failed deploys get an AI diagnosis grounded in the persisted step logs — evidence, not guesswork.
- ✓acquire app lock0.0s
- ✓source health check — int0.3s
- ✓deploy v2.14.0 to test8.2s
- ✓health check test — attempt 2/34.1s
- ✓record history0.1s
$ kubectl set image deploy/payments-api web=…:v2.14.0
deployment.apps/payments-api image updated
waiting for rollout: 2/3 replicas updated…
✓ recorded: promote int → test · healthy
The production gate
Harder to enter than to leave.
Production deserves asymmetry: deliberate on the way in, instant on the way out.
Entering prod asks for the password
With RP_PROD_PASSWORD set, anything that lands in the last ring — a promotion, a direct seed, even enabling auto-promote into it — must carry the production password.
Leaving prod never waits
Rollbacks are deliberately exempt from the gate. When you are paged at 3am, incident response is never blocked by a password prompt.
Deployers
It meets your infrastructure where it is.
The deployer is chosen per application — one control plane promotes Kubernetes services and VM apps side by side, under the same rules.
Kubernetes apps
The kubectl deployer runs set image + rollout status, authenticated in-cluster via the pod’s ServiceAccount. Battle-tested rollout semantics with a tiny dependency tree.
✓ deployed v2.14.0 · healthy — result recorded
VM apps with existing CI
The GitHub Actions deployer dispatches the workflow you already have and waits for the run to conclude — same health checks, same auto-rollback. Seeded versions are validated against the repo’s real branches and tags, so a typo’d ref never launches a doomed run.
✓ deployed v2.14.0 · healthy — result recorded
Onboarding
An app is a block of YAML.
No plugin, no SDK, no rebuild. Declare where each ring lives and how to check its health, apply the config, and the app appears in the console. Apps only define the rings they actually live in.
apps:
- name: billing-worker rings:
int:
namespace: int
deployment: billing-worker
container: worker
image: registry.example.com/billing-worker health_url: http://billing-worker.int.svc/health
test:
# …same shape, per ring
prod:
# only the rings the app lives inRoadmap
Where the ring is heading.
Not shipped yet — this is the direction the control plane is growing, ordered by what platform teams keep asking for. Open a GitHub issue to shape the priority.
Chat-ops approvals
Approve a gated prod promotion from Slack or Teams — with the audit entry recording who.
Metric-based gates
Gate promotions on Prometheus / OpenTelemetry queries — error rate and latency, not just a 200 from /health.
Freeze windows
Block promotions during change freezes and out-of-hours. Rollbacks stay exempt, always.
Canary steps inside a ring
Shift a slice of traffic to the new version before the ring flips over.
SSO & per-app roles
OIDC sign-in with per-app permissions, so every approval carries an identity.
GitOps & Terraform
Declare apps, rings and gates from your repository instead of a ConfigMap.
FAQ
Fair questions.
Does it replace my CI?
No. CI builds and tests; Ring Promoter moves what CI produced through your environments. A pipeline calls one authenticated endpoint — seed or promote — and a non-2xx response means the promotion failed, so `curl --fail` is all the integration you need.
What do I need to run it?
One small binary (or container) — the web console and REST API are embedded in it. Locally it runs with an in-memory store and a no-op deployer, no cluster or database needed. In production: Postgres and, for Kubernetes apps, a ServiceAccount.
What counts as a version?
For Kubernetes apps, an image tag. For GitHub-deployed apps, a branch, tag or commit SHA — validated against the repository before anything is dispatched, and the console's picker only offers refs that exist.
What happens when a health check fails?
The target ring is retried a configurable number of times, then automatically rolled back to its previous version. The failure, the rollback and the step logs all land in history, and the console can produce an AI diagnosis from that evidence.
Can two promotions collide?
No. Operations on the same application are serialized by a Postgres advisory lock, which holds across replicas — an accidental scale-up cannot run two concurrent promotions on the same app.
Bring release discipline to your cluster.
Try it in one command — the defaults use an in-memory store and a no-op deployer, so there is nothing to install and nothing to break.
$ go run ./cmd/ringpromoter --config config.yaml ▊
# → http://localhost:8080 · token: local-dev-token