{
  "manifest_version": "1",
  "slug": "migrate-durable-objects",
  "title": "Durable Objects from Cloudflare",
  "headline": "Migrate a Durable Object from Cloudflare",
  "description": "Move a Cloudflare Worker and its Durable Objects' data to celld on an exe.dev VM: same code, every object copied and checked, in your own bucket.",
  "category": "migration",
  "tags": [
    "cloudflare durable objects",
    "migrate off cloudflare",
    "export durable object data",
    "celld",
    "wrangler",
    "sqlite",
    "self-hosted"
  ],
  "url": "https://exe.dev/deploy/migrate-durable-objects",
  "markdown_url": "https://exe.dev/deploy/migrate-durable-objects.md",
  "upstream": {
    "website": "https://celld.dev",
    "source": "https://github.com/denoland/celld",
    "docs": "https://celld.dev/docs/cloudflare-compat",
    "license": "Apache-2.0 (celld)"
  },
  "example_vm_name": "my-worker",
  "time": "1 hour",
  "stack": [
    {
      "name": "celld 0.6",
      "detail": "Runs your Worker, set up as in [A Durable Object with celld](/deploy/celld)",
      "port": 8000,
      "data": "~/celld/state"
    },
    {
      "name": "Your Worker",
      "detail": "Your project, with a `celld.json` beside its Wrangler config",
      "data": "~/app"
    },
    {
      "name": "Migration files",
      "detail": "Every object's export, one file each; also your fallback copy",
      "data": "~/migration"
    },
    {
      "name": "Your bucket",
      "detail": "Every object's database, saved before celld answers",
      "data": "R2, S3, or Tigris"
    }
  ],
  "access": [
    {
      "url": "https://my-worker.exe.xyz/",
      "who": "private",
      "what": "Your Worker on celld, until you move your domain"
    }
  ],
  "inputs": [
    {
      "name": "APP_REPO",
      "description": "your Worker's Git repository; for a private GitHub repository, connect the [GitHub integration](/docs/integrations-github) first"
    },
    {
      "name": "FROM_URL",
      "description": "where the Worker answers on Cloudflare; its `workers.dev` address is best, because it stays put when your domain moves"
    },
    {
      "name": "CLOUDFLARE_ACCOUNT_ID",
      "description": "the account that runs the Worker"
    },
    {
      "name": "CLOUDFLARE_API_TOKEN",
      "description": "an API token from the **Edit Cloudflare Workers** template"
    },
    {
      "name": "CELLD_BUCKET",
      "description": "an empty bucket for celld, written `s3://name` ([how](/deploy/celld#get-a-bucket))"
    },
    {
      "name": "S3_ENDPOINT",
      "description": "R2: `https://\u003cACCOUNT_ID\u003e.r2.cloudflarestorage.com`; Amazon S3: leave empty"
    },
    {
      "name": "AWS_REGION",
      "description": "`auto` for R2 and Tigris; the bucket's region for Amazon S3"
    },
    {
      "name": "AWS_ACCESS_KEY_ID",
      "description": "a key that can read and write that bucket, and nothing else"
    },
    {
      "name": "AWS_SECRET_ACCESS_KEY",
      "description": "the key's secret"
    }
  ],
  "delivers": [
    "Your Worker running on celld at `https://\u003cvm\u003e.exe.xyz/`, private, with its state in your bucket.",
    "Every Durable Object with data on Cloudflare exported to `~/migration` and imported into celld.",
    "Each imported object checked identical to its export from Cloudflare.",
    "A report of what moved, which objects had no name, and anything celld can't run.",
    "Cloudflare serving as before, plus a token-protected export route."
  ],
  "deploy": {
    "new_vm_url": "https://exe.dev/new?prompt=APP_REPO%3D%3Cfill-this-in%3E%0AFROM_URL%3D%3Cfill-this-in%3E%0ACLOUDFLARE_ACCOUNT_ID%3D%3Cfill-this-in%3E%0ACLOUDFLARE_API_TOKEN%3D%3Cfill-this-in%3E%0ACELLD_BUCKET%3D%3Cfill-this-in%3E%0AS3_ENDPOINT%3D%3Cfill-this-in%3E%0AAWS_REGION%3D%3Cfill-this-in%3E%0AAWS_ACCESS_KEY_ID%3D%3Cfill-this-in%3E%0AAWS_SECRET_ACCESS_KEY%3D%3Cfill-this-in%3E%0A%0ASet%20up%20Durable%20Objects%20from%20Cloudflare%20on%20this%20VM%20by%20following%20the%20exe.dev%20recipe%20at%20https%3A%2F%2Fexe.dev%2Fdeploy%2Fmigrate-durable-objects.md%20from%20top%20to%20bottom.%0A%0AThe%20values%20at%20the%20top%20of%20this%20message%20are%20mine%3A%0A-%20APP_REPO%3A%20your%20Worker%27s%20Git%20repository%3B%20for%20a%20private%20GitHub%20repository%2C%20connect%20the%20%5BGitHub%20integration%5D%28%2Fdocs%2Fintegrations-github%29%20first%0A-%20FROM_URL%3A%20where%20the%20Worker%20answers%20on%20Cloudflare%3B%20its%20%60workers.dev%60%20address%20is%20best%2C%20because%20it%20stays%20put%20when%20your%20domain%20moves%0A-%20CLOUDFLARE_ACCOUNT_ID%3A%20the%20account%20that%20runs%20the%20Worker%0A-%20CLOUDFLARE_API_TOKEN%3A%20an%20API%20token%20from%20the%20%2A%2AEdit%20Cloudflare%20Workers%2A%2A%20template%0A-%20CELLD_BUCKET%3A%20an%20empty%20bucket%20for%20celld%2C%20written%20%60s3%3A%2F%2Fname%60%20%28%5Bhow%5D%28%2Fdeploy%2Fcelld%23get-a-bucket%29%29%0A-%20S3_ENDPOINT%3A%20R2%3A%20%60https%3A%2F%2F%3CACCOUNT_ID%3E.r2.cloudflarestorage.com%60%3B%20Amazon%20S3%3A%20leave%20empty%0A-%20AWS_REGION%3A%20%60auto%60%20for%20R2%20and%20Tigris%3B%20the%20bucket%27s%20region%20for%20Amazon%20S3%0A-%20AWS_ACCESS_KEY_ID%3A%20a%20key%20that%20can%20read%20and%20write%20that%20bucket%2C%20and%20nothing%20else%0A-%20AWS_SECRET_ACCESS_KEY%3A%20the%20key%27s%20secret%0AAsk%20me%20for%20any%20that%20still%20say%20%3Cfill-this-in%3E%20before%20you%20need%20them.%0A%0AThe%20recipe%27s%20example%20VM%20name%20is%20my-worker.%20This%20VM%27s%20name%20is%20the%20output%20of%20%60hostname%60%3A%20use%20it%20wherever%20the%20recipe%20writes%20my-worker.%20Skip%20the%20step%20that%20creates%20the%20VM%3B%20you%20are%20on%20it.%0A%0AYou%20are%20done%20when%20all%20of%20these%20are%20true.%20Check%20each%20one%20yourself%3A%0A-%20Your%20Worker%20running%20on%20celld%20at%20%60https%3A%2F%2F%3Cvm%3E.exe.xyz%2F%60%2C%20private%2C%20with%20its%20state%20in%20your%20bucket.%0A-%20Every%20Durable%20Object%20with%20data%20on%20Cloudflare%20exported%20to%20%60~%2Fmigration%60%20and%20imported%20into%20celld.%0A-%20Each%20imported%20object%20checked%20identical%20to%20its%20export%20from%20Cloudflare.%0A-%20A%20report%20of%20what%20moved%2C%20which%20objects%20had%20no%20name%2C%20and%20anything%20celld%20can%27t%20run.%0A-%20Cloudflare%20serving%20as%20before%2C%20plus%20a%20token-protected%20export%20route.%0A%0ARead%20the%20whole%20recipe%2C%20including%20the%20closed%20details%2C%20and%20do%20%22Prepare%20the%0Amove%20by%20hand%22%20in%20order%2C%20skipping%20VM%20creation%3B%20the%20walkthrough%20above%20it%20is%0Afor%20the%20owner.%20Where%0Ait%20names%20steps%20of%20the%20celld%20recipe%2C%20follow%20%2Fdeploy%2Fcelld.md%20on%20this%20site.%0AAsk%20the%20owner%20before%20deploying%20anything%20to%20Cloudflare.%20Never%20freeze%20the%0AWorker%2C%20change%20DNS%2C%20run%20share%20set-public%2C%20or%20delete%20anything%20on%0ACloudflare%3A%20making%20the%20move%20is%20the%20owner%27s%20call.%0AKeep%20the%20tokens%20and%20bucket%20keys%20out%20of%20your%20replies%20and%20out%20of%20Git%3B%0Acelld.json%20holds%20secrets%2C%20so%20make%20sure%20Git%20ignores%20it.%0AIf%20the%20Worker%20uses%20something%20celld%20lacks%2C%20stop%20and%20report%20it%20rather%20than%0Arewriting%20the%20app.%20Before%20exporting%20a%20class%20that%20is%20addressed%20by%20name%2C%0Aask%20the%20owner%20where%20its%20names%20are%20recorded.%0AFinish%20with%20the%20dry%20run%27s%20numbers%20per%20binding%20%28by%20name%2C%20by%20id%20only%29%2C%0Ahttps%3A%2F%2F%3Cvm%3E.exe.xyz%2F%2C%20and%20the%20owner%27s%20steps%20in%20%22Make%20the%20move%22.%0A%0AFinish%20with%20the%20URLs%20I%20should%20open.%0A",
    "terminal": "curl -fsSL https://exe.dev/deploy/migrate-durable-objects/prompt.txt | ssh exe.dev new --prompt=/dev/stdin",
    "prompt": "APP_REPO=\u003cfill-this-in\u003e\nFROM_URL=\u003cfill-this-in\u003e\nCLOUDFLARE_ACCOUNT_ID=\u003cfill-this-in\u003e\nCLOUDFLARE_API_TOKEN=\u003cfill-this-in\u003e\nCELLD_BUCKET=\u003cfill-this-in\u003e\nS3_ENDPOINT=\u003cfill-this-in\u003e\nAWS_REGION=\u003cfill-this-in\u003e\nAWS_ACCESS_KEY_ID=\u003cfill-this-in\u003e\nAWS_SECRET_ACCESS_KEY=\u003cfill-this-in\u003e\n\nSet up Durable Objects from Cloudflare on this VM by following the exe.dev recipe at https://exe.dev/deploy/migrate-durable-objects.md from top to bottom.\n\nThe values at the top of this message are mine:\n- APP_REPO: your Worker's Git repository; for a private GitHub repository, connect the [GitHub integration](/docs/integrations-github) first\n- FROM_URL: where the Worker answers on Cloudflare; its `workers.dev` address is best, because it stays put when your domain moves\n- CLOUDFLARE_ACCOUNT_ID: the account that runs the Worker\n- CLOUDFLARE_API_TOKEN: an API token from the **Edit Cloudflare Workers** template\n- CELLD_BUCKET: an empty bucket for celld, written `s3://name` ([how](/deploy/celld#get-a-bucket))\n- S3_ENDPOINT: R2: `https://\u003cACCOUNT_ID\u003e.r2.cloudflarestorage.com`; Amazon S3: leave empty\n- AWS_REGION: `auto` for R2 and Tigris; the bucket's region for Amazon S3\n- AWS_ACCESS_KEY_ID: a key that can read and write that bucket, and nothing else\n- AWS_SECRET_ACCESS_KEY: the key's secret\nAsk me for any that still say \u003cfill-this-in\u003e before you need them.\n\nThe recipe's example VM name is my-worker. This VM's name is the output of `hostname`: use it wherever the recipe writes my-worker. Skip the step that creates the VM; you are on it.\n\nYou are done when all of these are true. Check each one yourself:\n- Your Worker running on celld at `https://\u003cvm\u003e.exe.xyz/`, private, with its state in your bucket.\n- Every Durable Object with data on Cloudflare exported to `~/migration` and imported into celld.\n- Each imported object checked identical to its export from Cloudflare.\n- A report of what moved, which objects had no name, and anything celld can't run.\n- Cloudflare serving as before, plus a token-protected export route.\n\nRead the whole recipe, including the closed details, and do \"Prepare the\nmove by hand\" in order, skipping VM creation; the walkthrough above it is\nfor the owner. Where\nit names steps of the celld recipe, follow /deploy/celld.md on this site.\nAsk the owner before deploying anything to Cloudflare. Never freeze the\nWorker, change DNS, run share set-public, or delete anything on\nCloudflare: making the move is the owner's call.\nKeep the tokens and bucket keys out of your replies and out of Git;\ncelld.json holds secrets, so make sure Git ignores it.\nIf the Worker uses something celld lacks, stop and report it rather than\nrewriting the app. Before exporting a class that is addressed by name,\nask the owner where its names are recorded.\nFinish with the dry run's numbers per binding (by name, by id only),\nhttps://\u003cvm\u003e.exe.xyz/, and the owner's steps in \"Make the move\".\n\nFinish with the URLs I should open.\n",
    "prompt_url": "https://exe.dev/deploy/migrate-durable-objects/prompt.txt"
  },
  "updated": "2026-10-16",
  "related": [
    "celld",
    "migrate-digitalocean-droplet",
    "n8n",
    "claude-code",
    "openclaw",
    "code-server"
  ],
  "alternatives": [
    "Cloudflare Durable Objects"
  ]
}
