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.

release-control · payments-api

int

Integration

v2.14.0

test

Test

v2.13.9

acc

Acceptance

v2.13.9

prod

Production

v2.13.2

v2.14.0

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.

    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.

    intauto
    testauto
    acchold
    prod

    # 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.

    PROMOTE payments-api · int → testjob 8f3a…c2
    • 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.

    ring-promoter

    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.

    config.yamlapplied ✓
    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 in

    Roadmap

    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.

    planned

    Chat-ops approvals

    Approve a gated prod promotion from Slack or Teams — with the audit entry recording who.

    planned

    Metric-based gates

    Gate promotions on Prometheus / OpenTelemetry queries — error rate and latency, not just a 200 from /health.

    planned

    Freeze windows

    Block promotions during change freezes and out-of-hours. Rollbacks stay exempt, always.

    exploring

    Canary steps inside a ring

    Shift a slice of traffic to the new version before the ring flips over.

    exploring

    SSO & per-app roles

    OIDC sign-in with per-app permissions, so every approval carries an identity.

    exploring

    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