Skip to content
downpipes docs

Upgrading a live deployment and rolling back safely

You perform every upgrade yourself, in your own account, with your own credentials. The vendor cannot deploy, modify or read your deployment, so there is no upstream actor who could push a change at you. This page is the runbook a self-hoster follows to take a live engine from one release to the next without surprises, and the exact rules for backing a release out if it misbehaves.

Two facts shape the whole procedure. First, nothing applies itself: even with the update channel switched on, all it ever does is tell the engine that a newer version exists. Second, the archives on your destination are not part of an engine upgrade at all. They are an open, versioned format the offline reader understands regardless of what the engine is running, and the engine never starts writing a newer format version implicitly. So an upgrade is about the running worker, never about the data already in the bucket.

Before you start: read every intervening release note

This is a required stop, not advice. Read the release notes for every version between the one you are running and the one you are upgrading to, not just the target’s notes. A release can be marked as a required stop that cannot be skipped, and a release can change the schema epoch that bounds whether you can later roll back. Both of those are things you can only learn from the intervening notes, and both change how the rest of this runbook plays out.

Skipping intervening notes is how an operator walks into an upgrade that needed a stepping-stone version first, or rolls back across an epoch boundary they did not know had moved. The engine has guards for some of this, covered below, but the notes are where the human decision is made.

The upgrade runbook

This is the operator-CLI path, which works on any engine. The one-click console path is covered at the end, and it produces the same outcome by a different route.

  1. Get preflight green first

    Run GET /admin/preflight and confirm it is green; the console Readiness card shows the same facts. On the account side, run downpipe preflight --account <id>, which lists the account’s available domains, verifies the chosen one is on an active zone, and reports Secrets Store headroom, the Workers plan and R2. Do not upgrade over a red preflight. If you do, you will not be able to tell afterwards whether the upgrade caused a breakage or merely revealed one that was already there.

  2. Export your evidence before any major upgrade

    Export the support bundle, the audit log as JSON with its head hash, and the config history. These exports are small and redaction-safe, and they are what makes “restore the pre-upgrade state” a real remediation rather than a hope. For an upgrade across a schema epoch they are not optional, because the pre-upgrade evidence plus the release notes, not a blind redeploy, is the path back across an epoch.

  3. Verify artefact digests against the published checksums

    Pull the release into your deployment pipeline and verify the artefact digests against the published checksums before you deploy anything. The engine can self-report the SHA-384 of its deployable bundle in GET /admin/status once deployed, but the check that matters here is the one you do against the published checksum before promotion, so a corrupted or substituted artefact never goes live.

  4. Deploy with npm run deploy: engine, then console

    Run npm run deploy from the engine directory, then deploy the console. Use npm run deploy, never a bare wrangler deploy. The reason is that a bare deploy replaces the worker’s binding set with exactly what wrangler.toml lists, which silently drops every source you attached from the console, because those sources live on the worker and not in the committed config. npm run deploy runs the source-binding reconcile first, reading the live bindings and deploying a superset, and it stops rather than ship a binding-dropping config. The full hazard and the reconcile are in deploy safety and bindings.

    cd engine
    npm run deploy        # reconciles console-attached sources, then deploys
    # then deploy the console

    For a cautious rollout, you can upload the new version with zero traffic first and smoke it before promotion. Run npm run sync-bindings so the preview config carries the live sources, then npx wrangler versions upload -c wrangler.deploy.toml. Note the constraint in the next section: a release containing a Durable Object migration cannot use this gradual path.

  5. Verify after, at five minutes and at one hour

    Confirm the engine came up correctly and stayed up. Check GET /admin/health returns 200, and GET /admin/status reports the new engineVersion with ready: true. Confirm the audit log carries the engine-version-change event, so the upgrade is itself in the tamper-evident trail. Confirm the next */15 cron tick lands, which you can see as lastTickAt advancing and the preflight cron item staying verified, and confirm the next scheduled restore test passes. Check the fast signals at five minutes and the slower ones, the cron tick and the restore test, again at an hour.

The post-upgrade signals, gathered in one place:

Signal Where to read it What “good” looks like
Liveness GET /admin/health 200
Version and readiness GET /admin/status the new engineVersion, ready: true
The upgrade itself recorded the audit tail an engine-version-change event
Cron is still firing GET /admin/preflight cron item, or lastTickAt the item stays verified; lastTickAt advances within the interval
Recovery still proves out the next scheduled restore test it passes

Rolling back

Rolling back means putting the previous release live again, either by redeploying it or with wrangler rollback. It is a useful tool, but it is not universally safe, and the limits are real platform limits rather than caution for its own sake.

