Offboarding and a clean, no-custody exit
This page is for the self-hoster decommissioning a downpipes deployment, whether you are leaving the product, consolidating an organisation, or migrating to another provider. Leaving is meant to be boring. It is the no-custody model working as designed: when you are done, your archives are still yours and still recoverable, with neither the engine nor the vendor in the loop.
There is no one-click exit, and that is deliberate. Exit is a short, composed workflow over surfaces you already use, and the order matters. Done in order, it never strands your data. Done out of order, you could delete the engine before you have proven you can still read what it wrote. Read the sequence once before you start, then work it top to bottom.
What you keep, and why it is a complete recovery capability
Before the teardown, understand what survives it, because that is the whole point. Five things, kept together, are a complete recovery capability that needs no engine, no licence, and no vendor. The simplest way to keep four of the five is to keep the whole recovery-kit folder you downloaded at the key ceremony, because every one of them except the bucket is a file in it.
| What you keep | Why it matters |
|---|---|
| The destination bucket | Your archives. They are ciphertext object-store bytes, portable to any S3-compatible store. |
The downpipe CLI binary | The offline reader. It verifies and restores directly from the bucket bytes, reading a local copy of the bucket tree, an S3-compatible endpoint over --s3-endpoint, or an Azure Blob container over --azure-endpoint. |
| The break-glass private key | The one key that decrypts your archives. It was never in the account and never sent to the vendor. |
signer.pub, the operator signer public key | The reader refuses to verify or restore without a pinned signer. From engine 0.3.6, the bucket holds a copy of the current signer’s key that the fingerprint on your sheet can pin (reader 0.3.4). A bucket that no engine 0.3.6 or later has written to has none, and after a re-key the copy is the new signer’s. In those cases the file in your recovery kit is the only source. |
| The printed recovery sheet | Your public fingerprints, which are what you check a candidate key file against. It carries no secret and no key. |
The recovery sheet is not the signer key
Keep signer.pub itself, not only the sheet. downpipe verify and downpipe restore refuse to run without a pinned signer, and the sheet carries a signer fingerprint, not the key. From engine 0.3.6, the recovery bundle beside your data holds a copy of the current signer’s signer.pub. From reader 0.3.4, --signer-fingerprint with the value on your sheet pins that copy, and the reader refuses a copy whose fingerprint does not match. Every run overwrites the copy. A bucket that no engine 0.3.6 or later has written to has no copy, and after a re-key the copy is the new signer’s. In those cases a kit reduced to the break-glass identity and a printed sheet reads nothing. So keep every signer.pub you have had, including the old one after a re-key. If you are unsure which of your files is the right one, downpipe keys --fingerprint --signer <file> prints the fingerprint to compare against the sheet.
An Azure Blob destination needs its credential kept, not a copy taken
The reader pulls from an Azure container directly, over --azure-endpoint with --azure-container, so there is no copy-to-disk step to add to the teardown below. What an Azure estate does have to keep, beyond the five above, is the container’s credential, because the reader takes it from the environment and never from a flag. Keep a storage account access key for AZURE_STORAGE_KEY, or a shared access signature for AZURE_STORAGE_SAS_TOKEN with an expiry far enough out to be worth holding, since a SAS that has lapsed reads as an access failure rather than as a missing key. Prove it while the engine is still up, by running downpipe verify --deep against the container with the credential you intend to keep.
One offline operation is not available on Azure: prune --apply, because the reader’s Azure backend implements no delete. That is retention rather than recovery, so it changes nothing about reading your archives back; after the engine is gone, what the container holds is governed by the container’s own lifecycle and immutability settings. See destination providers compared.
These five are enough because of how the format works. The archive format is open and versioned, and a versioned format pointer (FORMAT.md) and recovery instructions live in the bucket itself under the _RECOVERY/ prefix, next to the data. The full specification and the reader are the open-source downpipe project, and the offline CLI reads the bytes wherever they sit using only your own two key files. So your recoverability does not depend on the vendor being reachable, the licence being active, or the engine still running. It depends only on the format and your own keys, both of which you are keeping.
Getting the Go CLI, and keeping a copy that lasts
The downpipe reader is the open-source CLI, built from source with Go 1.26 or newer, from the public repository github.com/downpipes-io/downpipe. go install github.com/downpipes-io/downpipe/cmd/downpipe@latest resolves against the public module proxy, and a plain git clone of the repository also works, with no account or engagement needed; the MIT licence then lets you keep it, rebuild it and pass it on without asking anyone. Even so, take that source before you leave, not after: an exit plan that assumes you can fetch the reader later has a gap in it exactly where you cannot afford one. Keep the source alongside the built binary, because for something you are holding for the long term the source is the more durable of the two: it needs no module proxy and no release page to rebuild years from now, provided the machine doing the rebuild already has a Go that satisfies the toolchain go1.26.6 pin in go.mod. Under Go’s default GOTOOLCHAIN=auto an older local Go tries to download that toolchain and fails outright when nothing is reachable, so hold a matching go binary beside the source rather than discovering the gap after you have left (install the tool carries the pin). Before you rely on the binary for a real recovery, run downpipe --help and confirm prune, recombine and unseal-export are all listed. A from-source build stamps no release version, so it reports dev. The tool follows 0ver, so its version stays below 1.0 and will not become v1.0.0 under that policy. Keeping the binary alongside your recovery sheet is what makes the archives readable for as long as you retain them, and it carries a duty you must not take on trust. A reader implements exactly one major.minor of the archive format, at any patch, and refuses every other version with exit 6 rather than making a best effort (checkFormatVersion in internal/format/verify.go). There is one format line, downpipe/0.1.x, and the reader implements exactly it.
Confirm that against your own bucket rather than against this page, because the format version is recorded there: read formatVersion in a run’s root manifest, or the format pointer at _RECOVERY/FORMAT.md. If what you find is not downpipe/0.1.x, stop and raise it, because no override reads a format a build does not implement and no second reader is published for another line.
Then prove the pairing instead of inferring it. Run a downpipe verify against a real archive of your own before you file the binary away. Keep the binary only if that verify passes. A version gate accepting your format label is a necessary condition and not a demonstration that the whole read path works. The whole point of the copy you are retaining is that nobody will be available to debug it later. See the command reference for the build and the verify and restore invocations.
The ordered teardown
Work these in order. Each step either captures something you may need later or removes something you no longer should keep. The ordering guarantees you never remove a capability before you have what replaces it.
Take a final backup and wait for it to land
Run a final backup of anything you still want captured, and wait for it to complete. The Runs screen shows it
okwhen the run has sealed to the destination. This is the last point at which the engine writes for you, so make sure the thing you most want preserved is in this run before you go further.Prove recoverability one last time
Do not take the final backup on faith. Prove it. Either run a drill from the console, or run
downpipe verifyagainst the destination from a clean machine with your break-glass key and yoursigner.pub.verifyrefuses to run without a pinned signer, so the invocation isdownpipe verify --archive <dir> --run <runId> --identity identity.key --signer signer.pub. For an archive from engine 0.3.6 on, run it again with--signer-fingerprintand the signer line of your sheet in place of--signer. This proves that the sheet route works too.A verify recomputes every hash, checks the signed root against your pinned signer, and confirms completeness, so a green result is evidence that the bytes you are about to depend on are genuine and whole.
This is the step that turns “we have a bucket” into “we have a recovery capability”. Doing it now, while the engine is still standing, is also the cheapest moment to discover that a file is missing from your kit. See prove recoverability for the drill and the offline verify.
Export the evidence you may need later
Export the records that make a later argument or audit possible, and store them off the account. Pull the audit log as JSON with its head hash included (
GET /admin/audit/export), the config history (GET /admin/config/history), and a support bundle (GET /admin/support/bundle) for the versions and provenance snapshot. These are small and redaction-safe for customer data, though the audit export does carry operator identity such as emails and addresses, so store it as a sensitive operational record. The head hash is what lets you prove later that the exported chain was not rewritten. These are evidence for a later argument or audit, not a restore artefact; rebuilding a working environment from the signed control-plane export is a separate path, described in recovering downpipes itself.One thing this export cannot be is the last word. It is a snapshot taken at the sequence number it reports, and every act the remaining steps perform, revoking the pull credentials, pausing the schedules and switching off the canary, is audited after it and appears nowhere in it. If you need the record to run to the end of the account’s life, repeat the export as the last thing you ask the engine to do, immediately before you delete the Workers, and keep both. The engine cannot audit its own removal, so no export will ever contain that.
The engine’s assurance reports can also be evidence. Save each report you want as JSON from
GET /admin/reports/<kind>, because the PDF carries no signature that the reader can check. From reader 0.3.4,downpipe verify-report --signer signer.pub --report <file>checks the signature of a saved report offline, with no engine. It exits 0 when the report verified and 2 when it did not. It exits 6 when the file is not a report JSON, for example the PDF.Revoke the vendor-facing surfaces
Now wind down the two things that touch the vendor. Revoke any owner-minted pull credentials, so no feed remains live after you leave. There are three scopes and they are not all in one place: the diagnostics scope is under Settings, and the audit-feed and metrics scopes are on their own tiles on the Integrations screen. Check all three; revoking the one under Settings leaves the other two live. A revoke needs only the Owner role, with no step-up: the engine gates minting a credential, not shutting one off (
STEPUP_SUBS,engine/src/admin/router-core.ts).Then let the licence lapse, or remove
LICENCE_TOKEN. Nothing operational changes when the licence goes: backups, restores, drills, and the offline reader all keep working exactly as before, because the licence is an assurance and support entitlement, never a key. See licensing and the control plane for what does and does not change.Pause the schedules, and know what a pause does and does not stop
Pause every downpipe so no backup runs after your final one. Pausing also stops the retention prune for that downpipe: nothing in the archive is deleted for a downpipe you have paused, and the pass records that it stood down rather than doing it silently, so you can see it in the support pack. That is the whole point of the word, and it is why this step comes before you start removing anything.
One engine activity is not a downpipe and keeps running. The canary is on by default, so pausing downpipes does not pause it. Every hour it writes a probe object and a small known-answer cell into each configured destination, reads them back and deletes them again, all inside its own
_CANARY/namespace.It never touches your archives, and while it flies it is a continuing proof that the destination still accepts writes and that a sealed run can still be read back, which is worth having against a bucket you are about to hold on your own. If you would rather the bucket were completely still, turn the canary off on the Canary screen under Govern, which is an Owner action. Either choice is legitimate. See configuring and operating the canary.
If you do want data pruned before you go, pausing is not the way to ask for it. Use the prune request on the downpipe, which needs a second person to approve it, and do it before you pause.
Delete the two Workers and their secrets
Delete the engine and the console Workers, and their secrets, from your Cloudflare account. This removes all of the compute, and it is the irreversible step: after it there is no console to export from, no drill to run and no audit route to call, so anything you have not already captured is not recoverable from us, because we never had it. The destination bucket is untouched by this step; you are removing the engine that wrote to it, not the archives it wrote.
No backup data of yours is vendor-held, so there is nothing of that kind to request back. What the vendor does hold is a customer-of-record: your organisation name, your contacts and the commercial terms, which is how the licence and the support entitlement work at all. For support matching it also holds the email domains derived from your purchase and the Cloudflare zones your consoles claimed the licence from. It keeps a list of any such domain an operator removed, so that domain is not derived again. Cancel the licence subscription, and if you want the business-contact details cleared as well, ask, because that is an action on our side rather than a switch on yours. The financial records are kept for the retention period that applies to financial records.
After the last step, the engine is gone and your five kept artefacts remain. That is the exit complete: no vendor involvement is required for you to retain recoverability, because the kept artefacts are a complete recovery capability on their own.
If you are removing people rather than the whole account
Offboarding individual members is a different job, and it has a guard worth knowing before you start: the engine refuses to remove the last remaining Owner, answering 400 would remove the last Owner, and the same refusal applies whether the removal comes from the console or from a SCIM deprovision. So an account can never be left with no one able to administer it. Under dual control the floor is two Owners rather than one, because a lone Owner could otherwise arm four-eyes approval and have no second Owner to approve anything.
Nothing above is affected by that guard, because deleting the Workers removes the whole control plane rather than a grant within it. See offboard a member for the per-person path and SCIM and deprovisioning for the automated one.
Why the order is what it is
The sequence is not arbitrary. Each early step is a capture, and each late step is a removal, and you never remove before you have captured.
The final backup and the recoverability proof come first because they are the only steps that need a working engine. Once you have a proven-recoverable final run and the evidence exported, the engine has done everything it can do for you, and deleting it costs you nothing. Revoking the vendor surfaces and disabling schedules come next because they stop new activity without removing any capability. Deleting the Workers comes last because it is the irreversible removal. By the time you reach it, you have already proven you do not need them.
The same logic decides what you keep versus what you remove. You remove the things that grant access into the account, the Workers, their secrets, the support credentials, and the standing licence. You keep the things that grant access to your data offline, the bucket, the reader, the recovery sheet, the break-glass key and signer.pub. The cut runs exactly along the no-custody line.
Two exit shapes: migration and proof-of-deletion
Two endings need a small variation on the last steps.
If the exit is a migration to a new provider or an organisation consolidation, copy the bucket the way you would copy any object-store data. The archives are portable bytes and the reader reads them wherever they sit, so a migration is a bucket copy plus the recovery sheet, the break-glass key and every signer.pub travelling with it. Nothing about the format ties it to the original account.
If the exit must instead prove deletion, the order changes, because the audit export is an engine route and the engine is what you are about to remove. Empty and delete the bucket while the Workers are still standing, then take a second audit export as the very last thing you ask the engine to do, and keep it with its head hash as the closing evidence. Only then delete the Workers. The plain teardown above would have you reverse those two. Reversing them leaves you with no way to produce the closing record at all: GET /admin/audit/export dies with the Worker that served it. Nothing in the destination or the recovery kit replaces it.
Here is what that closing record can and cannot show. It covers the bucket deletion, because that happened while the engine was running and audited. It cannot cover its own export or the Worker deletion that follows, because no log records the act that ends it. So the closing statement is that the archives were deleted on a stated date, evidenced by a hash-chained export whose head hash you hold, and that the deployment was removed afterwards, evidenced by your own Cloudflare account records rather than by ours.
Member offboarding and retiring the bootstrap token are separate ceremonies
Decommissioning the whole deployment is distinct from removing a single person, and distinct again from retiring the one-time bootstrap credential. Both of those have their own pages, and neither is part of the exit teardown above.
Removing a member does two things in the engine and leaves one thing to you. The console removes the departing person’s in-app role immediately and, in the same step, terminates their native downpipes sessions: it bumps the per-email session epoch and the per-subject not-before instant, so any of their sessions fail closed on the very next request, whether held by passkey, recovery code, OIDC, or SAML.
What the engine cannot do is deprovision your identity provider or end a Cloudflare Access session at the edge, because those are Cloudflare-edge tokens the engine does not mint, so that part is yours to do. Nor does removing the role remove the ways that person signs in: their passkey credentials, their banked recovery codes and any unredeemed invite in their name all survive it, and the last two are bearer secrets that each lead back to a fresh credential. Revoking those is a separate action, on the Roles and access tab under Sign-in factors, and on the admin API as POST /admin/signin-factors/revoke. See offboard a member for the ceremony, the three sign-in stores and the hard boundary.
Retiring the bootstrap token belongs to setup hygiene, not exit. The ADMIN_TOKEN is a one-time bootstrap for the first Owner; once your Owner passkey or Cloudflare Access works and you have saved your recovery codes offline, retire it in the Security Centre (immediate, no redeploy) or delete the secret. The engine refuses to retire it until a way back in exists, either recovery codes for an Owner or a second Owner, so retiring it can never strand you. A live shared bearer is one human holding two identities, which quietly defeats dual control, which is why the Security Centre raises a dispose-bootstrap-token finding while it remains live.
Where this fits
For the broader guided wind-down of leaving downpipes, including the no-custody guarantee, see leaving downpipes. For removing a single team member and the identity-provider boundary, see offboard a member. For what does and does not change when the licence lapses, see licensing and the control plane. For the offline verify and restore that prove the kept artefacts are a real recovery capability, see prove recoverability and break-glass offline recovery. For why the kept artefacts are enough on their own, see the no-custody trust model.
Last updated .