[{"data":1,"prerenderedAt":1215},["ShallowReactive",2],{"page:\u002Fhow-to":3},{"id":4,"title":5,"accent":6,"body":7,"coming":1202,"description":1203,"extension":1204,"headline":1205,"intro":1206,"meta":1207,"navigation":1208,"path":1209,"repos":1210,"seo":1211,"stats":1212,"statsDetail":6,"statsNote":6,"stem":1213,"__hash__":1214},"content\u002Fhow-to.md","How-To",null,{"type":8,"value":9,"toc":1185},"minimark",[10,15,24,27,127,130,134,141,170,176,202,247,251,254,264,267,277,295,326,337,341,363,367,370,377,397,426,432,436,447,460,524,528,543,564,567,585,589,596,613,616,640,650,688,692,748,752,755,761,770,785,794,809,813,819,844,847,899,908,912,918,924,930,950,961,970,975,1000,1009,1014,1023,1038,1042,1048,1052,1065,1107,1123,1127,1181],[11,12,14],"h2",{"id":13},"before-you-start","🧾 Before You Start",[16,17,18,19,23],"p",{},"You need a Cloudflare account and a GitHub account. ",[20,21,22],"pitch",{},"The free Cloudflare plan runs a site",", and moving\nto the $5 Workers Paid plan later is one variable and a redeploy.",[16,25,26],{},"A deploy creates these on your account:",[28,29,30,46],"table",{},[31,32,33],"thead",{},[34,35,36,40,43],"tr",{},[37,38,39],"th",{},"Binding",[37,41,42],{},"Type",[37,44,45],{},"What it holds",[47,48,49,64,76,89,102,114],"tbody",{},[34,50,51,58,61],{},[52,53,54],"td",{},[55,56,57],"code",{},"SITE",[52,59,60],{},"Durable Object",[52,62,63],{},"the site itself: PHP, Drupal and the SQLite database",[34,65,66,71,73],{},[52,67,68],{},[55,69,70],{},"RENDER_LANES",[52,72,60],{},[52,74,75],{},"image style rendering, done at upload time",[34,77,78,83,86],{},[52,79,80],{},[55,81,82],{},"ASSETS",[52,84,85],{},"Workers Assets",[52,87,88],{},"Drupal's core files, the packed database and themes",[34,90,91,96,99],{},[52,92,93],{},[55,94,95],{},"CONFIG_KV",[52,97,98],{},"KV",[52,100,101],{},"the runtime settings you change without a redeploy",[34,103,104,109,111],{},[52,105,106],{},[55,107,108],{},"PAGE_KV",[52,110,98],{},[52,112,113],{},"stored pages shared across Cloudflare locations",[34,115,116,121,124],{},[52,117,118],{},[55,119,120],{},"FLEET_DB",[52,122,123],{},"D1",[52,125,126],{},"an inventory of your sites, useful once you run several",[16,128,129],{},"R2 file storage is optional and left out of the default deploy, because an account has to enable R2\nin the dashboard before a bucket can exist.",[11,131,133],{"id":132},"the-quick-path-with-drangler","🧑‍🚀 The Quick Path with drangler",[16,135,136,137,140],{},"If you are comfortable in a terminal, drangler does every step below for you. Every command here\nruns on your own computer. Install drangler and wrangler, Cloudflare's own CLI, then sign in to\nCloudflare once; ",[55,138,139],{},"wrangler login"," opens a browser to approve access:",[142,143,148],"pre",{"className":144,"code":145,"language":146,"meta":147,"style":147},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","npm i -g @drupflare\u002Fdrangler wrangler\nwrangler login\ndrangler doctor\n","sh","",[55,149,150,158,164],{"__ignoreMap":147},[151,152,155],"span",{"class":153,"line":154},"line",1,[151,156,157],{},"npm i -g @drupflare\u002Fdrangler wrangler\n",[151,159,161],{"class":153,"line":160},2,[151,162,163],{},"wrangler login\n",[151,165,167],{"class":153,"line":166},3,[151,168,169],{},"drangler doctor\n",[16,171,172,175],{},[55,173,174],{},"doctor"," checks your tools and your Cloudflare sign-in, and says how to fix anything missing. Then:",[142,177,179],{"className":144,"code":178,"language":146,"meta":147,"style":147},"drangler dev                                   # a real Drupal site at http:\u002F\u002Flocalhost:8787\ndrangler deploy                                # the same site on your Cloudflare account\ndrangler site claim my-site.example --save     # prints the admin password and owner token once\ndrangler health my-site.example                # ok, or warming for the first few seconds\n",[55,180,181,186,191,196],{"__ignoreMap":147},[151,182,183],{"class":153,"line":154},[151,184,185],{},"drangler dev                                   # a real Drupal site at http:\u002F\u002Flocalhost:8787\n",[151,187,188],{"class":153,"line":160},[151,189,190],{},"drangler deploy                                # the same site on your Cloudflare account\n",[151,192,193],{"class":153,"line":166},[151,194,195],{},"drangler site claim my-site.example --save     # prints the admin password and owner token once\n",[151,197,199],{"class":153,"line":198},4,[151,200,201],{},"drangler health my-site.example                # ok, or warming for the first few seconds\n",[16,203,204,207,208,211,212,214,215,218,219,222,223,226,227,230,231,234,235,238,239,242,243,246],{},[55,205,206],{},"dev"," runs the site on your computer and ",[55,209,210],{},"deploy"," uploads the same site to your Cloudflare account.\n",[55,213,210],{}," prints the site's address, such as ",[55,216,217],{},"https:\u002F\u002Fdrupflare.\u003Cyour-subdomain>.workers.dev","; use it\nwherever this page says ",[55,220,221],{},"my-site.example",".\nBoth share one workspace, so ",[20,224,225],{},"what you tried locally is what you deploy",". The claim\nstores the owner token in your system keychain, so later commands such as ",[55,228,229],{},"drangler modify"," and\n",[55,232,233],{},"drangler site upgrade"," find it; ",[55,236,237],{},"--save"," also writes it to your user config. Add ",[55,240,241],{},"--dry-run"," to any command to see what it would do without doing it, and ",[55,244,245],{},"--json"," when a script\nreads the output. The rest of this page explains each step and the options behind it.",[11,248,250],{"id":249},"_1-deploy","🚀 1. Deploy",[16,252,253],{},"Three routes, and they end in the same place: a site on your own account, with its database and\nits bill yours.",[16,255,256,260,261],{},[257,258,259],"strong",{},"One click."," Press the button and follow Cloudflare's prompts: name the repository and the worker,\nand let it connect to GitHub. The build step downloads the generated Drupal tree and the PHP\ninterpreter, then uploads them as the worker's assets. ",[20,262,263],{},"Nothing is installed locally.",[265,266],"deploy-button",{},[16,268,269,272,273,276],{},[257,270,271],{},"drangler."," ",[55,274,275],{},"drangler deploy",", as in the quick path above.",[16,278,279,282,283,290,291,294],{},[257,280,281],{},"By hand",", for full control or to change the worker itself. You need ",[284,285,289],"a",{"href":286,"rel":287},"https:\u002F\u002Fbun.sh",[288],"nofollow","Bun",",\nNode 24 or newer, and ",[55,292,293],{},"bunx wrangler login",":",[142,296,298],{"className":144,"code":297,"language":146,"meta":147,"style":147},"git clone https:\u002F\u002Fgithub.com\u002Fdrupflare\u002Fworker.git\ncd worker\nbun install\nbun run hydrate   # the generated Drupal tree and the interpreter, which a clone does not carry\nbun run deploy\n",[55,299,300,305,310,315,320],{"__ignoreMap":147},[151,301,302],{"class":153,"line":154},[151,303,304],{},"git clone https:\u002F\u002Fgithub.com\u002Fdrupflare\u002Fworker.git\n",[151,306,307],{"class":153,"line":160},[151,308,309],{},"cd worker\n",[151,311,312],{"class":153,"line":166},[151,313,314],{},"bun install\n",[151,316,317],{"class":153,"line":198},[151,318,319],{},"bun run hydrate   # the generated Drupal tree and the interpreter, which a clone does not carry\n",[151,321,323],{"class":153,"line":322},5,[151,324,325],{},"bun run deploy\n",[16,327,328,331,332,336],{},[55,329,330],{},"bun run dev"," serves the same tree on localhost first if you want to look before deploying. To move\na site you already have instead, start at ",[284,333,335],{"href":334},"\u002Fmigrate","Migrate",".",[11,338,340],{"id":339},"_2-wait-for-the-first-boot","⏳ 2. Wait for the First Boot",[16,342,343,344,347,348,351,352,355,356,359,360,336],{},"A new site is an empty Durable Object until someone asks it for a page. The first visit starts\nunpacking the database and shows a page that refreshes itself; ",[20,345,346],{},"a real Drupal page answers within seconds of the deploy",". A ",[55,349,350],{},"503"," with the body ",[55,353,354],{},"migrating"," in that window means the site is\nstarting, not that the deploy failed. The ",[55,357,358],{},"x-cfw-migrate"," response header shows progress as\n",[55,361,362],{},"\u003Cchunk>\u002F\u003Cchunks>",[11,364,366],{"id":365},"_3-claim-the-site","🔑 3. Claim the Site",[16,368,369],{},"The packed database is already installed, so Drupal's installer never runs. Claiming the site is\nwhat sets the administrator account. Open the site in a browser and the claim page asks for a site\nname, an administrator email and, optionally, a password.",[16,371,372,373,376],{},"From a terminal instead, ",[55,374,375],{},"drangler site claim my-site.example --save"," does it, or with plain HTTP:",[142,378,380],{"className":144,"code":379,"language":146,"meta":147,"style":147},"curl -X POST \"https:\u002F\u002Fmy-site.example\u002Ffirstrun\" \\\n  -H 'content-type: application\u002Fjson' \\\n  -d '{\"siteName\":\"My Site\",\"adminMail\":\"you@example.com\"}'\n",[55,381,382,387,392],{"__ignoreMap":147},[151,383,384],{"class":153,"line":154},[151,385,386],{},"curl -X POST \"https:\u002F\u002Fmy-site.example\u002Ffirstrun\" \\\n",[151,388,389],{"class":153,"line":160},[151,390,391],{},"  -H 'content-type: application\u002Fjson' \\\n",[151,393,394],{"class":153,"line":166},[151,395,396],{},"  -d '{\"siteName\":\"My Site\",\"adminMail\":\"you@example.com\"}'\n",[16,398,399,400,403,404,407,408,411,412,415,416,419,420,403,423,336],{},"The response carries ",[55,401,402],{},"adminPass"," and ",[55,405,406],{},"ownerToken",". Each is shown once. Save both before closing the tab; a lost owner token can be recovered with ",[55,409,410],{},"drangler recover-token"," (see below), a lost password through Drupal's reset. Pass ",[55,413,414],{},"\"adminPass\""," in the body to choose your own\npassword. The body also accepts ",[55,417,418],{},"siteMail",", ",[55,421,422],{},"adminName",[55,424,425],{},"timezone",[427,428,429],"tip",{},[16,430,431],{},"Claim from the domain you intend to keep. Claiming pins the address Drupal builds every absolute URL\nagainst: canonical tags, redirects and password-reset links.",[11,433,435],{"id":434},"_4-log-in","👤 4. Log In",[16,437,438,439,442,443,446],{},"Log in at ",[55,440,441],{},"\u002Fuser\u002Flogin"," as ",[55,444,445],{},"admin"," with the password from the claim. That account is the only one\nthat reaches Drupal's administration pages.",[16,448,449,452,453,456,457,294],{},[20,450,451],{},"The owner token is a different credential."," It signs you in to the Drupflare pages at ",[55,454,455],{},"\u002F_cfw\u002Flogin",",\nand it authorises these routes when sent as ",[55,458,459],{},"Authorization: Bearer \u003CownerToken>",[28,461,462,472],{},[31,463,464],{},[34,465,466,469],{},[37,467,468],{},"Route",[37,470,471],{},"What it does",[47,473,474,484,494,504,514],{},[34,475,476,481],{},[52,477,478],{},[55,479,480],{},"\u002Fexport",[52,482,483],{},"downloads the whole site database",[34,485,486,491],{},[52,487,488],{},[55,489,490],{},"\u002Fhealth",[52,492,493],{},"the health ledger, repair state and budget forecasts",[34,495,496,501],{},[52,497,498],{},[55,499,500],{},"\u002Fsettings",[52,502,503],{},"reads and changes the runtime levers",[34,505,506,511],{},[52,507,508],{},[55,509,510],{},"\u002Fsetup\u002Fmail",[52,512,513],{},"sets up a sending domain for outgoing mail",[34,515,516,521],{},[52,517,518],{},[55,519,520],{},"\u002Fsetup\u002Foidc",[52,522,523],{},"sets the OpenID Connect issuer and client id",[11,525,527],{"id":526},"_5-use-your-own-domain","🌐 5. Use Your Own Domain",[142,529,531],{"className":144,"code":530,"language":146,"meta":147,"style":147},"drangler domain add www.example.org https:\u002F\u002Fwww.example.org\ndrangler deploy\n",[55,532,533,538],{"__ignoreMap":147},[151,534,535],{"class":153,"line":154},[151,536,537],{},"drangler domain add www.example.org https:\u002F\u002Fwww.example.org\n",[151,539,540],{"class":153,"line":160},[151,541,542],{},"drangler deploy\n",[16,544,545,548,549,552,553,556,557,419,560,563],{},[55,546,547],{},"domain add"," maps the hostname to your site and adds it as a Custom Domain in ",[55,550,551],{},"wrangler.jsonc",". When\nthe domain's DNS is on Cloudflare in the same account, ",[20,554,555],{},"the deploy creates the DNS record and\nthe certificate",". A hostname that already has a DNS record is refused until you delete that record.\nThe dashboard's ",[257,558,559],{},"Settings",[257,561,562],{},"Domains & Routes"," page does the same for the route alone.",[16,565,566],{},"Add as many hostnames as the site answers on. Each one serves the same site, and links, redirects\nand logins stay on the hostname the visitor used.",[16,568,569,570,573,574,577,578,581,582,584],{},"If you claimed the site on the ",[55,571,572],{},"workers.dev"," address first, set the ",[55,575,576],{},"SITE_ORIGIN"," variable to your\nmain domain, for example ",[55,579,580],{},"https:\u002F\u002Fwww.example.org",", and redeploy. ",[55,583,576],{}," always wins over the\npinned address.",[11,586,588],{"id":587},"_6-choose-free-or-paid","💳 6. Choose Free or Paid",[16,590,591,592,595],{},"On the free plan, one Cloudflare account serves ",[20,593,594],{},"3.04 million views a month"," across all its\nsites, with a daily budget for re-rendering pages after content changes that comfortably covers\nsmall and medium sites. Quotas are shared across the account and reset at midnight UTC. With an R2\nbucket and a custom domain you own, the page mirror lifts that to a derived 13.2 million views a\nmonth. The mirror also caps the re-render budget at 33,333 pages a day, down from 50,916. A site that\nre-renders fewer pages than that is unaffected. Both figures count one Worker request\na view. Static assets do not bill as Worker requests. The one exception is Workers Cache, which the shipped config does not enable; with it\non, a free account serves about 1,500 views a day.",[16,597,598,599,602,603,606,607,609,610,612],{},"The $5 Workers Paid plan raises those limits and keeps sites warm, so visitors rarely wait for a\ncold start. After upgrading, set ",[55,600,601],{},"PLAN"," to ",[55,604,605],{},"paid"," in ",[55,608,551],{}," and redeploy. ",[55,611,601],{}," is not\nchangeable at runtime on purpose, because it selects the whole limits profile.",[16,614,615],{},"Warming is a runtime setting you can change without a redeploy:",[142,617,619],{"className":144,"code":618,"language":146,"meta":147,"style":147},"curl -X PUT \"https:\u002F\u002Fmy-site.example\u002Fsettings\" \\\n  -H \"Authorization: Bearer $OWNER_TOKEN\" \\\n  -H 'content-type: application\u002Fjson' \\\n  -d '{\"SITE_WARM\":\"1\",\"WARM_INTERVAL_MS\":\"30000\"}'\n",[55,620,621,626,631,635],{"__ignoreMap":147},[151,622,623],{"class":153,"line":154},[151,624,625],{},"curl -X PUT \"https:\u002F\u002Fmy-site.example\u002Fsettings\" \\\n",[151,627,628],{"class":153,"line":160},[151,629,630],{},"  -H \"Authorization: Bearer $OWNER_TOKEN\" \\\n",[151,632,633],{"class":153,"line":166},[151,634,391],{},[151,636,637],{"class":153,"line":198},[151,638,639],{},"  -d '{\"SITE_WARM\":\"1\",\"WARM_INTERVAL_MS\":\"30000\"}'\n",[16,641,642,643,646,647,649],{},"A ",[55,644,645],{},"GET"," on ",[55,648,500],{}," lists every lever with the value in force and where it came from.",[16,651,652,653,602,656,659,660,606,663,665,666,669,670,673,674,677,678,681,682,687],{},"Spool, the small service worker that answers repeat views on the visitor's own device, follows the\nplan: on for paid sites, off for free ones. Set ",[55,654,655],{},"SPOOL",[55,657,658],{},"1"," or ",[55,661,662],{},"0",[55,664,551],{}," to\noverride it. Three more levers, all on while spool is on, turn off its page changes one by one:\n",[55,667,668],{},"SPOOL_IMAGES"," converts images to WebP for browsers that accept it, ",[55,671,672],{},"SPOOL_VIDEO"," adds\n",[55,675,676],{},"preload=\"none\""," to video tags, and ",[55,679,680],{},"SPOOL_EMBEDS"," swaps YouTube and Vimeo players for a facade\nuntil someone clicks. Each new visitor costs the Worker about two extra requests, so a site whose\nvisitors usually leave after one page can end up with more requests, not fewer. The\n",[284,683,686],{"href":684,"rel":685},"https:\u002F\u002Fdocs.drupflare.com\u002Fimpact\u002Fenergy\u002Fviews.html#what-spool-adds",[288],"spool energy report"," has the measurements and the break-even.",[11,689,691],{"id":690},"️-7-mail-sign-on-and-redis","✉️ 7. Mail, Sign-On and Redis",[693,694,695,708,727],"ul",{},[696,697,698,272,701,703,704,707],"li",{},[257,699,700],{},"Mail:",[55,702,510],{}," onboards a sending domain. The ",[55,705,706],{},"smtp"," module is verified if you already\nhave a mail provider.",[696,709,710,272,713,715,716,719,720,403,723,726],{},[257,711,712],{},"Single sign-on:",[55,714,520],{}," sets the OpenID Connect issuer and client id. The\n",[55,717,718],{},"openid_connect"," module is verified enabled on 8.x-1.6 and on 3.0.0-alpha9. A login through its\nown client against a Keycloak test server was driven on the 3.x branch, and the 8.x-1.6 login\nhas not been driven. The ",[55,721,722],{},"cas",[55,724,725],{},"samlauth"," modules are verified on the services, routes and libraries they\ndeclare. No login against a real CAS or SAML provider has been driven.",[696,728,729,732,733,736,737,659,740,743,744,747],{},[257,730,731],{},"Redis:"," store the connection string as a secret with ",[55,734,735],{},"bunx wrangler secret put REDIS_URL",", as\n",[55,738,739],{},"redis:\u002F\u002Fuser:pass@host:6379\u002F0",[55,741,742],{},"rediss:\u002F\u002F"," for TLS. ",[20,745,746],{},"The site's own SQLite is still the faster cache","; Redis is there for setups that already depend on it.",[11,749,751],{"id":750},"_8-install-modules","🧩 8. Install Modules",[16,753,754],{},"There are three ways in, depending on where the module comes from.",[16,756,757,760],{},[257,758,759],{},"From drupal.org or Packagist",", with drangler. It checks the package against the shipped Drupal\nversions, installs it and its dependencies, and turns it on:",[142,762,764],{"className":144,"code":763,"language":146,"meta":147,"style":147},"drangler modify require drupal\u002Fpathauto drupal\u002Fmetatag --enable\n",[55,765,766],{"__ignoreMap":147},[151,767,768],{"class":153,"line":154},[151,769,763],{},[16,771,772,773,776,777,780,781,784],{},"The same steps are plain HTTP routes if you would rather script them with the owner token:\n",[55,774,775],{},"\u002Finstallable?module=pathauto"," checks, ",[55,778,779],{},"\u002Finstall?module=pathauto"," installs, and\n",[55,782,783],{},"\u002Fenable?module=pathauto"," turns it on through Drupal's own installer. Installing and enabling are\nseparate on purpose, because a package whose files have landed is not yet a module Drupal knows.",[16,786,787,790,791,793],{},[257,788,789],{},"A module you wrote"," arrives through ",[55,792,229],{}," (next section) or a git remote.",[16,795,796,799,800,804,805,808],{},[257,797,798],{},"Before you pick a module",", the ",[284,801,803],{"href":802},"\u002Ffixtures","fixtures page"," lists every module that has been\n",[20,806,807],{},"verified on a real Drupflare site"," and says exactly how each one was checked.",[11,810,812],{"id":811},"_9-contribute-code","🧑‍💻 9. Contribute Code",[16,814,815,816,336],{},"drangler develops a custom module or theme against a real site, locally or deployed, one revision at\na time. Every upload is checked by booting Drupal before it goes live, so ",[20,817,818],{},"a module that would fatal is rolled back automatically",[142,820,822],{"className":144,"code":821,"language":146,"meta":147,"style":147},"cd ~\u002Fwork\u002Fmy_module\ndrangler modify init --site https:\u002F\u002Fmy-site.example\ndrangler modify check --php php\ndrangler modify upload --message \"add the event listing\"\n",[55,823,824,829,834,839],{"__ignoreMap":147},[151,825,826],{"class":153,"line":154},[151,827,828],{},"cd ~\u002Fwork\u002Fmy_module\n",[151,830,831],{"class":153,"line":160},[151,832,833],{},"drangler modify init --site https:\u002F\u002Fmy-site.example\n",[151,835,836],{"class":153,"line":166},[151,837,838],{},"drangler modify check --php php\n",[151,840,841],{"class":153,"line":198},[151,842,843],{},"drangler modify upload --message \"add the event listing\"\n",[16,845,846],{},"The common cases:",[693,848,849,858,867,880,893],{},[696,850,851,272,854,857],{},[257,852,853],{},"Iterate locally:",[55,855,856],{},"drangler dev --modify ."," runs a local Drupal site with your module mounted\nand re-uploads on every change.",[696,859,860,272,863,866],{},[257,861,862],{},"Ship a release:",[55,864,865],{},"drangler modify release --tag v1.2.0"," uploads from a tagged commit and refuses\na dirty working tree.",[696,868,869,272,872,875,876,879],{},[257,870,871],{},"Undo a bad change:",[55,873,874],{},"drangler modify rollback --yes"," puts the previous revision back instantly.\nThe last five revisions are kept, and ",[55,877,878],{},"drangler modify revisions"," lists them.",[696,881,882,272,885,888,889,892],{},[257,883,884],{},"Review before sending:",[55,886,887],{},"drangler modify diff"," shows which files would change, and\n",[55,890,891],{},"drangler modify status"," compares the site with your disk.",[696,894,895,898],{},[257,896,897],{},"Deploy from git:"," a site can pull a module straight from a git remote, at a branch or a commit,\nwith the same boot check and rollback.",[16,900,901,902,907],{},"To contribute to Drupflare itself, every piece is on ",[284,903,906],{"href":904,"rel":905},"https:\u002F\u002Fgithub.com\u002Fdrupflare",[288],"GitHub",", with\nits own tests. The worker, the Drupal module, the database driver and drangler each take pull\nrequests.",[11,909,911],{"id":910},"️-10-maintain-the-site","🛠️ 10. Maintain the Site",[16,913,914,917],{},[20,915,916],{},"Most upkeep happens on its own."," What is left for you is short.",[16,919,920,923],{},[257,921,922],{},"What runs by itself:"," Drupal's cron runs on a schedule inside the site. Pages that change are\nre-rendered in the background, so visitors keep getting a stored copy. When a new release fixes\nsomething inside the packed Drupal, existing sites apply it through reconciliation, one step at a\ntime, checking each step before and after.",[16,925,926,929],{},[257,927,928],{},"Updating Drupflare."," The deploy button created a copy of the worker, so add the original as a\nremote once and merge releases from it:",[142,931,933],{"className":144,"code":932,"language":146,"meta":147,"style":147},"git remote add template https:\u002F\u002Fgithub.com\u002Fdrupflare\u002Fworker\ngit fetch --all\ngit merge template --allow-unrelated-histories -m \"chore: merge upstream\"\n",[55,934,935,940,945],{"__ignoreMap":147},[151,936,937],{"class":153,"line":154},[151,938,939],{},"git remote add template https:\u002F\u002Fgithub.com\u002Fdrupflare\u002Fworker\n",[151,941,942],{"class":153,"line":160},[151,943,944],{},"git fetch --all\n",[151,946,947],{"class":153,"line":166},[151,948,949],{},"git merge template --allow-unrelated-histories -m \"chore: merge upstream\"\n",[16,951,952,953,956,957,960],{},"Pushing the merge deploys it when the repository is connected to Cloudflare. ",[55,954,955],{},"drangler update my-site"," updates the checkout and deploys in one step, and ",[55,958,959],{},"drangler site upgrade my-site"," deploys,\nwaits for the site to pick up the new release, and then runs Drupal's database updates.",[16,962,963,272,966,969],{},[257,964,965],{},"Database updates on their own:",[55,967,968],{},"drangler site updb my-site"," shows Drupal's update chain and runs\nit one step at a time. Running several steps at once asks you to name a snapshot directory first,\nand a run that halts waits for you to roll it back or accept it rather than retrying on its own.",[16,971,972],{},[257,973,974],{},"Checking on it:",[142,976,978],{"className":144,"code":977,"language":146,"meta":147,"style":147},"drangler status my-site.example     # what is deployed, and whether it is claimed\ndrangler health my-site.example     # is it serving, and from which cache tier\ndrangler reconcile my-site.example  # what the site still owes the current release\ndrangler heal my-site.example       # any repairs the site is holding, and why\n",[55,979,980,985,990,995],{"__ignoreMap":147},[151,981,982],{"class":153,"line":154},[151,983,984],{},"drangler status my-site.example     # what is deployed, and whether it is claimed\n",[151,986,987],{"class":153,"line":160},[151,988,989],{},"drangler health my-site.example     # is it serving, and from which cache tier\n",[151,991,992],{"class":153,"line":166},[151,993,994],{},"drangler reconcile my-site.example  # what the site still owes the current release\n",[151,996,997],{"class":153,"line":198},[151,998,999],{},"drangler heal my-site.example       # any repairs the site is holding, and why\n",[16,1001,1002,272,1005,1008],{},[257,1003,1004],{},"Clearing caches:",[55,1006,1007],{},"drangler site invalidate"," purges stored pages by cache tag, or retires them all\nat once, when you need a change to show everywhere immediately.",[16,1010,1011],{},[257,1012,1013],{},"Backups, and leaving whenever you like:",[142,1015,1017],{"className":144,"code":1016,"language":146,"meta":147,"style":147},"curl -H \"Authorization: Bearer $OWNER_TOKEN\" \"https:\u002F\u002Fmy-site.example\u002Fexport\" -o site.sqlite\n",[55,1018,1019],{"__ignoreMap":147},[151,1020,1021],{"class":153,"line":154},[151,1022,1016],{},[16,1024,1025,1026,1029,1030,1033,1034,1037],{},"The dump is the whole site database. It withholds the owner token and the hash salt unless you add\n",[55,1027,1028],{},"?secrets=1",", which a full restore needs. ",[55,1031,1032],{},"drangler migrate export"," does the same from the command line,\nand ",[55,1035,1036],{},"drangler migrate convert"," turns the dump into MySQL for a conventional host.",[11,1039,1041],{"id":1040},"bring-your-own-site","🧳 Bring Your Own Site",[16,1043,1044,1045,1047],{},"An existing Drupal site moves in without a rebuild. The ",[284,1046,335],{"href":334}," page covers the whole\npath, from a read-only survey of the old server to the database, the code and the cutover, with a\nworked example you can reproduce.",[11,1049,1051],{"id":1050},"on-your-own-servers","🏰 On Your Own Servers",[16,1053,1054,1055,1060,1061,1064],{},"When a site has to stay on hardware you control, ",[284,1056,1059],{"href":1057,"rel":1058},"https:\u002F\u002Fgithub.com\u002Fdrupflare\u002Fbastion",[288],"bastion","\nruns ",[20,1062,1063],{},"the same release on Linux as one binary",", also published as a container image. It provides what\nCloudflare otherwise would: TLS, per-tenant CPU and memory limits, storage behind every binding,\nbackups and metrics.",[142,1066,1068],{"className":144,"code":1067,"language":146,"meta":147,"style":147},"curl -fsSL https:\u002F\u002Fgithub.com\u002Fdrupflare\u002Fbastion\u002Freleases\u002Flatest\u002Fdownload\u002Fbastion-linux-x64 -o bastion\ninstall -m 0755 bastion \u002Fusr\u002Flocal\u002Fbin\u002Fbastion\nbastion init\nbastion doctor\nbastion tenant add acme --cpu 2 --memory 4Gi\nbastion site add www.example.edu --tenant acme --bundle .\u002Fpayload.tar.gz\nbastion up\n",[55,1069,1070,1075,1080,1085,1090,1095,1101],{"__ignoreMap":147},[151,1071,1072],{"class":153,"line":154},[151,1073,1074],{},"curl -fsSL https:\u002F\u002Fgithub.com\u002Fdrupflare\u002Fbastion\u002Freleases\u002Flatest\u002Fdownload\u002Fbastion-linux-x64 -o bastion\n",[151,1076,1077],{"class":153,"line":160},[151,1078,1079],{},"install -m 0755 bastion \u002Fusr\u002Flocal\u002Fbin\u002Fbastion\n",[151,1081,1082],{"class":153,"line":166},[151,1083,1084],{},"bastion init\n",[151,1086,1087],{"class":153,"line":198},[151,1088,1089],{},"bastion doctor\n",[151,1091,1092],{"class":153,"line":322},[151,1093,1094],{},"bastion tenant add acme --cpu 2 --memory 4Gi\n",[151,1096,1098],{"class":153,"line":1097},6,[151,1099,1100],{},"bastion site add www.example.edu --tenant acme --bundle .\u002Fpayload.tar.gz\n",[151,1102,1104],{"class":153,"line":1103},7,[151,1105,1106],{},"bastion up\n",[16,1108,1109,1112,1113,1116,1117,1122],{},[55,1110,1111],{},"bastion doctor"," reports which isolation modes the host supports before anything is configured.\n",[55,1114,1115],{},"payload.tar.gz"," is the release artifact published with every Drupflare version, and bastion's own\ntest suite serves it unmodified. The\n",[284,1118,1121],{"href":1119,"rel":1120},"https:\u002F\u002Fgithub.com\u002Fdrupflare\u002Fbastion#readme",[288],"bastion README"," covers tenants, clustering and\nbackups.",[11,1124,1126],{"id":1125},"when-something-looks-wrong","🩺 When Something Looks Wrong",[693,1128,1129,1140,1146,1152,1158,1168],{},[696,1130,1131,1136,1137],{},[257,1132,1133],{},[55,1134,1135],{},"503 migrating"," right after a deploy is the first boot. ",[20,1138,1139],{},"Wait a few seconds.",[696,1141,1142,1145],{},[257,1143,1144],{},"\"Try Again in a Moment\", a 500 or a 1101 during the first minutes of admin work"," on a new or\njust-updated site: the site restarted under memory pressure. Reload the page. Public cached pages\nare not affected.",[696,1147,1148,1151],{},[257,1149,1150],{},"The site went read-only"," on the free plan: the account spent its daily write budget. It resets\nat midnight UTC; the paid plan raises it.",[696,1153,1154,1157],{},[257,1155,1156],{},"Lost the admin password:"," use Drupal's password reset, which needs mail set up.",[696,1159,1160,1163,1164,1167],{},[257,1161,1162],{},"Lost the owner token:"," run ",[55,1165,1166],{},"drangler recover-token my-site.example --store",". It proves you can\nwrite to the site's Cloudflare account through your own wrangler login, gets the token back and\nsaves it to your keychain. It does not rotate the token or touch the admin password.",[696,1169,1170,272,1173,1176,1177,1180],{},[257,1171,1172],{},"Not sure what is running:",[55,1174,1175],{},"drangler status my-site.example"," reads the deployed version and\nclaim state from one public request, and ",[55,1178,1179],{},"drangler health"," says which cache tier answered.",[1182,1183,1184],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":147,"searchDepth":160,"depth":160,"links":1186},[1187,1188,1189,1190,1191,1192,1193,1194,1195,1196,1197,1198,1199,1200,1201],{"id":13,"depth":160,"text":14},{"id":132,"depth":160,"text":133},{"id":249,"depth":160,"text":250},{"id":339,"depth":160,"text":340},{"id":365,"depth":160,"text":366},{"id":434,"depth":160,"text":435},{"id":526,"depth":160,"text":527},{"id":587,"depth":160,"text":588},{"id":690,"depth":160,"text":691},{"id":750,"depth":160,"text":751},{"id":811,"depth":160,"text":812},{"id":910,"depth":160,"text":911},{"id":1040,"depth":160,"text":1041},{"id":1050,"depth":160,"text":1051},{"id":1125,"depth":160,"text":1126},[],"Deploy Drupflare to your own Cloudflare account, claim it, and run it, from one click to the command line.","md","The Setup, Step by Step","Everything from an empty Cloudflare account to a Drupal site on your own domain, with modules, mail and updates. Each step names what it creates and what to save.",{},true,"\u002Fhow-to",[],{"title":5,"description":1203},[],"how-to","HC-HuKwAT5GWQJyWpNk1riQsG1XbjRkAL97wRnCMbLc",1793683736804]