A rollback never touches state. Durable Object storage, KV, R2 and D1 keep whatever the newer release wrote, so a rollback reverts the code, not the data. That is exactly why it is safe within its bounds, and exactly why it cannot be relied on to undo a schema change.

Rollback rule What it means in practice
Safe only within one schema epoch Redeploying the previous release or wrangler rollback is safe between releases that share a schema epoch. The release notes say when an epoch changes; epoch bumps are rare and always land on a required stop.
Never touches state The newer release’s writes to Durable Object storage, KV, R2 and D1 remain. A rollback is a code revert, not a state revert.
Refused across a Durable Object migration A release with a DO migration cannot be rolled back across that migration.
Reaches only recent retained versions A rollback can reach at most the recent versions Cloudflare retains, not arbitrarily far back.

A release with a Durable Object migration is a one-way step for the gradual path and for rollback

A release that carries a Durable Object migration, meaning new DO classes or a schema change, applies atomically through a full deploy. It cannot use the gradual zero-traffic versions-upload path, because the platform applies migrations atomically rather than to a fraction of traffic, and a rollback is refused across that migration. Plan a migration release as a deliberate forward step with its evidence exported first, not as something you can ramp or quietly back out.

Across an epoch boundary, the path back is not a blind redeploy. It is the pre-upgrade evidence you exported plus the release notes, used to rebuild the pre-upgrade state deliberately. This is the reason the evidence export is a required step before a major upgrade.

Secrets survive deploys and rollbacks; plain-text vars do not

Worker secrets persist across deploys and rollbacks, so your signer, recipient keys and any destination credentials are not disturbed by an upgrade. Plain-text vars in wrangler.toml, by contrast, are overwritten by whatever the deployed config says. Keep secrets as secrets, and treat any value you put in [vars] as defined entirely by the config you deploy.

How this relates to the update channel

The console offers a one-click update as the no-CLI path to the same outcome the runbook above produces by hand. It is built on the same safety order: a release is only ever uploaded or promoted after its bytes verify SHA-384 against a signature-verified channel, a rollback target is recorded first, and the promotion is canary-gated with an automatic rollback on any non-passing verdict. A release that needs a Durable Object migration is refused for one-click apply and must be applied manually, for the same reason the gradual path cannot ramp one. A release can also carry the console as a component: the one-click apply then updates the engine first, waits for it to settle, and only then deploys the console, and a console that fails to land is rolled back alone while the engine keeps the new version. The component flow is on applying updates.

Two honesty points govern how to think about that one-click path today.

The update channel is live. The engine pulls a signed channel and verifies it against a pinned signer; it never receives a push. The owner ceremony is complete: the signer public key is pinned and the vendor serves the signed channel host at the singular update.downpipes.io, so on a base-config deployment GET /admin/updates reports configured: true and verifies under the pinned signer, and the console shows an available-update posture rather than asking you to configure the channel. The channel is signed static content, so the engine trusts the pinned signature rather than the host, and a channel that does not verify under the pinned signer is reported as unverified rather than applied. The generated wrangler.deploy.toml used for the gradual zero-traffic rollout is not an exception to any of this: the reconcile builds it as the committed wrangler.toml plus the live source bindings appended, and it never touches [vars], so it carries forward the same pinned signer and the same singular update.downpipes.io host, and a deployment built from it reports configured: true exactly as a base deployment does. None of this gates a backup or a recovery: the licence and the update channel are optional and never sit on the data path.

The live apply and rollback are supervised-first. The parts of the console path that actually talk to Cloudflare to upload and promote a version are correct by the documented Cloudflare API shapes and are defensively written, and their validators drive every method against a stub, asserting the request shapes, the binding and secret preservation, and that every opaque Cloudflare failure becomes a precise, actionable message. The engine driver has now also run against a real account exactly once: on 2026-07-03 a promote and an automatic rollback both executed correctly against live Cloudflare. Once is not a track record, and the console component’s static-assets driver has not run live at all. Because everything is gated before a driver can act, the worst a bad call can do is a brief availability blip on the engine or console worker, which Cloudflare lets you roll back, while the data in your bucket and your offline recovery are independent of the running engine and unharmed. Treat the first live use of one-click apply or rollback on your deployment as a supervised exercise, and keep the operator-CLI runbook above as the proven path until you have done that.

Where this fits

This page is the canonical home for the upgrade runbook and the rollback rules. For the binding hazard that makes npm run deploy mandatory and how to recover a dropped source, see deploy safety and bindings. For the two-worker shape an upgrade deploys into and why only the console needs a route, see topology. For what to do when an upgrade or anything else goes wrong on a live deployment, see incident response.

Last updated .