<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Flared Up]]></title><description><![CDATA[Covering all the latest news around Cloudflare's developer platform, alongside deep dives into products & services, helpful tips and tutorials.]]></description><link>https://blog.ashleypeacock.co.uk</link><image><url>https://substackcdn.com/image/fetch/$s_!GGdd!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6a3e112-1b9a-4fbd-b70c-b2be1aa61070_256x256.png</url><title>Flared Up</title><link>https://blog.ashleypeacock.co.uk</link></image><generator>Substack</generator><lastBuildDate>Sat, 16 May 2026 18:29:14 GMT</lastBuildDate><atom:link href="https://blog.ashleypeacock.co.uk/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Ashley Peacock]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[flaredup@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[flaredup@substack.com]]></itunes:email><itunes:name><![CDATA[Ashley Peacock]]></itunes:name></itunes:owner><itunes:author><![CDATA[Ashley Peacock]]></itunes:author><googleplay:owner><![CDATA[flaredup@substack.com]]></googleplay:owner><googleplay:email><![CDATA[flaredup@substack.com]]></googleplay:email><googleplay:author><![CDATA[Ashley Peacock]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Cloudflare Agents Week: The 6-Minute Recap]]></title><description><![CDATA[Every product launch, every new feature, distilled into digestible chunks]]></description><link>https://blog.ashleypeacock.co.uk/p/cloudflare-agents-week-the-6-minute</link><guid isPermaLink="false">https://blog.ashleypeacock.co.uk/p/cloudflare-agents-week-the-6-minute</guid><dc:creator><![CDATA[Ashley Peacock]]></dc:creator><pubDate>Tue, 21 Apr 2026 14:26:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!yYcJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5d671ee-6ec9-433f-944e-93a64b89503d_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yYcJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5d671ee-6ec9-433f-944e-93a64b89503d_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yYcJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5d671ee-6ec9-433f-944e-93a64b89503d_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!yYcJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5d671ee-6ec9-433f-944e-93a64b89503d_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!yYcJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5d671ee-6ec9-433f-944e-93a64b89503d_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!yYcJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5d671ee-6ec9-433f-944e-93a64b89503d_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yYcJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5d671ee-6ec9-433f-944e-93a64b89503d_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a5d671ee-6ec9-433f-944e-93a64b89503d_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1710150,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://blog.ashleypeacock.co.uk/i/194922150?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5d671ee-6ec9-433f-944e-93a64b89503d_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!yYcJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5d671ee-6ec9-433f-944e-93a64b89503d_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!yYcJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5d671ee-6ec9-433f-944e-93a64b89503d_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!yYcJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5d671ee-6ec9-433f-944e-93a64b89503d_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!yYcJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5d671ee-6ec9-433f-944e-93a64b89503d_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Last week was Cloudflare&#8217;s <strong>Agents Week</strong>, a marathon of announcements focused on the Agentic Era. I covered the news daily, but there was a lot to track. I&#8217;ve condensed those 5,500 words across the week into this 6-minute guide. Let&#8217;s dive in.</p><h2><strong>&#127959;&#65039; The Agentic Backbone</strong></h2><p><strong>Durable Object Facets</strong><br>Durable Object Facets allow any Durable Object to create child Durable Objects on-the-fly using Dynamic Workers, with each Facet having its own isolated storage.</p><p>This allows novel use cases like AI agents writing their own tools that can store data related to that tool&#8217;s use.</p><p><strong>Project Think<br></strong>Project Think provides a new base class in Cloudflare&#8217;s Agents SDK that gives you a highly opinionated agent harness to use out of the box. It implements the agentic loop, message persistence, streaming, tool execution, stream resumption, and extensions.</p><p>You get all of this with 3 lines of code with customization possible.</p><p>There&#8217;s several new primitives in the Agents SDK too:</p><ul><li><p><strong>Fibers</strong> allow you to recover from failure for long-running tasks that may take hours or even days.</p></li><li><p><strong>Subagents</strong> using Durable Objects Facets.</p></li><li><p><strong>Sessions</strong> change how conversations are stored from a flat list to a tree structure with full-text search across conversation history via FTS5.</p></li><li><p><strong>ExtensionManager</strong> allows an AI agent to self-author its own extensions. Define the extensions and the agent will write its own tools, which will be executed in a Dynamic Worker.</p></li></ul><p><strong>Voice Agents<br></strong>Agents SDK now has a VoiceAgent class, which handles speech-to-text (STT), text-to-speech (TTS), conversation persistence and support for React as well as a framework-agnostic client.</p><p>It includes everything you&#8217;d expect from a voice agent, such as real-time audio, persistence for audio, output audio streaming, utterance detection and hooks to intercept and transform text during a conversation.</p><p><strong>Agent Memory<br></strong>Agent Memory from Cloudflare will be a fully managed solution to give AI agents long term memory, with an opinionated approach and API.</p><p>Agent Memory will then handle:</p><ul><li><p>Ingestion of messages that belong to a conversation</p></li><li><p>Classification of memories (facts, events, instructions or tasks)</p></li><li><p>Explicit storage of key information</p></li><li><p>Recall, with Agent Memory determining the most useful results</p></li></ul><p><strong>Containers &amp; Sandboxes are Generally Available<br></strong>Dynamic Workers are incredibly lightweight, with start up times in single-digit milliseconds. They don&#8217;t work for all sandboxing use cases though, and that&#8217;s where Sandboxes and Containers come in.</p><p>They have a ton of features, such as PTY support, filesystem watching, snapshots and persistent code interpreters. Additionally, you can keep your secrets from ever being visible to your AI agent by using Outbound Workers in conjunction with a sandbox, which acts as an egress proxy.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.ashleypeacock.co.uk/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Flared Up! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2><strong>&#128737;&#65039; AI Agent Connectivity &amp; Security</strong></h2><p><strong>CodeMode for MCP Server Portals<br></strong>Code Mode is an approach to MCP that replaces every tool in an MCP server with just two: search and execute. The client then searches for what endpoints it needs, and writes typed code against those endpoints, which it can then execute in a sandbox safely.</p><p>Code Mode is now available with Cloudflare&#8217;s MCP Server Portals, significantly reducing the cost when integrating with multiple MCP servers.</p><p><strong>A Unified AI Gateway<br></strong>You can now call pretty much any LLM provider using the same Workers AI binding that natively-hosted Cloudflare models use - without needing an API key for individual providers.</p><p>Cloudflare will bill you for the usage directly. There are 70+ models across 12+ providers available right now from all the names you&#8217;d expect.</p><p>Additionally, in the future, you&#8217;ll be able to run AI models directly on Cloudflare that you have built using Replicate&#8217;s Cog technology.</p><p><strong>Cloudflare Mesh<br></strong>Imagine you have a bunch of devices, some are servers in data centers, plus your laptops and phones. You want all of them to communicate with each other privately and securely, like they&#8217;re all on the same home Wi-Fi network, even though they&#8217;re scattered across the world.</p><p>That&#8217;s what Cloudflare Mesh does - no VPN required. For an extra layer of security, everything is post-quantum encrypted.</p><p>With the introduction of Mesh comes support for Workers VPC bindings. You can connect any Worker or Durable Object to a Mesh node via the binding using its IP address, or to a Cloudflare Tunnel with a tunnel ID.</p><p><strong>Managed OAuth for Cloudflare Access<br></strong>Cloudflare Access is a product from Cloudflare that allows you to control who can access your internal company applications and services.</p><p>Managed OAuth is built for AI agent auth. Enable it on any Access app, and Cloudflare Access becomes an OAuth authorization server. Agents can automatically discover how to authenticate, send the human through a PKCE flow, then the AI agent receives a scoped token to make requests on the user&#8217;s behalf.</p><h2><strong>&#128736;&#65039; Agent Toolbox &amp; Developer Platform</strong></h2><p><strong>Artifacts: Git-compatible Versioned Storage<br></strong>Artifacts is<strong> </strong>a brand new product from Cloudflare that allows you to create an unlimited number of repos to store your code, versioned and Git-compatible.</p><p>You can create, fork and import a repo, interact with Artifacts via binding or API and connect to a repo using a Git client.</p><p><strong>Email Service Public Beta<br></strong>Email Sending is now available to send transactional emails, both via a new binding as well as REST API.</p><p>Setup is easy, you just onboard a domain in the dashboard, add the email binding, then you can send emails with a single line of code. Keep in mind that only transactional use cases are allowed.</p><p><strong>Domain Registrar API Beta + MCP<br></strong>The Registrar API makes it possible to search for domains, check availability, and register them programmatically. There&#8217;s three endpoints at launch:</p><ul><li><p>Search, to find domain names with or without an extension</p></li><li><p>Check, to determine availability and pricing for a search result</p></li><li><p>Register, to actually register a domain as yours</p></li></ul><p>Domain Registrar is also available via the Cloudflare MCP server.</p><p><strong>Flagship<br></strong>Feature flags are now available on Cloudflare, underpinned by OpenFeature. It supports all you&#8217;d expect from a feature flagging solution, and it&#8217;s in private beta currently.</p><p><strong>PlanetScale &lt;&gt; Workers Integration<br></strong>Users can now create and manage PlanetScale Postgres and MySQL databases directly through the Cloudflare dashboard and API.</p><p>Starting next month, billing will be unified, allowing self-serve and enterprise customers to pay for their databases via their Cloudflare account.</p><p><strong>Live View + Chrome DevTools Protocol for Browser <s>Rendering</s> Run<br></strong>Browser Rendering is now called Browser Run, and received a bunch of updates:</p><ul><li><p>Live View allows a human to jump into a browser session to help an AI agent out.</p></li><li><p>Browser Run now supports Chrome DevTools Protocol (CDP), a remote debugging protocol that allows you to instrument, inspect, debug, and profile Chromium-based browsers.</p></li><li><p>MCP client support, such as Claude Desktop, Cursor, and OpenCode.</p></li><li><p>WebMCP support, enabling websites to declare what actions are available for agents</p></li><li><p>Session recordings to aid debugging, including DOM changes, user interactions, and page navigation.</p></li><li><p>Limit increases to allow you to run up to 120 concurrent browsers</p></li></ul><p><strong>AI Search Improvements<br></strong>AI Search allows you to create knowledge bases for your AI agents to use.</p><p>You can now ingest data directly using the new Items API. There&#8217;s also a new ai_search_namespaces binding that allows you to create, modify and delete AI search indexes programmatically. </p><p>New on the search side, Hybrid search combines vector search with BM25 keyword search. Relevance boosting allows you to modify how a search is handled based on document metadata.</p><p><strong>Workflows Capacity Increases<br></strong>The limits associated with Workflows have increased massively since it was first introduced at the end of 2024, and they are going even higher now:</p><ul><li><p>Max concurrent instances raised to 50,000 from 10,000</p></li><li><p>Instance creation rate increased to 300/second per account &amp; 100/second per workflow, up from 100/second per account</p></li><li><p>Ability to queue 2 million instances per workflow, up from 1 million</p></li></ul><p><strong>Agent Lee<br></strong>The Cloudflare Dashboard now has an AI assistant for everyone to use that can undertake tasks and answer any questions you have.</p><p>For example, if you&#8217;re like me and worry each time you make a DNS change, you can ask Agent Lee to sense-check the DNS change and even action it directly from the chat.</p><h2><strong>&#127758; Improving the Web for Agents</strong></h2><p><strong>Agent Readiness Checker<br></strong>Cloudflare has introduced a new website to check if your website is well suited for agents to discover and use.</p><p>Simply enter your domain and it will be assessed across 4 key areas, and present you with a final score - think Lighthouse for AI agents.</p><p><strong>Shared Dictionaries<br></strong>Over time, web pages have grown in size - roughly 6-9% per year. With agents arriving on the scene, the amount of web traffic is growing at an astonishing rate, making this bloat slow everything down for everyone.</p><p>A compression dictionary is a shared reference between server and client that works like a cheat sheet. Instead of compressing a response from scratch, the server says &#8220;you already know this part of the file because you&#8217;ve cached it before&#8221; and only sends what&#8217;s new.</p><p><strong>Redirects for AI Training<br></strong>AI crawlers aren&#8217;t particularly intelligent - they can&#8217;t tell what information is outdated, even if the page explicitly says so.</p><p>To solve for this, Cloudflare is introducing Redirects for AI Training, which lets you enforce redirects for verified crawlers to the most up to date content. This transforms your existing canonical tags to become 301 redirects automatically with a single toggle.</p><p><strong>Unweight<br></strong>Large language models are huge, often into the 100s of GBs largely made up of numbers. They present unique challenges to run at scale.</p><p>Cloudflare focused on shrinking those numbers (model weights) without changing what the model actually does. They built a system called Unweight that compresses the model&#8217;s data so it takes up less space, but still gives exactly the same answers.</p><p><strong>Network Performance Update<br></strong>At regular intervals, Cloudflare provides updates on its network performance. Here&#8217;s the highlights from this review:</p><ul><li><p>Number 1 in 40% of networks to number 1 in 60% of networks in 3 months</p></li><li><p>Gained the fastest status in an additional 40 countries</p></li><li><p>Now number 1 in 54 more US ASNs</p></li><li><p>6ms faster than competitors on average in December</p></li></ul><p>That&#8217;s everything, hope you enjoyed Agents Week. I&#8217;ll be back for the next innovation week too, as well as my daily musings and insights all related to Cloudflare.</p><p>Want to read all the blog posts yourself? Check them out <a href="https://blog.cloudflare.com/">here</a>.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.ashleypeacock.co.uk/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Flared Up! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[vinext: One Week, One Engineer, $1,100 in Tokens]]></title><description><![CDATA[A conversation with Steve Faulkner from Cloudflare on recreating Next.js, one test at a time]]></description><link>https://blog.ashleypeacock.co.uk/p/vinext-one-week-one-engineer-1100</link><guid isPermaLink="false">https://blog.ashleypeacock.co.uk/p/vinext-one-week-one-engineer-1100</guid><dc:creator><![CDATA[Ashley Peacock]]></dc:creator><pubDate>Thu, 26 Mar 2026 18:25:29 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!fn06!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea6247ab-6373-49f9-b22e-5f9ab09eb251_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fn06!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea6247ab-6373-49f9-b22e-5f9ab09eb251_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fn06!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea6247ab-6373-49f9-b22e-5f9ab09eb251_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!fn06!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea6247ab-6373-49f9-b22e-5f9ab09eb251_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!fn06!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea6247ab-6373-49f9-b22e-5f9ab09eb251_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!fn06!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea6247ab-6373-49f9-b22e-5f9ab09eb251_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fn06!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea6247ab-6373-49f9-b22e-5f9ab09eb251_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ea6247ab-6373-49f9-b22e-5f9ab09eb251_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3442287,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://flaredup.substack.com/i/191569368?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea6247ab-6373-49f9-b22e-5f9ab09eb251_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!fn06!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea6247ab-6373-49f9-b22e-5f9ab09eb251_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!fn06!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea6247ab-6373-49f9-b22e-5f9ab09eb251_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!fn06!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea6247ab-6373-49f9-b22e-5f9ab09eb251_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!fn06!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea6247ab-6373-49f9-b22e-5f9ab09eb251_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>On 24th February 2026, Cloudflare <a href="https://blog.cloudflare.com/vinext/">announced vinext</a>, a complete rewrite of Next.js built on Vite, deployable to Cloudflare with a single command.</p><p>For years, Cloudflare has worked to support Next.js on its platform, but the framework&#8217;s bespoke build output made that difficult. Running Next.js outside its intended environment requires reshaping that output for each platform.</p><p>This pain point led to vinext. By rebuilding Next.js on top of Vite, deployment becomes considerably simpler.</p><p>That alone would be an interesting story. But what if I told you that vinext was created in one week, by a single engineer, Steve Faulkner, using AI and around $1,100 in tokens.</p><p>I sat down with Steve to understand how vinext came together, and what it tells us about the future of AI-assisted development.</p><div><hr></div><p><strong>Ashley:</strong> Hi Steve, and thank you for agreeing to be interviewed today! Can you briefly introduce yourself and your role at Cloudflare?</p><p><strong>Steve:</strong> Hey everyone, I'm <a href="https://x.com/southpolesteve">Steve Faulkner</a>, Director of Engineering for Workers at Cloudflare. I run a roughly 90-person org that covers Workers, Containers, Agents SDK, Sandboxes, Wrangler, Frameworks, and several other teams.</p><div><hr></div><p><strong>Ashley:</strong> vinext is a pretty radical idea. How did it actually come about?</p><p><strong>Steve:</strong> We&#8217;re constantly trying to figure out how to best support Next.js on Cloudflare. We&#8217;ve invested heavily in <a href="https://opennext.js.org/">OpenNext</a> and we&#8217;re still involved in that project, but OpenNext is fundamentally built on top of Next.js and its Turbopack-based output, which constrains what we can do.</p><p>There was always this idea of reimplementing the Next.js API to get a higher ceiling on performance and better output. We actually tried it twice with human engineers.</p><p>Then the models got dramatically better last December and January, and I started wondering if AI could just do it. Next.js has a massive test suite. I used that as the spec, started on a Friday night with Opus, and woke up Saturday morning to an app router demo that was kind of working. That kicked this whole project into gear.</p><div><hr></div><p><strong>Ashley: </strong>Rewriting Next.js is no small undertaking, even for AI. How did you structure your workflow with AI on this project?</p><p><strong>Steve:</strong> Almost all of it was OpenCode with Opus 4.5 and 4.6. I also do a lot of voice-to-text via <a href="https://superwhisper.com/">SuperWhisper</a>. A few markdown files for planning and context: an agents.md file generated and maintained by the agent, a discoveries.md that logged ecosystem gotchas to avoid repeat mistakes, and tracking documents for ported Next.js tests. I didn&#8217;t use special sub-agents or custom MCP servers.</p><p>Closer to launch I started using <a href="https://context7.com/">context7</a> and <a href="https://exa.ai/docs/reference/exa-mcp">Exa search MCP</a> for better library lookups. I also made use of <a href="https://github.com/vercel-labs/agent-browser">agent-browser,</a> which was critical for actual debugging in a live browser.</p><p>I asked OpenCode to analyze its own sessions and they were surprisingly barbell-shaped: either two-to-three-minute corrections or one-to-two-hour deep dives. My peak token usage was at 3 AM, when I&#8217;m sleeping, so I was clearly setting up task lists before bed and letting it run overnight.</p><p>This was not a fancy or complex setup. No Ralph Wiggum loops. Just giving OpenCode a list of tasks I thought would keep it busy for a few hours.</p><div><hr></div><p><strong>Ashley:</strong> It&#8217;s refreshing to see a relatively simple workflow be so effective, especially when all the discourse online is often buzzword soup. I&#8217;m sure it wasn&#8217;t all plain sailing though.</p><p>Which parts of vinext did AI handle well, and which parts still needed deep human expertise?</p><p><strong>Steve:</strong> AI excelled at the boring, systemic work: porting tests from the Next.js suite, implementing next module shims against a known API surface, and grinding through compatibility issues. If you give it a failing test and a clear target, it iterates well.</p><p>The big human contribution was setting overall direction, such as:</p><ul><li><p>Deciding this should be a Vite plugin rather than a custom bundler</p></li><li><p>Choosing to port tests rather than run the Next.js harness</p></li><li><p>Prioritizing which features to tackle first. And finding bugs</p></li></ul><p>I had to do manual QA many times, but AI would surprise me here too. It would get really far on its own with agent-browser.</p><div><hr></div><p><strong>Ashley:</strong> That definitely resonates with how I&#8217;m working with AI too, it can churn out code but it still lacks some critical thinking in my experience, especially when given a larger task.</p><p>I&#8217;d say recreating Next.js qualifies as a pretty large task, so how did you break down such a large framework into tasks that AI could reliably execute?</p><p><strong>Steve:</strong> The agent helped a ton. We focused mostly on the test suite, but rather than trying to run their test harness directly, I had the agent port tests one by one into our Vitest and Playwright setup, then implement the code to make them pass. A tracking document helped the AI keep track along the way.</p><div><hr></div><p><strong>Ashley:</strong> There&#8217;s a long-standing idea that test suites are the best form of documentation, and this seems to prove it.</p><p>No matter the model, AI isn&#8217;t perfect though and I find it often gets things wrong or gets stuck.</p><p>What were the biggest failure points of AI during the project? What was the hardest technical problem in vinext where AI struggled the most?</p><p><strong>Steve:</strong> There were definitely times where the AI just got stuck and I had to open a browser, click around, look at logs, and figure out what was actually going on. That manual debugging loop never fully went away.</p><p>I&#8217;ve also noticed it struggles with larger files. We have some that are two to three thousand lines, and I suspect smaller, more focused files work better for AI. That&#8217;s something we&#8217;re actively refactoring toward right now.</p><p>The other recurring issue was getting the AI to match Next.js behavior exactly. It would implement something that seemed reasonable but was subtly different from how Next.js actually works.</p><p>I&#8217;d point out the discrepancy and it would immediately agree and fix it, but it wouldn&#8217;t catch it on its own. A lot of iterating on our agents.md file went into teaching it to look at the Next.js implementation first before writing anything, so it was informed by how Next.js actually does things rather than guessing.</p><div><hr></div><p><strong>Ashley:</strong> I&#8217;ve been playing around with agents.md files a lot more lately, so I can definitely see how that would help. The one that Cloudflare ships with any new project created by npm create is quite good, I noticed the AI often tries to validate its work because it&#8217;s very aware of the tools to do so.</p><p>Another common discussion point online is the quality of code in the era of AI.</p><p>How would you rate the code quality of vinext, considering it was entirely written by AI which can often be sloppy or overly verbose in some cases?</p><p><strong>Steve:</strong> It&#8217;s not amazing code. It&#8217;s verbose, and there are patterns I don&#8217;t love, like heavy use of template strings for code generation. However, this project was fundamentally about trusting the tests. If it passes the 2,700+ tests across Vitest and Playwright, it works.</p><p>We&#8217;re embracing that philosophy in maintenance too. We have AI reviewing code, and if we&#8217;re confident it&#8217;s the right direction and the AI approves it, we merge it.</p><p>This project was born from AI, and I think it will succeed because of AI. We&#8217;re trying to embrace that mindset rather than fight it. Almost all activity in the repo is driven by AI, including from the community.</p><div><hr></div><p><strong>Ashley:</strong> If nothing else, it&#8217;s a fascinating way to test how far AI can be pushed, along with how well it can maintain a project of considerable size.</p><p>In the vinext blog post, it mentions 94% coverage of Next.js&#8217; test suite, so what big gaps remain in vinext vs Next.js?</p><p><strong>Steve:</strong> There are still gaps and they&#8217;re well documented in the README. Static pre-rendering was called out in the blog post when we initially released it, but we just merged a PR with the first version of that, so it&#8217;s already being tackled.</p><p>One thing that&#8217;s surprised me is how few reported issues are about actual behavioral differences with Next.js. If you look at our issue tracker, the problems are mostly general JavaScript ecosystem issues: CommonJS versus ESM, bundling and module resolution differences between Vite and Turbopack.</p><p>Vite has a very ESM-first view of the world where Turbopack/Webpack are more lax about what they will accept especially around CommonJS.</p><div><hr></div><p><strong>Ashley:</strong> It&#8217;s great to see vinext being iterated on and maintained, as there was some concern online that this would be pushed out and then abandoned as a PR piece.</p><p>Looking forward, what&#8217;s the plan for vinext in the future? Is the plan to maintain it and continue working on it?</p><p><strong>Steve:</strong> We are continuing to work on it. It started as an experiment but it&#8217;s clearly something people want.</p><p>In four weeks we&#8217;ve merged over 350 PRs, shipped 19 releases, and have 50+ contributors from the community. The repo has nearly 7,000 stars.</p><p>If you&#8217;re using Next.js and want to try it, you should. The more people who try it, the better it gets. The most valuable thing you can do is point your agent of choice at the repo, have it use the migration skill, and file issues for anything it hits along the way.</p><div><hr></div><p><strong>Ashley:</strong> That&#8217;s an incredible number of PRs for such a new project! The fact that other engineers are contributing shows there is value in the project, and that there&#8217;s a real desire to more easily run Next.js in places that were previously difficult.</p><p>Something that came to my mind immediately when I read the blog post was how vinext would co-exist with Next.js.</p><p>What does maintenance look like? How do you see updates in Next.js flowing into vinext, or will they diverge in future?</p><p><strong>Steve:</strong> This project was born from AI and it&#8217;s going to be maintained by AI. We&#8217;ve gone all-in on AI development in this repo.</p><p>We have a strong bias toward merging AI-written PRs, we actively encourage contributors to use AI, and we spend a lot of time on our agents.md files to make sure the AI has the context it needs.</p><p>We have AI doing code review, AI doing security scanning, and AI keeping up with Next.js commits. That&#8217;s been the philosophy from day one.</p><p>Our primary focus right now is maintaining feature parity with Next.js. But I&#8217;m open to a future where we introduce small divergences. We&#8217;ve already shipped one feature that Next.js lacks (traffic-aware pre-rendering), and we&#8217;ll continue to look for similar opportunities.</p><p>We&#8217;re also getting requests to address Next.js bugs or implement behavioral modifications. We&#8217;re not actively pursuing those today, but I&#8217;m open to it depending on how the project grows.</p><div><hr></div><p><strong>Ashley:</strong> That&#8217;s really exciting, and I&#8217;m looking forward to seeing what happens with vinext in the future. Given the popularity of Vite, and the ability for AI to migrate from one framework to another at pace, it&#8217;ll be interesting to see how much it grows.</p><p>I know there are lots of engineers out there that are grappling with AI, some are embracing it, others are resistant, and everything in-between.</p><p>What skills do you think matter most for engineers working in this new world with heavy AI assisted coding?</p><p><strong>Steve:</strong> You still need to know what to build and why. Recognize when AI output is structurally flawed, even if the tests pass, and understand your problem space deeply enough to steer effectively.</p><p>That ability to clearly articulate what&#8217;s right and course-correct is becoming one of the most valuable engineering skills.</p><p>Agents are remarkably good at taking feedback - often better than humans.</p><div><hr></div><p><strong>Ashley:</strong> There&#8217;s a lot to be said for taste in this new world we are in, and I think it&#8217;s even more important than it was before.</p><p>You&#8217;ve stated that you want to be as AI-pilled as possible with this project, so do you think AI will eventually be able to maintain a project like vinext without a human in the loop?</p><p><strong>Steve:</strong> We're close, but not quite there yet.</p><p>For me, the truly interesting part of this project isn't Next.js; it's the potential of AI and pushing its limits. If the models improve just a bit more, and if we can establish a few more robust workflows (like Ralph Wiggum loops and leveraging the emerging auto-research techniques), I believe we can reach a point where AI can maintain projects like this without human intervention.</p><p>Given the current trajectory of these tools, that's where I think we&#8217;ll end up.</p><div><hr></div><p><strong>Ashley:</strong> Thank you so much for doing this interview with me, it&#8217;s been incredibly enlightening, answering a number of questions I had when I read the blog post - and I&#8217;m sure that others had too.</p><p>Good luck with vinext in the future, and I&#8217;ll be watching with eagerness to see how the project develops!</p><p>Is there anything you&#8217;d like to sign off with?</p><p><strong>Steve:</strong> Go try <a href="https://github.com/cloudflare/vinext">vinext</a>! It works everywhere, not just Cloudflare.</p><p>Running outside Cloudflare is as simple as <code>vite start</code> and you&#8217;re up on any Node server. The more feedback we get, the faster we can move.</p><p>And please, use AI to do the <a href="https://github.com/cloudflare/vinext?tab=readme-ov-file#quick-start">migration</a> and have AI file issues. We&#8217;ve had really good luck with this kind of AI-first migration surfacing what problems people hit in the wild.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.ashleypeacock.co.uk/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe below to get similar Cloudflare content in your inbox.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Build a Ticket Alert App with Cloudflare Durable Objects]]></title><description><![CDATA[How I tracked ticket availability using Workers, Durable Objects, Browser Rendering and Email Sending]]></description><link>https://blog.ashleypeacock.co.uk/p/build-a-ticket-alert-app-with-cloudflare</link><guid isPermaLink="false">https://blog.ashleypeacock.co.uk/p/build-a-ticket-alert-app-with-cloudflare</guid><dc:creator><![CDATA[Ashley Peacock]]></dc:creator><pubDate>Tue, 17 Mar 2026 19:14:43 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!X8tE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fda945c-651c-428f-be9f-56f64acf7dcc_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!X8tE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fda945c-651c-428f-be9f-56f64acf7dcc_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!X8tE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fda945c-651c-428f-be9f-56f64acf7dcc_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!X8tE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fda945c-651c-428f-be9f-56f64acf7dcc_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!X8tE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fda945c-651c-428f-be9f-56f64acf7dcc_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!X8tE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fda945c-651c-428f-be9f-56f64acf7dcc_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!X8tE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fda945c-651c-428f-be9f-56f64acf7dcc_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5fda945c-651c-428f-be9f-56f64acf7dcc_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3153488,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://flaredup.substack.com/i/191287767?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fda945c-651c-428f-be9f-56f64acf7dcc_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!X8tE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fda945c-651c-428f-be9f-56f64acf7dcc_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!X8tE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fda945c-651c-428f-be9f-56f64acf7dcc_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!X8tE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fda945c-651c-428f-be9f-56f64acf7dcc_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!X8tE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5fda945c-651c-428f-be9f-56f64acf7dcc_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Durable Objects allow you to create very novel architectures that at the same time, considerably simplify the development effort required. When combined with Cloudflare&#8217;s growing list of primitives, you can offload a sizeable amount of complexity to the platform itself. This leaves you to focus on the things that make your application unique.<br><br>To demonstrate this, I built an application that notifies users when Eurostar Snap tickets become available for sale for your desired date and time. I never knew this website existed until recently, but effectively you can buy last minute Eurostar tickets very cheaply - some as cheap as &#163;35 ($50) one way!</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!r3Gz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b9548b-7494-4113-8e28-2005a684f2ef_2047x1666.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!r3Gz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b9548b-7494-4113-8e28-2005a684f2ef_2047x1666.jpeg 424w, https://substackcdn.com/image/fetch/$s_!r3Gz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b9548b-7494-4113-8e28-2005a684f2ef_2047x1666.jpeg 848w, https://substackcdn.com/image/fetch/$s_!r3Gz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b9548b-7494-4113-8e28-2005a684f2ef_2047x1666.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!r3Gz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b9548b-7494-4113-8e28-2005a684f2ef_2047x1666.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!r3Gz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b9548b-7494-4113-8e28-2005a684f2ef_2047x1666.jpeg" width="1456" height="1185" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/41b9548b-7494-4113-8e28-2005a684f2ef_2047x1666.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1185,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Image&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Image" title="Image" srcset="https://substackcdn.com/image/fetch/$s_!r3Gz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b9548b-7494-4113-8e28-2005a684f2ef_2047x1666.jpeg 424w, https://substackcdn.com/image/fetch/$s_!r3Gz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b9548b-7494-4113-8e28-2005a684f2ef_2047x1666.jpeg 848w, https://substackcdn.com/image/fetch/$s_!r3Gz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b9548b-7494-4113-8e28-2005a684f2ef_2047x1666.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!r3Gz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41b9548b-7494-4113-8e28-2005a684f2ef_2047x1666.jpeg 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">For the non-Europeans, Eurostar is a high speed train link between popular destinations like London, Paris and Brussels</figcaption></figure></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.ashleypeacock.co.uk/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Flared Up! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><h1><strong>High-level Architecture</strong></h1><p>From the user&#8217;s point of view, the flow is really quite simple. A user selects the details of their trip such as from/to and the date, and if tickets become available, they receive an email notifying them.<br><br>On the technical side, we need the following:</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.ashleypeacock.co.uk/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Flared Up! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><ul><li><p>A way to serve assets to the frontend</p></li><li><p>An API to handle form submissions &amp; unsubscribes</p></li><li><p>Be able to store the trips we need to track that users select</p></li><li><p>Periodically check for ticket availability (we&#8217;ll do it every 20 minutes)</p></li><li><p>Scrape the website for tickets using a headless browser (there&#8217;s no API)</p></li><li><p>Send notification emails</p></li></ul><p>We can map all these directly onto Cloudflare primitives like so:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JrhV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e087ea0-3fc5-44b7-ada5-90ca09e6254a_2048x1300.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JrhV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e087ea0-3fc5-44b7-ada5-90ca09e6254a_2048x1300.jpeg 424w, https://substackcdn.com/image/fetch/$s_!JrhV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e087ea0-3fc5-44b7-ada5-90ca09e6254a_2048x1300.jpeg 848w, https://substackcdn.com/image/fetch/$s_!JrhV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e087ea0-3fc5-44b7-ada5-90ca09e6254a_2048x1300.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!JrhV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e087ea0-3fc5-44b7-ada5-90ca09e6254a_2048x1300.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JrhV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e087ea0-3fc5-44b7-ada5-90ca09e6254a_2048x1300.jpeg" width="1456" height="924" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6e087ea0-3fc5-44b7-ada5-90ca09e6254a_2048x1300.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:924,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Image&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Image" title="Image" srcset="https://substackcdn.com/image/fetch/$s_!JrhV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e087ea0-3fc5-44b7-ada5-90ca09e6254a_2048x1300.jpeg 424w, https://substackcdn.com/image/fetch/$s_!JrhV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e087ea0-3fc5-44b7-ada5-90ca09e6254a_2048x1300.jpeg 848w, https://substackcdn.com/image/fetch/$s_!JrhV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e087ea0-3fc5-44b7-ada5-90ca09e6254a_2048x1300.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!JrhV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e087ea0-3fc5-44b7-ada5-90ca09e6254a_2048x1300.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>It&#8217;s really quite simple, as the platform is handling so much for us.<br><br>The <a href="https://workers.cloudflare.com/">Cloudflare Worker</a> is responsible for serving the frontend using <a href="https://developers.cloudflare.com/workers/static-assets/">static assets</a>, and also implements the backend APIs for form submissions and unsubscribes.</p><h2><strong>Durable Objects Are The Real MVP</strong></h2><p>We use <a href="https://developers.cloudflare.com/durable-objects/">Durable Objects</a> to track the trips, with one created per unique trip combination using a simple key for each Durable Object (e.g. <code>london-paris-2027-01-01</code>).</p><p>If no Durable Object exists for a trip, we simply create one and store the user&#8217;s email. For all future submissions for that same trip, we just add the new email to the existing Durable Object.</p><p>For anyone not familiar with Durable Objects, they combine compute and storage into one primitive, so the storage for each trip lives inside each Durable Object instance.<br><br>The Durable Object does more for us than that though, as it uses the alarm functionality to wake up every 20 minutes to check if tickets are available. More on that later.</p><h2><strong>There&#8217;s A Primitive For Everything</strong></h2><p>In order to check availability, we need a headless browser, so the application makes use of Browser Rendering - effectively headless browsers as a service, with support for Puppeteer.<br><br>If tickets are found, we need to send an email to let users know. For that, the application uses Email Sending via Cloudflare, which is currently in closed beta at this time.</p><p>Is Cloudflare&#8217;s offering as vast as AWS or GCP? Nope, but it has all the key building blocks that the majority of applications need these days.</p><h2><strong>Durable Objects Are Just Code</strong></h2><p>Let&#8217;s take a look at what the Durable Object looks like. Best of all, it&#8217;s just code, with the underlying infrastructure handled by Cloudflare.</p><p>Before sharing the code, this is by no means glorious code - it was absolutely vibe-coded as it&#8217;s just a demo app.</p><p>Here&#8217;s how we initialize the Durable Object:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;51346f4e-bd7c-4f53-a4d9-4f70953ba899&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import { DurableObject } from "cloudflare:workers";
import * as puppeteer from "@cloudflare/puppeteer";
import { TripDetails } from "./shared";
import { sendNotificationEmail } from "./email";
import { checkAvailability } from "./scraper";

const FOURTEEN_DAYS_MS = 14 * 24 * 60 * 60 * 1000;
const TWENTY_MINUTES_MS = 20 * 60 * 1000;
const FIVE_MINUTES_MS = 5 * 60 * 1000;

function firstRow&lt;T&gt;(cursor: { toArray(): T[] }): T | undefined {
    return cursor.toArray()[0];
}

export class SnapNotifier extends DurableObject&lt;Env&gt; {
    constructor(ctx: DurableObjectState, env: Env) {
        super(ctx, env);
        this.ctx.storage.sql.exec(`
            CREATE TABLE IF NOT EXISTS trip (
                origin TEXT NOT NULL,
                destination TEXT NOT NULL,
                date TEXT NOT NULL,
                time_slot TEXT NOT NULL
            );
            CREATE TABLE IF NOT EXISTS subscribers (
                email TEXT NOT NULL UNIQUE,
                token TEXT PRIMARY KEY
            );
            CREATE TABLE IF NOT EXISTS availability_state (
                id INTEGER PRIMARY KEY CHECK (id = 1),
                last_seen_available INTEGER NOT NULL
            );
        `);
    }
]</code></pre></div><p>As each Durable Object has its own SQLite database, and it sits on the same machine that runs your Durable Object, the majority of queries <a href="https://blog.cloudflare.com/sqlite-in-durable-objects/">run in 0ms</a> - that&#8217;s not a typo. We can then add a method that&#8217;s called via RPC to handle subscribes on form submission from the Worker:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;5519bc8b-27a4-427f-8e5b-422e8144e94a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">async subscribe(email: string, trip: TripDetails): Promise&lt;void&gt; {
  const existing = firstRow(this.ctx.storage.sql.exec("SELECT 1 FROM trip LIMIT 1"));

  if (!existing) {
    this.ctx.storage.sql.exec(
      "INSERT INTO trip (origin, destination, date, time_slot) VALUES (?, ?, ?, ?)",
      trip.origin,
      trip.destination,
      trip.date,
      trip.timeSlot,
    );
  }

  const alreadySubscribed = firstRow(
    this.ctx.storage.sql.exec("SELECT token FROM subscribers WHERE email = ?", email),
  );

  if (!alreadySubscribed) {
    const token = crypto.randomUUID();
    this.ctx.storage.sql.exec(
      "INSERT INTO subscribers (email, token) VALUES (?, ?)",
      email,
      token,
    );
  }

  await this.scheduleAlarm(trip.date);
}</code></pre></div><p>If you&#8217;re curious how the Worker and the Durable Object interact, there&#8217;s great information in the <a href="https://developers.cloudflare.com/durable-objects/best-practices/create-durable-object-stubs-and-send-requests/">docs</a> that cover it. This is what interaction with a Durable Object looks like though:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;6ea0c889-d0b6-4b41-ac6e-57781b192fa2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">const outboundKey = buildDoKey(origin, destination, outboundDate, outboundTimeSlot); //london-paris-2027-01-01

const outboundStub = env.SNAP_NOTIFIER.get(
  env.SNAP_NOTIFIER.idFromName(outboundKey),
) as DurableObjectStub&lt;SnapNotifier&gt;;

await outboundStub.subscribe(email, {
  origin,
  destination,
  date: outboundDate,
  timeSlot: outboundTimeSlot as TripDetails["timeSlot"],
});</code></pre></div><p>Once subscribed, we handle the alarms in code too:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;07339eee-34f4-45a2-aac9-d61933528ecf&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">private async scheduleAlarm(tripDate: string): Promise&lt;void&gt; {
  const tripMs = new Date(tripDate + "T00:00:00Z").getTime();
  const now = Date.now();

  const targetMs = tripMs - FOURTEEN_DAYS_MS;
  const alarmTime = targetMs &gt; now ? targetMs : now + 1000;

  const currentAlarm = await this.ctx.storage.getAlarm();
  if (currentAlarm === null || currentAlarm &gt; alarmTime) {
    await this.ctx.storage.setAlarm(alarmTime);
  }
}

async alarm(): Promise&lt;void&gt; {
  const tripRow = firstRow(
    this.ctx.storage.sql.exec("SELECT origin, destination, date, time_slot FROM trip LIMIT 1"),
  );

  if (!tripRow) return;

  const trip: TripDetails = {
    origin: tripRow.origin as string,
    destination: tripRow.destination as string,
    date: tripRow.date as string,
    timeSlot: tripRow.time_slot as TripDetails["timeSlot"],
  };

  const tripMs = new Date(trip.date + "T23:59:59Z").getTime();
  if (Date.now() &gt; tripMs) {
    this.ctx.storage.deleteAll();
    return;
  }

  let browser: Awaited&lt;ReturnType&lt;typeof puppeteer.launch&gt;&gt; | null = null;
  try {
    browser = await puppeteer.launch(this.env.BROWSER);
    const result = await checkAvailability(browser, trip);
    const lastSeenAvailable = this.getLastSeenAvailability();

    if (result.available) {
      await this.sendNotifications(trip);
    }
    
    await this.ctx.storage.setAlarm(Date.now() + TWENTY_MINUTES_MS);
  } catch (err) {
    console.error("Availability check failed, retrying in 5 minutes:", err);
    await this.ctx.storage.setAlarm(Date.now() + FIVE_MINUTES_MS);
  } finally {
    if (browser) {
      await browser.close();
    }
  }
}</code></pre></div><p>Note that an alarm is run once by default, but will retry on error automatically with exponential backoff. You&#8217;ll see in the alarm method, we set the alarm again at the end of the flow and control retries ourselves in this instance.</p><p>To optimise cost, once the trip date for a given Durable Object passes, we can just call deleteAll() and no longer trigger the alarm to remove any cost associated with that Durable Object.</p><p>Lastly, if tickets are found, we send the email from the Durable Object too:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;9ba7fff9-85e3-48e0-a8cd-19f1a79a6b3d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">private async sendNotifications(trip: TripDetails): Promise&lt;void&gt; {
  const rows = this.ctx.storage.sql
    .exec("SELECT email, token FROM subscribers")
    .toArray();

  if (rows.length === 0) return;

  const doId = this.ctx.id.toString();

  for (const row of rows) {
    const unsubscribeUrl = `${this.env.WORKER_URL}/api/unsubscribe?id=${doId}&amp;token=${row.token as string}`;
    await sendNotificationEmail({
      trip,
      email: row.email as string,
      unsubscribeUrl,
    });
  }
}</code></pre></div><p>There&#8217;s a little more code in the full application, but I omitted some as it&#8217;s not showing anything unique (e.g. unsubscribes, avoiding re-sending emails on repeat).</p><p>If you&#8217;ve not used the Cloudflare developer platform before, it&#8217;s also worth reading about <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/">bindings</a>, as the code above makes use of them for interacting with both Browser Rendering and Email Sending. In short, they provide access to resources with an SDK injected at runtime for you to use - a bit like platform-level dependency injection.</p><h2><strong>Cloudflare Does The Undifferentiated Heavy Lifting</strong></h2><p>The big takeaway from this is the platform is doing all of the heavy lifting. TThe frontend is served globally, as is the backend API. There are no regions when deploying to Cloudflare, and both will scale based on demand.</p><p>We&#8217;re sharding by trip date, and each Durable Object can store up to 10GB of data - there&#8217;s no way we ever store so many email addresses we hit that limit. Alarms run independently on each Durable Object, so if one encounters an error, no others are impacted, and we don&#8217;t have to worry about crons or anything like that.<br><br>You&#8217;re only charged when a Durable Object is running too, so when it&#8217;s asleep between checking for ticket availability, there&#8217;s no runtime cost incurred.<br><br>Lastly, we offload the responsibility of handling the headless browsers to Browser Rendering and just interact with it using a simple API via Puppeteer, and it&#8217;s a one-liner to send an email with Cloudflare too once Email Sending becomes publicly available.</p><p>Both are charged in a serverless manner, with Browser Rendering charging $0.09 per browser hour, and Email Sending pricing TBC but fully expect it to be based on usage. There&#8217;s <a href="https://developers.cloudflare.com/durable-objects/platform/pricing/">pricing available</a> for Durable Objects too.</p><p>The speed at which you can move with Cloudflare is unmatched in my experience, and by no means is it perfect, but I&#8217;ll accept some of the warts if the platform is doing so much for me, and I highly recommend you give it a go too.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.ashleypeacock.co.uk/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Flared Up! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Running Containers on Cloudflare: Everything You Need to Know]]></title><description><![CDATA[A hands-on deep dive into Cloudflare&#8217;s new container platform - how it works, why it matters, and how to get started today]]></description><link>https://blog.ashleypeacock.co.uk/p/running-containers-on-cloudflare</link><guid isPermaLink="false">https://blog.ashleypeacock.co.uk/p/running-containers-on-cloudflare</guid><dc:creator><![CDATA[Ashley Peacock]]></dc:creator><pubDate>Tue, 24 Jun 2025 16:48:26 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/9b71ae27-d610-4e58-a2b9-54b276e06885_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ic-P!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae1bc24-8ff0-4407-89fc-02190abd7a2c_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ic-P!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae1bc24-8ff0-4407-89fc-02190abd7a2c_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!ic-P!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae1bc24-8ff0-4407-89fc-02190abd7a2c_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!ic-P!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae1bc24-8ff0-4407-89fc-02190abd7a2c_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!ic-P!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae1bc24-8ff0-4407-89fc-02190abd7a2c_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ic-P!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae1bc24-8ff0-4407-89fc-02190abd7a2c_1536x1024.png" width="602" height="401.47115384615387" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0ae1bc24-8ff0-4407-89fc-02190abd7a2c_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:602,&quot;bytes&quot;:2198429,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://flaredup.substack.com/i/165476495?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae1bc24-8ff0-4407-89fc-02190abd7a2c_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ic-P!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae1bc24-8ff0-4407-89fc-02190abd7a2c_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!ic-P!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae1bc24-8ff0-4407-89fc-02190abd7a2c_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!ic-P!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae1bc24-8ff0-4407-89fc-02190abd7a2c_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!ic-P!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae1bc24-8ff0-4407-89fc-02190abd7a2c_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Unless you&#8217;ve been living under a rock, you&#8217;ve undoubtedly heard that Cloudflare is going to be offering support for container-based workloads starting this year.</p><p>During Birthday Week 2024, <a href="https://blog.cloudflare.com/container-platform-preview/">Cloudflare announced</a> that they will be providing access to the container platform they have been using in production to run existing products such as Workers AI and Browser Rendering.</p><p>No definitive date was set for public access, but I&#8217;m excited that the time has come for all of you to take the container platform for a spin. I&#8217;ve had access during closed beta, so I&#8217;m here to tell you all about Cloudflare Containers: what are they? How do they work? And how can you get started?</p><blockquote><p>This post assumes some basic understanding of <a href="https://www.docker.com/resources/what-container/">Docker and containers</a>.</p></blockquote><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.ashleypeacock.co.uk/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Flared Up! Subscribe for free to receive new posts and keep up to date with all things Cloudflare.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>What are Cloudflare Containers?</h2><p>If you&#8217;ve used containers before, you&#8217;ll feel right at home with Cloudflare Containers. I use containers on a daily basis as part of my day job, and it was simple to get started with Cloudflare&#8217;s offering.</p><p>You can literally take a Dockerfile you have for an existing service and run that on Cloudflare, because it uses the things you likely already use. I&#8217;d say containers on Cloudflare are positioned as lightweight, ephemeral containers that are globally-distributed automatically, and will have auto-scaling built-in.</p><p>I&#8217;ve not used it myself, but my understanding is it&#8217;s pretty similar to <a href="https://cloud.google.com/run">Google Cloud Run</a> based on some conversations with others. In effect, you create your container, and Cloudflare handles deploying and provisioning that container when needed. As I&#8217;ve come to expect with Cloudflare, the developer experience is incredibly simple, allowing you to offload as much as possible to Cloudflare&#8217;s platform.</p><p>Whether you&#8217;ve used Google Cloud Run, or deployed containers to EC2 instances via ECS on AWS, there are some key differences to keep in mind.</p><p>For starters, there&#8217;s no persistent storage at launch. With ECS for example, you can attach EBS storage that survives the container being restarted. With Cloudflare, you do have access to the file system, but any data will be lost when the container is stopped.</p><p>On the plus side, containers are deployed globally automatically by Cloudflare, ensuring they are spun up close to the end user, which reduces latency compared to single-region deploys of Cloud Run and EC2. You can deploy to multiple regions, but you'd need to handle orchestration yourself.</p><blockquote><p>Cloud Run has recently released <a href="https://cloud.google.com/run/docs/multiple-regions">multi-region deploys in beta</a>, but you then need to setup load balancing too, so it&#8217;s not as effortless as with Cloudflare</p></blockquote><p>This isn&#8217;t the only benefit, as it also prevents your containers being taken out by a single region going down. If that were to happen, Cloudflare would just spin up your containers in another region, a big win for fault tolerance and disaster recovery.</p><blockquote><p><em>Enjoying the article and want to get a head start developing with Cloudflare? I&#8217;ve published a book, Serverless Apps on Cloudflare, that introduces you to all of the platform&#8217;s key offerings by guiding you through building a series of applications on Cloudflare.<br><br>Buy now: <a href="https://pragprog.com/titles/apapps/serverless-apps-on-cloudflare/">eBook</a> | P<a href="https://www.amazon.com/Serverless-Apps-Cloudflare-Solutions-Infrastructure/dp/B0DFNTSMHP">aperback</a></em></p></blockquote><h3>Cloudflare Containers Use Cases</h3><p>As with all pieces of technology, they are often targeted at certain use cases, and Cloudflare Containers are no different. Cloudflare already has Workers, Durable Objects, SQLite databases in the form of D1, blob storage with R2 and much more.</p><p>If you want to build a standard SaaS application that primarily handles CRUD operations, host a blog or build an API, you&#8217;re going to want to reach for the existing tools, as they excel at that task and similarly globally distribute your application.</p><p>However, due to the runtime that Workers operate in, which is limited to 128MB of memory, and only offers a Node-like environment, there are workloads that simply don&#8217;t fit with a Worker.</p><p>For example, let&#8217;s say your application allows users to upload videos that are then served to other users. You want to serve different formats based on the end user&#8217;s device, so you need to re-encode the uploaded video. The most common library for video encoding is <a href="https://github.com/FFmpeg/FFmpeg">FFmpeg</a>, but it&#8217;s written in C, and naturally won&#8217;t run on Workers. Even if it did, 128MB of memory isn&#8217;t enough for such a task.</p><p>This is a perfect use case to reach for a container on Cloudflare. You can deploy containers running FFmpeg, spin up the containers as needed to handle demand, and let Cloudflare worry about deploying and provisioning them.</p><p>Besides that use case, Workers only run natively with JavaScript, TypeScript and Python. You can deploy other languages using WASM, but this requires a little more setup and you&#8217;re ultimately relying on WASM compatibility with your chosen language.</p><p>With containers, you can simply use a base image for your language of choice, install any dependencies you need (e.g. Gems in Ruby, Crates in Rust), copy the code across, and off you go, with all of this being orchestrated by a standard Dockerfile.</p><p>One of my favourite use cases for this, in the world of vibe coding, is the ability to run user-generated code in a sandbox for any language. Take <a href="https://bolt.new/">bolt.new</a> or <a href="https://v0.dev/">v0</a>, which allow you to create an app using AI, each time the AI makes changes, it needs to be run in a safe, sandbox environment so the end-user can see the outcome. This would work perfectly with containers, and again is not something you would be able to do with Workers.</p><p>Being able to support any language in the world naturally opens up another use case: porting existing applications from the likes of AWS, GCP and Azure. If you&#8217;re using containers already, you can lift-and-shift existing applications right over to Cloudflare, and get access to their highly performant, global network.</p><p>That&#8217;s the high-level view of <em>what </em>Cloudflare&#8217;s Containers are, but how do they work under the hood?</p><h2>How do Cloudflare Containers Work?</h2><blockquote><p>This post will talk about Durable Objects a lot, which I&#8217;ve covered extensively before, so if you&#8217;re not sure what one is, I recommend reading at least the intro <a href="https://flaredup.substack.com/p/the-ultimate-guide-to-cloudflares">here</a> before continuing!</p></blockquote><p>The typical flow for deploying a container to most cloud platforms tends to follow the pattern of:</p><ol><li><p>Create your Dockerfile</p></li><li><p>Build your container</p></li><li><p>Push to a registry</p></li><li><p>Deploy those containers to instances from the registry</p></li><li><p>Start the containers</p></li></ol><p>So you&#8217;ll be pleased to know that&#8217;s the exact same pattern that Cloudflare has adopted. As I&#8217;ve come to enjoy with Cloudflare, it&#8217;s all wrapped up nicely for you by Wrangler, which I&#8217;ll go through later on when we go through the guide to deploying a container.</p><p>There&#8217;s actually a few pieces that combine to power containers on Cloudflare, and in terms of flow, this is how a typical request will look.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!01o5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40ef9e7-0013-44f3-9c65-aec2e56bbda1_1999x1000.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!01o5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40ef9e7-0013-44f3-9c65-aec2e56bbda1_1999x1000.png 424w, https://substackcdn.com/image/fetch/$s_!01o5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40ef9e7-0013-44f3-9c65-aec2e56bbda1_1999x1000.png 848w, https://substackcdn.com/image/fetch/$s_!01o5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40ef9e7-0013-44f3-9c65-aec2e56bbda1_1999x1000.png 1272w, https://substackcdn.com/image/fetch/$s_!01o5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40ef9e7-0013-44f3-9c65-aec2e56bbda1_1999x1000.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!01o5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40ef9e7-0013-44f3-9c65-aec2e56bbda1_1999x1000.png" width="1456" height="728" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a40ef9e7-0013-44f3-9c65-aec2e56bbda1_1999x1000.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:728,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!01o5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40ef9e7-0013-44f3-9c65-aec2e56bbda1_1999x1000.png 424w, https://substackcdn.com/image/fetch/$s_!01o5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40ef9e7-0013-44f3-9c65-aec2e56bbda1_1999x1000.png 848w, https://substackcdn.com/image/fetch/$s_!01o5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40ef9e7-0013-44f3-9c65-aec2e56bbda1_1999x1000.png 1272w, https://substackcdn.com/image/fetch/$s_!01o5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40ef9e7-0013-44f3-9c65-aec2e56bbda1_1999x1000.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Image credit: <a href="https://blog.cloudflare.com/cloudflare-containers-coming-2025/">Cloudflare</a></figcaption></figure></div><p>At least for now, containers on Cloudflare are only accessible via a Durable Object. Therefore, you need a Worker to front any user-facing requests that you want to hit a container.</p><p>That Worker can then request a specialised Durable Object that acts as a <a href="https://learn.microsoft.com/en-us/azure/architecture/patterns/sidecar">sidecar</a> to a container, providing an interface to interact with that container. In the same way you can create multiple instances of a regular Durable Object, you can do the same with the specialised container-focussed Durable Objects.</p><p>This mechanism enables the dynamic scaling of containers by associating one container per Durable Object instance.</p><p>Once you&#8217;ve retrieved the Durable Object for a container, behind the scenes the container will be started, ready to serve requests. The start-up time will depend entirely on what the container needs to do to start, but a simple Node.js Express app started in around 1.5 seconds on average when I tried. Subsequent requests were then served in around 100ms to the client.</p><p>Additionally, you can define how long a container stays in memory after it serves a request. In the future, you&#8217;ll be able to enable auto-scaling, but for now, the only way to spin up containers is when they are requested, with requests being queued if necessary.</p><p>In terms of the interface the Durable Object provides, it&#8217;s relatively minimal right now but contains what you&#8217;d probably expect:</p><ul><li><p>Start a container (you can opt to manually start containers, rather than have them auto-start when the Durable Object is requested)</p></li><li><p>Stop a container</p></li><li><p>Destroy a container</p></li><li><p>Exec against a container (e.g. run a Bash script)</p></li><li><p>Schedule (and unschedule) a task within the container</p></li><li><p>Hit the container with a request (using fetch)</p></li></ul><p>Besides HTTP, containers are also able to handle WebSockets. The docs are pretty light on detail, but it looks like the Worker effectively forwards the request to the container.</p><p>If you want to connect from your container to outside resources, such as a database, it&#8217;s possible to pass environment variables to the container. As each container has an associated Durable Object, which has access to your bindings, you can retrieve any secrets (from Worker Secrets or Secrets Store) and pass them as environment variables.</p><p>You can define callbacks when certain things happen to your container too. For now, there&#8217;s <code>onStart</code>, <code>onStop</code> and <code>onError</code>.</p><p>That rounds how containers on Cloudflare work, and goes over the functionality they provide. I find it helps to see things in action to really understand how you might use containers, so I&#8217;ll go over exactly how containers work in your code next.</p><h2><br>Get Started with Cloudflare Containers</h2><p>Let&#8217;s get your first container deployed to Cloudflare. We will use a simple Express application to get started, and then you can iterate from there. If you don&#8217;t want to use Node, it will be simple enough to update the Dockerfile to the runtime of your choice.</p><p>Before you can get started, you&#8217;ll need to make sure you have two things:</p><ol><li><p>A <a href="https://dash.cloudflare.com/sign-up">free Cloudflare account</a></p></li><li><p><a href="https://docs.docker.com/desktop/">Docker desktop</a> installed on your machine, make sure it&#8217;s running too!</p></li></ol><p>If you want to skip understanding how all the pieces fit together, the quickest way to deploy something right now using Containers is by running one simple command:</p><pre><code><code>npm create cloudflare@latest -- --template=--template=cloudflare/templates/containers-template</code></code></pre><p>This will use a template from Mike Nomitch (Product Manager at Cloudflare for Containers) and create a project all set to deploy a container. Simply run <code>npm run deploy</code> after and you&#8217;ll be all set.</p><p>If you want to understand the pieces that contribute to a container-based application on Cloudflare, read on. It&#8217;s honestly pretty simple, as I&#8217;ve come to expect with Cloudflare&#8217;s developer experience.</p><h4>Create a New Project</h4><p>You&#8217;re not going to do <em>everything </em>from scratch, that would be madness. You&#8217;ll start with a basic Worker, and then bolt containers onto that project. Keep in mind containers are not publicly accessible to the internet, so you need the Worker to act as the proxy for any request you want to route to a container.</p><p>Bootstrapping a project is easy with Cloudflare, simply run the following:</p><pre><code><code>npm create cloudflare@latest containers-hello-world</code></code></pre><p>Select &#8220;Hello World&#8221; from the first menu, then &#8220;Worker only&#8221; and finally &#8220;TypeScript&#8221;. You can opt to use Git if you wish, but don&#8217;t deploy your application.</p><p>Once this finishes, you&#8217;ll have a new folder named <code>containers-hello-world</code> containing a single Worker.</p><h4>Setup an Express App</h4><p>Before you can route any requests to a container, you need the actual code that will run inside the container. I picked Express purely because it&#8217;s incredibly simple to setup, and it&#8217;s fast - ensuring your cold starts are as minimal as possible.</p><p>Create a new folder at <code>src/container</code> and create two new files in it:</p><p><strong>server.js</strong></p><pre><code>const express = require('express');
const app = express();
const port = 8080;
const host = '0.0.0.0';

app.get('/', (req, res) =&gt; {
  res.status(200).send('Hello World');
});

app.listen(port, host, () =&gt; {
  console.log(`Server is running on http://${host}:${port}`);
});</code></pre><p><strong>package.json</strong></p><pre><code>{
  "name": "hello-world-container",
  "version": "1.0.0",
  "description": "A hello world container",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "express": "^4.18.2"
  }
}</code></pre><p>These two files create a simple Express application, running on Node, that will simply respond to any request with the classic Hello World message.</p><p>That&#8217;s the bootstrapping out of the way, let&#8217;s get onto the fun stuff: the containers.</p><h4>Create a Dockerfile</h4><p>When working with containers, the most common approach to build one is using a Dockerfile. If you&#8217;re not familiar, it&#8217;s effectively a set of instructions on how to build a container ready to serve your application - install dependencies, generate assets, and of course run your application.</p><p>Create a file named <code>Dockerfile</code> in the root of your project and insert the following content:</p><pre><code># syntax=docker/dockerfile:1

FROM node:20-alpine

# Set working directory
WORKDIR /app

# Copy package files
COPY container/package*.json ./

# Install Node.js dependencies
RUN npm install

# Copy Node.js source code
COPY container/ ./

# Expose port
EXPOSE 8080

# Run the server
CMD ["node", "server.js"]</code></pre><p>The contents of this file will vary depending on what runtime and framework you use. In this case, we&#8217;re using an existing Docker image from Node that runs a lightweight version of Alpine Linux.</p><p>In short, someone else has done all the work necessary to prepare the environment to run Node applications, so you can just focus on the specifics of an application.</p><p>Since the application is simple, there isn&#8217;t much setup required. You simply install dependencies with npm, copy the code into the container, expose port 8080 (this is important!) and then lastly run the Express application.</p><p>You could do exactly the same with Ruby on Rails, just with a different base image, and then bundle your gems and run <code>bundle exec rails s</code> at the end.</p><p>As this is just a standard Dockerfile, you can build and run this container to make sure it&#8217;s working in isolation. To do so, run the following:</p><pre><code>docker build -t containers-hw . &amp;&amp; docker run -p 8080:8080 containers-hw</code></pre><p>This will build your container, tag it with the name <em>containers-hw </em>and then run it on port 8080. Once the command has finished running, you can go to <em>localhost:8080</em> in your browser and you&#8217;ll see the Hello World text on your screen.</p><h4>Deploying a Container to Cloudflare</h4><p>With the groundwork set, you can move onto the <em>really </em>fun stuff: diving into the specifics of how you can integrate containers into your Cloudflare applications and deployments.</p><p>As we&#8217;ve covered, each container deployed to Cloudflare has an associated Durable Object. Additionally, in order for Cloudflare to know about your container, that needs to be configured in the same way you would bindings in your Wrangler configuration.</p><p>Open up <strong>wrangler.jsonc</strong> in the root of your project and update it to the following:</p><pre><code>{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "containers-hello-world",
  "main": "src/index.ts",
  "compatibility_date": "2025-05-23",
  "compatibility_flags": [
    "nodejs_compat"
  ],
  "observability": {
    "enabled": true
  },
  "containers": [
    {
      "class_name": "NodeContainer",
      "image": "./Dockerfile",
      "max_instances": 1,
      "name": "hello-world-container"
    }
  ],
  "durable_objects": {
    "bindings": [
      {
        "class_name": "NodeContainer",
        "name": "CONTAINER"
      }
    ]
  },
  "migrations": [
    {
      "new_sqlite_classes": [
        "NodeContainer"
      ],
      "tag": "v1"
    }
  ]
}</code></pre><p>If you&#8217;re not familiar with bindings, I highly recommend reading the <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/">documentation</a>. In short though, bindings allow your Worker to connect to a number of resources - storage (R2), databases (D1), caches (KV) and in this case, Durable Objects.</p><p>To deploy a container to Cloudflare, you need to create a Durable Object that will map to a class that will manage your container - you&#8217;ll create that class shortly.</p><p>Additionally, you&#8217;ll notice the containers key defines the configuration for your container. As it&#8217;s an array, you can have multiple containers configured within a single project.</p><p>The important part here is that the class name is the same as what&#8217;s defined in your Durable Object binding, and of course that it points to the location of your Dockerfile.</p><p>I&#8217;ve set <em>max_instances</em> to 1 just so it reduces the cost, but you can set it higher if you wish. Keep in mind that this will mean all your requests route to a single container, so latency will be high from locations that are far away from your container. For a production-grade application, you&#8217;ll want to ensure this number is much higher so your containers can be spun up across the globe.</p><p>With Wrangler understanding where your container configuration is, and that you need a Durable Object, let&#8217;s create the Durable Object class that will be responsible for managing your containers.</p><p>Open up <strong>src/index.ts </strong>and update it to the following:</p><pre><code>import { Container, getContainer } from '@cloudflare/containers';

export class NodeContainer extends Container {
  defaultPort = 8080;
  sleepAfter = '10s';

  override onStart() {
    console.log('Container successfully started');
  }

  override onStop() {
    console.log('Container successfully shut down');
  }

  override onError(error: unknown) {
    console.log('Container error:', error);
  }
}

export default {
  async fetch(request, env, ctx): Promise&lt;Response&gt; {
    return new Response('Hello World!');
  },
} satisfies ExportedHandler&lt;Env&gt;;</code></pre><p>There&#8217;s a surprisingly small amount of code needed to interact with your containers. By adding this class, that extends a specialised type of Durable Object aptly named <em>Container</em>, you&#8217;ve now effectively got a sidecar for your container.</p><p>The code itself is pretty self-explanatory, with the key part being you need to make sure the port listed here matches what&#8217;s in your Dockerfile. As the name suggests, <em>sleepAfter </em>defines how long your containers stay in memory after handling a request. Lastly, there are a bunch of hooks you can use to handle lifecycle events of your container, such as when it starts and stops.</p><p>There&#8217;s a separate npm package for containers, so you need to add that to our application too:</p><pre><code>npm install @cloudflare/containers</code></pre><p>You&#8217;ll also need to update the Env interface to include your Durable Object, which you can do by simply running:</p><pre><code>wrangler types</code></pre><p>This will update <strong>worker-configuration.d.ts </strong>in the root to contain the definition for your Durable Object.</p><p>With the Durable Object created, there&#8217;s just one step left: route requests from your Worker to your container!</p><p>To do so, update the fetch method in <strong>src/index.ts </strong>to the following:</p><pre><code>export default {
  async fetch(request, env, ctx): Promise&lt;Response&gt; {
    const container = await getContainer(env.CONTAINER);

    return container.fetch(request);
  },
} satisfies ExportedHandler&lt;Env&gt;;</code></pre><p>Once again, there&#8217;s a surprisingly small amount of code to achieve this. As someone who definitely isn&#8217;t an expert when it comes to containers, I love that all the details have been abstracted away, and the platform is doing the heavy lifting for me.</p><p>All the code needs to do is fetch an instance of your container, which is the first line, and then return the response returned from the container, which is called using fetch.</p><p>The <em>getContainer </em>function is a handy wrapper that effectively gets the nearest available container that&#8217;s ready to serve requests. Should no containers be available, or perhaps the nearest container might be overloaded, the request will be potentially routed further away to another location.</p><p>Because access to a container is via a Durable Object, you can also manually scale containers yourself in the same way you would a Durable Object.</p><p>Let&#8217;s say you wanted to create 3 unique container instances every time a request is handled, you could do that with the following:</p><pre><code>env.CONTAINER.get(idOne)
env.CONTAINER.get(idTwo)
env.CONTAINER.get(idThree)</code></pre><p>You could then call fetch on each one, and you&#8217;d have 3 requests routed to 3 unique containers. This is perfect if you want to isolate code for specific users, for example, but for traditional application traffic, you likely want to use <em>getContainer</em>.</p><p>There&#8217;s one more helper method that allows you to load balance, but it has some pretty severe downsides at launch. Rather than calling <em>getContainer</em>, you can do the following:</p><pre><code>const containerInstance = loadBalance(env.CONTAINER, 5)
return containerInstance.fetch(request);</code></pre><p>Here are the downsides from the docs:</p><blockquote><p>We have provided the loadBalance function as a stopgap solution to route to multiple stateless container instances. It will randomly select one of N instances for each request and route to it. Unfortunately, it has two major downsides:</p><ul><li><p>It requires that the user set a fixed number of instances to route to.</p></li><li><p>It will randomly select each instance, regardless of location.</p></li></ul><p>We plan to fix these issues with built-in autoscaling and routing features in the near future.</p></blockquote><p>Keep in mind this is an early beta, so everything isn&#8217;t perfect yet.</p><p>Okay, enough information, it&#8217;s time to deploy your container to Cloudflare&#8217;s platform. There&#8217;s no crazy commands needed, just the same command as always:</p><pre><code>npm run deploy</code></pre><p>This will build your container, push to to Cloudflare&#8217;s registry, prepare it to serve requests and of course deploy the associated Worker and Durable Object.</p><p>Once deployed, you can go to your Cloudflare dashboard, click Containers under the Compute menu and you&#8217;ll see your list of containers.</p><blockquote><p><strong>An important note:</strong> your Worker is deployed immediately, but the containers will gradually rollout, so you&#8217;ll need to ensure any code is compatible with both versions of your new container code.</p></blockquote><p>Clicking into one shows you an overview of that container</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YxeU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b0c5e3-9268-4069-a126-c839eba02909_2346x1454.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YxeU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b0c5e3-9268-4069-a126-c839eba02909_2346x1454.png 424w, https://substackcdn.com/image/fetch/$s_!YxeU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b0c5e3-9268-4069-a126-c839eba02909_2346x1454.png 848w, https://substackcdn.com/image/fetch/$s_!YxeU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b0c5e3-9268-4069-a126-c839eba02909_2346x1454.png 1272w, https://substackcdn.com/image/fetch/$s_!YxeU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b0c5e3-9268-4069-a126-c839eba02909_2346x1454.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YxeU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b0c5e3-9268-4069-a126-c839eba02909_2346x1454.png" width="1456" height="902" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/06b0c5e3-9268-4069-a126-c839eba02909_2346x1454.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:902,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:201457,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://flaredup.substack.com/i/165476495?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b0c5e3-9268-4069-a126-c839eba02909_2346x1454.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YxeU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b0c5e3-9268-4069-a126-c839eba02909_2346x1454.png 424w, https://substackcdn.com/image/fetch/$s_!YxeU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b0c5e3-9268-4069-a126-c839eba02909_2346x1454.png 848w, https://substackcdn.com/image/fetch/$s_!YxeU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b0c5e3-9268-4069-a126-c839eba02909_2346x1454.png 1272w, https://substackcdn.com/image/fetch/$s_!YxeU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b0c5e3-9268-4069-a126-c839eba02909_2346x1454.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The UI will show the status of your containers, as they need to be distributed across the platform before they can be used. Initially it will show your container as <em>provisioning</em>, once that moves to <em>Ready </em>you can hit your Worker.</p><p>Hit the URL that was output in your terminal for your Worker, and watch as Hello World is served from your container &#127881;</p><p>Early on in the beta, local development wasn&#8217;t possible. However, right before release, local development now works. You can try by running npm run dev, which will build your container and then serve your Worker locally as usual.</p><blockquote><p><em>Enjoyed the article and want to get a head start developing with Cloudflare? I&#8217;ve published a book, Serverless Apps on Cloudflare, that introduces you to all of the platform&#8217;s key offerings by guiding you through building a series of applications on Cloudflare.<br><br>Buy now: <a href="https://pragprog.com/titles/apapps/serverless-apps-on-cloudflare/">eBook</a> | P<a href="https://www.amazon.com/Serverless-Apps-Cloudflare-Solutions-Infrastructure/dp/B0DFNTSMHP">aperback</a></em></p></blockquote><h2>How much does a Cloudflare Container cost?</h2><p>By this point, you&#8217;re probably itching to spin up some containers - I know I was when I first got access. Before you do though, let&#8217;s quickly cover the pricing for containers so you don&#8217;t wake up to a surprising bill when you throw 100 containers at it on day one.</p><p>I won&#8217;t go over the <a href="https://developers.cloudflare.com/workers/platform/pricing/">pricing for Workers</a> and <a href="https://developers.cloudflare.com/durable-objects/platform/pricing/">Durable Objects</a>, but you can naturally expect to billed for those at the normal rates when interacting with containers via these products. Keep in mind, both have a generous free tier.</p><p>At launch, there will be three types of instances to choose from, that come with pre-configured memory, CPU and disk:</p><ul><li><p><strong>dev</strong> provides 256MB of memory, 1/16 vCPU and 2GB of disk space.</p></li><li><p><strong>basic</strong> provides 1GB of memory, 1/4 vCPU and 4GB of disk space.</p></li><li><p><strong>standard</strong> provides 4 GB of memory, 1/2 vCPU and 4GB of disk space.</p></li></ul><p>You can pick which instance type you want in your Wrangler configuration.</p><p>In terms of what you pay, you&#8217;re charged for every 10ms your container runs at the following rates:</p><ul><li><p><strong>Memory:</strong> $0.0000025 per GB-second</p></li><li><p><strong>CPU:</strong> $0.000020 per vCPU-second</p></li><li><p><strong>Disk</strong> $0.00000007 per GB-second</p></li></ul><p>This can be hard to grok in terms of how much you&#8217;ll actually end up paying. To give you a better idea, let&#8217;s say a container is running for 24 hours, you&#8217;d pay:</p><ul><li><p>$0.174096 for <strong>dev</strong></p></li><li><p>$0.672192 for <strong>basic</strong></p></li><li><p>$1.752192 for <strong>standard</strong></p></li></ul><h2>What&#8217;s Coming Next?</h2><p>This is just the beta, and while containers been in development for a while, it&#8217;s a sizeable undertaking to introduce, and there will be further features added.</p><p>Here&#8217;s a quick list of what&#8217;s missing on day 1, and what to expect in the near future:</p><ul><li><p>Logs from the container itself. You can&#8217;t see them in the dashboard nor Wrangler, but you can export them via Logpush if you&#8217;re an enterprise customer.</p></li><li><p>As we&#8217;ve touched on, autoscaling and <em>proper</em> load balancing is not available yet.</p></li><li><p>The Durable Object associated with a container is not co-located in the same location at present. They will be co-located in the future.</p></li><li><p>There&#8217;s a disconnect between Worker deploys and container deploys, they are not in sync. The Worker code will be updated immediately, but the container rollouts are slower. Again, this will be addressed in the future.</p></li><li><p>The limits are pretty low, these will be raised in the future.</p></li></ul><h2>Wrapping Up</h2><p>That rounds out the introduction to containers on Cloudflare, hopefully you now have all the information needed to get started hacking away with your own containers.</p><p>It&#8217;s pretty early for this offering, but the developer experience and ease of getting started is really notable. It&#8217;s lacking some key features - such as autoscaling - but that&#8217;s coming in the future, alongside a bunch of other updates.</p><p>If you&#8217;ve enjoyed the article and found it useful, I&#8217;d really appreciate you giving it a share, or dropping me a comment!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.ashleypeacock.co.uk/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Flared Up! Subscribe for free to receive new posts and keep update to date with everything Cloudflare.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[The Ultimate Guide to Cloudflare's Durable Objects]]></title><description><![CDATA[An In-Depth Exploration of the Technology That's Redefining Stateful Serverless Apps]]></description><link>https://blog.ashleypeacock.co.uk/p/the-ultimate-guide-to-cloudflares</link><guid isPermaLink="false">https://blog.ashleypeacock.co.uk/p/the-ultimate-guide-to-cloudflares</guid><dc:creator><![CDATA[Ashley Peacock]]></dc:creator><pubDate>Fri, 30 May 2025 09:41:23 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/46da4907-d22d-4589-b29b-c964a3c933a3_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sIdi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac48169-854b-4e97-83f0-cbc1bce75041_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sIdi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac48169-854b-4e97-83f0-cbc1bce75041_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!sIdi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac48169-854b-4e97-83f0-cbc1bce75041_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!sIdi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac48169-854b-4e97-83f0-cbc1bce75041_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!sIdi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac48169-854b-4e97-83f0-cbc1bce75041_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sIdi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac48169-854b-4e97-83f0-cbc1bce75041_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9ac48169-854b-4e97-83f0-cbc1bce75041_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2788235,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://flaredup.substack.com/i/161450113?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac48169-854b-4e97-83f0-cbc1bce75041_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sIdi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac48169-854b-4e97-83f0-cbc1bce75041_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!sIdi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac48169-854b-4e97-83f0-cbc1bce75041_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!sIdi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac48169-854b-4e97-83f0-cbc1bce75041_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!sIdi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ac48169-854b-4e97-83f0-cbc1bce75041_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>If you&#8217;ve clicked into this post and wondered &#8220;what on earth is a Durable Object?&#8221;, then you&#8217;re in the right place - and trust me, you&#8217;re not alone, the <em>vast</em> majority of the tech community haven&#8217;t heard of them either.</p><p>And that&#8217;s a huge shame, because they are incredibly powerful and, as far as I know, unique to the Cloudflare developer platform. They do take some getting used to, and the aim of this post is to get you familiar with them, in the hope you can unlock the power they can bring.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.ashleypeacock.co.uk/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Flared Up! Subscribe for free to receive new posts and keep up to date with all things Cloudflare.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><h2>Introduction</h2><h3>What are Durable Objects?</h3><p>Let&#8217;s start out with the easy question, or so you would think, right? Well, I&#8217;ve been working with Cloudflare&#8217;s developer platform for many years, and used Durable Objects frequently, and for a long time, I struggled to explain what one was.</p><p>However, I feel like I&#8217;ve now settled on a consistent definition for them.</p><div class="pullquote"><p>Durable Objects allow you to instantly spin up a (near) infinite number of mini servers across the world, with built-in durable storage, that can hibernate between requests.</p></div><p>It&#8217;s incredibly difficult to write a one-liner to encapsulate everything a Durable Object can do, but this is the best I&#8217;ve come up with. As a teaser for later, they can also:</p><ul><li><p>Provide coordination in multiplayer scenarios (each instance of a Durable Object that you create has a unique ID, and only one instance can exist with that ID globally at any one time)</p></li><li><p>Built-in WebSocket capabilities (a few lines of code to get going!)</p></li><li><p>Able to wake up outside of HTTP requests (these are called alarms)</p></li><li><p>Manage the entire lifecycle of the key entities in your domain, in a way that feels very organic and natural</p></li><li><p>More recently, are the perfect mechanism to create and expose AI Agents</p></li></ul><p>At its core, a Durable Object looks like just like any other class you define in your code in terms of appearance - more on that later. That&#8217;s part of the beauty: you can write what&#8217;s effectively &#8220;normal&#8221; code, and the platform will handle the rest in terms of maintaining any Durable Objects you create, and the networking required to interact with them.</p><h3>Why Durable Objects Matter</h3><p>The Cloudflare developer platform is entirely serverless, and Durable Objects are no different, effectively giving you <strong>stateful serverless applications</strong>. This is actually incredibly rare, when you consider similar (but not really) technologies such as Lambda, that have to use external data storage and retrieve any state when needed.</p><p>To fully understand why Durable Objects are revolutionary, we need to take a step back and understand a little more about the Cloudflare developer platform. It&#8217;s a global platform, with no region restrictions - when you deploy, you deploy to the entire world.</p><p>That might sound unrealistic, but it&#8217;s genuinely what happens. If you deploy a Cloudflare Worker, which is the underlying technology that powers Durable Objects, it can be spun up in any of the 300 cities where Cloudflare has data centres. As you can imagine, this will significantly reduce latency to the end user, and give you more redundancy and failover than you could ever need.</p><p>The same is true for Durable Objects, but with a twist. With Cloudflare Workers, they can be spun up in as many locations as needed. If you&#8217;re handling requests simultaneously from New York, London, Sydney and South Korea, you&#8217;ll likely end up with 4 instances of that Worker in each location.</p><p>With each instance of a Durable Object, as there can only be one instance of that Durable Object with a given ID, it will be created (by default) close to the location of where the first request originates. It will then live there indefinitely, but it can be moved around depending on availability of servers and other factors, as Cloudflare will maintain the state of your Durable Objects in numerous locations worldwide in case it&#8217;s &#8220;home&#8221; server is down.</p><p>This is all very cool, but why does it matter? For the same reasons it matters that a Cloudflare Worker is deployed globally - your Durable Objects can be spun up across the world instantly, and recover from failures in specific data centres. Being able to horizontally scale your application with zero effort is a pretty big win too, if you ask me.</p><p>Compare that with a platform like AWS, where your resources are typically confined to a single region, you then have to worry a lot more about that region going down, alongside larger latency when requests come in from afar.</p><p>I think this all comes to life a bit more if we look at some use cases, so let&#8217;s dive into that next.</p><h3>Common Use Cases for Durable Objects</h3><p>This won&#8217;t be an exhaustive list, but it should hopefully give you an idea of what&#8217;s possible with Durable Objects, and highlight their versatility. Let&#8217;s tackle the obvious one first, that is the go-to use case for Durable Objects: multiplayer.</p><p>Yes, this could mean some sort of multiplayer game, but these days lots of things in the business world are &#8220;multiplayer&#8221;. Google Docs for example, or Miro, where changes made by numerous people working on a single document are all synchronised across clients in real-time.</p><p>The reason Durable Objects shine at multiplayer is two-fold. Firstly, they come with built-in WebSockets, which are the most common way to implement real-time applications on the web. I always thought WebSockets were difficult to setup, and they can be if you manage the infrastructure yourself, but with Durable Objects, you can have WebSockets working in a few lines of code.</p><p>Secondly, because new instances of a Durable Object are created dynamically at runtime, you can create as many as you need on-demand. If a user creates a new document, or a new game lobby, you simply create a new Durable Object, connect all the clients and then you&#8217;ve effortlessly sharded your application that will scale horizontally almost to infinity.</p><p>That second point is actually another use case on its own, and not tied to multiplayer. Multi-tenancy is an incredibly common pattern in today&#8217;s SaaS world, where a large number of users all use the same website or application.</p><p>How do you segregate the data safely and ensure your application can scale to meet demand? You got it: Durable Objects.</p><p>No matter what the SaaS is, you can likely use Durable Objects to represent the key resources that your users can create. Whether it&#8217;s an AI Agent, a hotel reservation system like Booking.com, or data storage like Dropbox - the underlying things that users create can be individual Durable Objects.</p><p>We&#8217;ll touch on it more later, but in the example of the hotel reservation system, you&#8217;d want to avoid double booking when two users submit a reservation at the same time. Durable Objects can prevent this being an issue, as each one is single-threaded and queues up requests without interweaving them. With the state being held within the Durable Object, the first request would secure the booking, and the second request would return an error to the user when the availability was checked.</p><p>Let&#8217;s cover one more, and this could be a very typical application that has no multi-tenancy, no real-time - just your regular CRUD application.</p><blockquote><p><em>Enjoying the article and want to get a head start developing with Cloudflare? I&#8217;ve published a book, Serverless Apps on Cloudflare, that introduces you to all of the platform&#8217;s key offerings by guiding you through building a series of applications on Cloudflare.<br><br>Buy now: <a href="https://pragprog.com/titles/apapps/serverless-apps-on-cloudflare/">eBook</a> | P<a href="https://www.amazon.com/Serverless-Apps-Cloudflare-Solutions-Infrastructure/dp/B0DFNTSMHP">aperback</a></em></p></blockquote><h2>Getting Started with Durable Objects</h2><p>Cover Prerequisites (Cloudflare Account, Wrangler, etc.) (now free!)</p><p>To help you better understand Durable Objects, let&#8217;s take a look at how to create and deploy one. Because I mentioned that they are like mini servers before, you might be imagining we&#8217;ll need to provision something, but we absolutely do not.</p><p>Before you can create and deploy Durable Objects, you&#8217;ll need a <a href="https://dash.cloudflare.com/">free</a> Cloudflare account. You&#8217;ll also need to install <a href="https://developers.cloudflare.com/workers/wrangler/install-and-update/">Wrangler</a>, Cloudflare&#8217;s CLI. Once you&#8217;ve got both, let&#8217;s get creating!</p><h3>Creating a Durable Object</h3><p>The best way to start pretty much any Cloudflare project, is by using <code>npm create</code>, and Durable Objects are no exception. Run the following to create the scaffolding required to use Durable Objects:</p><p><code>npm create cloudflare@latest -- durable-object-starter</code></p><p>You can choose whatever name you like for your project. When you run the command, you&#8217;ll effectively get a menu of options to choose from. Select the following:</p><ul><li><p>Hello World example</p></li><li><p>Worker + Durable Objects</p></li><li><p>TypeScript (you can use JS if you prefer)</p></li><li><p>Yes to using Git</p></li><li><p>No to deploying, we&#8217;ll do that later</p></li></ul><p>If you want to try out other Cloudflare products, feel free to poke around the other menu options and see what&#8217;s available.</p><p>All of the code that&#8217;s related to Durable Objects will be in <code>src/index.ts</code>. The Durable Object definition is pure code, and looks just like any other JavaScript or TypeScript class.</p><pre><code>export class MyDurableObject extends DurableObject&lt;Env&gt; {
  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
  }

  async sayHello(name: string): Promise&lt;string&gt; {
    return `Hello, ${name}!`;
  }
}</code></pre><p>This is what will allow you to effortlessly spin up as many Durable Objects as you need, because you create Durable Objects on-the-fly with code. That makes them scale to the moon, and I also really like that everything is code and you interact with a Durable Object as if it were a regular JavaScript class. We&#8217;ll get into how to create instances and how the communication works later on.</p><p>For now, looking at the Durable Object class definition, there&#8217;s a few things to callout. First, there&#8217;s the constructor, which has two parameters:</p><ul><li><p><strong>ctx</strong> (short for context) is where you&#8217;ll find some of the <a href="https://developers.cloudflare.com/durable-objects/api/state/">bells and whistles</a> that come built-in to a Durable Object. For example, there are methods to interact with WebSockets, and methods to interact with storage (both key-value and SQLite are supported!)</p></li><li><p><strong>env </strong>is a common parameter across Durable Objects and Workers, and you can think of this as like the environment your Durable Object has access to. There are things you&#8217;d expect from other programming languages, like environment variables and secrets, but also Cloudflare-specific magic such as <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/">bindings</a>.</p></li></ul><p>What are bindings, you might ask? This could honestly be an entire post on its own, but think of them as a way to link your compute (a Worker, a Durable Object) to a resource such as a cache, database, queue or, as we&#8217;ll see shortly, a Durable Object. </p><p>One of the really cool things about bindings is, they are injected automatically at runtime, depending on the environment your compute is operating in. You can bind different resources based on development, staging and production, for example. Then at runtime, env will contain objects for you to use to interact with these resources (e.g. reading from a KV cache by calling <code>env.KV_CACHE.get(&#8216;your-key&#8217;)</code>)</p><p>To see this in action, let&#8217;s look at the code that&#8217;s been generated that creates Durable Objects on-the-fly using a Worker:</p><pre><code>export default {
  async fetch(request, env, ctx): Promise&lt;Response&gt; {

    const id: DurableObjectId = env.MY_DURABLE_OBJECT.idFromName("foo");
    const stub = env.MY_DURABLE_OBJECT.get(id);

    const greeting = await stub.sayHello("world");

    return new Response(greeting);
  },
};</code></pre><p>If you&#8217;re new to Cloudflare, and haven&#8217;t seen a Worker before, this is the basic structure of a Cloudflare Worker. The most common way to interact with them is via HTTP, as Workers, by default, are public and accessible by hitting a URL. When a request comes in, the fetch function is called, and you can then handle the HTTP request with standard JavaScript code.</p><p>In the example code above, a binding to the Durable Object we looked at earlier is used to create an instance of that Durable Object, call its <code>sayHello</code> method and return a response.</p><p>A key thing to note is that while the call to <code>sayHello</code> might look like a regular in-memory method call, you&#8217;re actually calling methods on a stub, that makes RPC calls to your Durable Object that is likely sitting in another data centre alongside its storage.</p><p>You can add as many methods as you like to your Durable Object, in the same way you would any other class. There are other built-in capabilities, such as alarms and WebSockets, that we&#8217;ll cover later on.</p><p>If you want to see everything working locally, simply run <code>npm run dev</code> in your terminal, and open the URL shown. Another big feature of Cloudflare&#8217;s developer platform is that you can (nearly always) run everything locally, with Cloudflare simulating bindings locally too.</p><p>This is true for Durable Objects as well. Keep in mind there will be no latency locally, so it&#8217;s not a true representation of the performance you&#8217;ll see once deployed. For that, you can run <code>npm run deploy</code> and your Worker will be deployed to Cloudflare&#8217;s global network, along with your Durable Object.</p><p>Once again, open the URL output in the terminal and your Worker will serve the request by communicating with your Durable Object.</p><p>Now that you understand how to construct and deploy a Durable Object, let&#8217;s take a closer look at some core concepts and the underlying architecture of Durable Objects.</p><h2>Core Concepts of Durable Objects</h2><h3>State Management and Consistency</h3><p>As we&#8217;ve covered already, one of the key aspects of a Durable Object is the fact it comes with built-in durable storage. This storage is colocated with the Durable Object itself, so the latency to its storage is basically non-existent.</p><p>When Durable Objects were first released, they only came with a very simple key-value store. This was useful, but it made storing complex, relational data quite cumbersome.</p><p>In late 2024, a second storage option was released, allowing you to harness the power of SQLite within your Durable Object. As you can imagine, this gives you a lot more freedom and flexibility in how you structure and store your data.</p><p>Additionally, because the storage is colocated with the Durable Object, queries are effectively zero-latency. This is because SQLite is being run not only in the same data centre, but in the exact same thread as the compute for your Durable Object. Database queries are also synchronous, because they are executed so rapidly there&#8217;s no need to make them async.</p><p>If you&#8217;ve ever worked with SQL before, you&#8217;ll likely have heard of the <a href="https://stackoverflow.com/questions/97197/what-is-the-n1-selects-problem-in-orm-object-relational-mapping">N+1 problem</a>, but with SQLite-backed Durable Objects, you can genuinely write these types of queries and not incur any noticeable performance hit.</p><p>In the case of writes, they too are synchronous, but they are still durable and written to disk before a response is returned from your Durable Object. This is where some clever engineering comes in on Cloudflare&#8217;s part, as the code doesn&#8217;t wait for the query to be written to disk before continuing, but you&#8217;re still protected from inconsistencies thanks to what Cloudflare call Output Gates.</p><p>Rather than regurgitate <a href="https://blog.cloudflare.com/sqlite-in-durable-objects/">Kenton&#8217;s blog post</a>, I&#8217;ll just quote him verbatim:</p><blockquote><p>In DOs, when the application issues a write, it continues executing without waiting for confirmation. However, when the DO then responds to the client, the response is blocked by the "Output Gate". This system holds the response until all storage writes relevant to the response have been confirmed, then sends the response on its way. In the rare case that the write fails, the response will be replaced with an error and the Durable Object itself will restart. So, even though the application constructed a "success" response, nobody can ever see that this happened, and thus nobody can be misled into believing that the data was stored.</p></blockquote><p>You can see this in action in the following diagram:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BmWY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11a01a2a-cd49-4397-b85c-e9d49b718c4a_732x734.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BmWY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11a01a2a-cd49-4397-b85c-e9d49b718c4a_732x734.png 424w, https://substackcdn.com/image/fetch/$s_!BmWY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11a01a2a-cd49-4397-b85c-e9d49b718c4a_732x734.png 848w, https://substackcdn.com/image/fetch/$s_!BmWY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11a01a2a-cd49-4397-b85c-e9d49b718c4a_732x734.png 1272w, https://substackcdn.com/image/fetch/$s_!BmWY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11a01a2a-cd49-4397-b85c-e9d49b718c4a_732x734.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BmWY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11a01a2a-cd49-4397-b85c-e9d49b718c4a_732x734.png" width="732" height="734" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/11a01a2a-cd49-4397-b85c-e9d49b718c4a_732x734.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:734,&quot;width&quot;:732,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;BLOG-2536 7&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="BLOG-2536 7" title="BLOG-2536 7" srcset="https://substackcdn.com/image/fetch/$s_!BmWY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11a01a2a-cd49-4397-b85c-e9d49b718c4a_732x734.png 424w, https://substackcdn.com/image/fetch/$s_!BmWY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11a01a2a-cd49-4397-b85c-e9d49b718c4a_732x734.png 848w, https://substackcdn.com/image/fetch/$s_!BmWY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11a01a2a-cd49-4397-b85c-e9d49b718c4a_732x734.png 1272w, https://substackcdn.com/image/fetch/$s_!BmWY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11a01a2a-cd49-4397-b85c-e9d49b718c4a_732x734.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Image credit: <a href="https://blog.cloudflare.com/sqlite-in-durable-objects/">Cloudflare</a></figcaption></figure></div><p>There are other benefits to this approach, such as lower latency when handling requests, as the Durable Object can work on building the response while the storage layer confirms the write, actually speeding up overall request handling time.</p><h3>Isolation and Concurrency Model</h3><p>As previously mentioned, Durable Objects are a special kind of Worker, but they still use the same runtime and how that runtime is operated is the same.</p><p>The runtime is called <a href="https://v8.dev/">V8</a>, and it&#8217;s the same that runs when you&#8217;re using Google Chrome. In terms of isolation, you can think of it like tabs in your browser, where one tab cannot access data in another tab.</p><p>The same is true for Workers and Durable objects, that run in something called an isolate. To quote Cloudflare&#8217;s documentation:</p><div class="pullquote"><p>V8 provides lightweight contexts that provide your code with variables it can access and a safe environment to be executed within. You could even consider an isolate a sandbox for your function to run in.</p></div><p>It&#8217;s actually an incredibly efficient way to run software. There are no containers, and within a single process, you can run hundreds or thousands of isolates, each completely separate and unable to access one another&#8217;s data.</p><p>As an extra benefit, in the same way your tabs open instantly, isolates are near-instant in terms of start time too. That means there&#8217;s rarely cold starts on Cloudflare&#8217;s platform, as your code can be loaded ready to be executed during the TLS handshake.</p><p>Unlike Lambda, isolates are typically rapidly evicted from memory after they have finished executing code. That makes in-memory caching largely useless, but if your Durable Object is busy and handling lots of concurrent requests, it will stay around for a few seconds, in which case you can see benefit from in-memory caching.</p><p>But to really ram the point home, while the storage layer of a Durable Object is persisted between evictions, in-memory data is not. This is important to keep in mind, as looking at the code, you might be fooled into thinking the constructor worked in the same way as a standard class, where you&#8217;re able to initialize and cache data on creation.</p><h3>Location and Global Distribution</h3><p>As with the majority of Cloudflare&#8217;s developer platform, Durable Objects are automatically available globally. They are not available in as many locations as Workers are (330+ cities!) but they are definitely globally-distributed across the continents of the world.</p><p>Their initial location is determined when you first create a Durable Object, and it will be created close to where your Worker is calling from. It might be in the same data centre, but if not, it will be in close proximity.</p><p>Due to the nature of Durable Objects, and the fact they are created dynamically, the assumption here is that whoever is calling your application likely wants to keep accessing the data you&#8217;ll store in a Durable Object.</p><p>For example, consider Google Docs, if you create a document, you&#8217;re likely to be the primary person editing and working on that document. Therefore, to improve the responsiveness of the application, it makes sense the Durable Object lives close to that end user.</p><p>Naturally, there might be occasions where you want to set the location of a Durable Object yourself. Perhaps the person using a SaaS application is in Australia, but the majority of their customers are in the US. In that case, you might want to offer them the ability to create any documents in the US, to reduce latency to their customers.</p><p>This is possible at creation by specifying a location hint when you retrieve a Durable Object:</p><pre><code>let durableObjectStub = DO_NAMESPACE.get(id, { locationHint: "enam" });</code></pre><p>You can see the full list of available locations <a href="https://developers.cloudflare.com/durable-objects/reference/data-location/">here</a>.</p><p>Another quirk of Durable Objects is that once their location is set, it typically won&#8217;t move from that data centre, and you can&#8217;t request it be moved either. If you wanted to move the location of a Durable Object, you&#8217;d need to perform your own migration, where you create new Durable Objects in a different location, and then migrate any storage.</p><p>This doesn&#8217;t mean that if that data centre goes down that your Durable Object is inaccessible though, as Cloudflare can opt to move your Durable Object around if needed. There are backups of your Durable Object held in other locations to facilitate this. This is all managed by the platform though, and again, you have no control over when and if this happens.</p><h2>Durable Objects API</h2><p>With a solid understanding of what Durable Objects are and how they are architected, let&#8217;s take a closer look at the APIs available - there&#8217;s everything from RPC, to WebSockets, to AI agents and more to cover.</p><h3>Synchronous Calls with Fetch &amp; RPC</h3><p>Let&#8217;s start out with the simple stuff: you can synchronous call your Durable Object via fetch or RPC. For a while, there was only fetch, so if you wanted to provide more than one piece of functionality from a Durable Object, you basically had to implement path-based routing in the same way you would a HTTP-based API.</p><p>This had other drawbacks, as you had to serialize/deserialize JSON for the payloads on either side of the call. This isn&#8217;t the end of the world, but it does involve a lot more code versus RPC. Here&#8217;s what a call might look like if you wanted to use a Durable Object for authentication from a Worker:</p><pre><code>// Get the Durable Object
const id = env.MY_DURABLE_OBJECT.idFromName('foo');
let stub = env.MY_DURABLE_OBJECT.get(id);

// Construct a JSON request to the auth service.
let authRequest = {
  cookie: req.headers.get("Cookie")
};

// Send it to the Durable Object
let resp = await stub.fetch(
  "https://auth/check-cookie", {
  method: "POST",
  headers: {
    "Content-Type": "application/json; charset=utf-8",
  },
  body: JSON.stringify(authRequest)
});

if (!resp.ok) {
  return new Response("Internal Server Error", {status: 500});
}

// Parse the JSON result.
let authResult = await resp.json();

// Use the result.
if (!authResult.authenticated) {
  return new Response("Not authenticated", {status: 401});
}
    
let username = authResult.username;</code></pre><p>Considering you likely want to make calls to Durable Objects and other Workers throughout your application, the amount of code needed to make a call rapidly adds up.</p><p>Luckily, <a href="https://blog.cloudflare.com/javascript-native-rpc/">in 2024</a>, Cloudflare added support for calling Durable Objects using RPC (alongside Worker-to-Worker calls as well). For anyone not familiar with RPC, it&#8217;s effectively an alternative to using a protocol such as HTTP, where you make calls on a stub object (that looks like regular method calls), but behind the scenes, RPC handles all the serializing/deserializing for you as well as transmitting the data.</p><p>This ends up removing a lot of the boilerplate code above:</p><pre><code>const id = env.MY_DURABLE_OBJECT.idFromName('foo');
let stub = env.MY_DURABLE_OBJECT.get(id);

// Call the Durable Object
let resp = await stub.authenticate(req.headers.get("Cookie"));

// Use the result.
if (!resp.authenticated) {
  return new Response("Not authenticated", {status: 401});
}
    
let username = resp.username;</code></pre><p>I think we can all agree this is a lot simpler and cleaner. Additionally, let&#8217;s say we wanted this Durable Object to handle authentication and authorisation, we could easily add an authorized method to the Durable Object and call that too. If we were limited to fetch, we&#8217;d have to introduce some path-based routing to handle this.</p><p>It&#8217;s a lot easier to deal with regular objects too, as it means, if you&#8217;re using TypeScript, you can have type definitions for the inputs and outputs and simply pass regular data types back and forth. As long as what you pass is able to be cloned, you&#8217;re good to go.</p><p>You can even pass callbacks in the form of functions, and if that function is called, an RPC call will be made automatically back to the caller.</p><h3>Schedule Execution with Alarms</h3><p>If you need a Durable Object to wake itself up and execute some code on a schedule, then you&#8217;re in luck, as they come with alarms built-in. Much like the alarm you set to wake up in the morning, you simply set an alarm programatically, and the alarm method of your Durable Object will be called at that time.</p><p>To bring this concept to life, here&#8217;s what it looks like in code:</p><pre><code>export class AlarmExample extends DurableObject {
  constructor(ctx, env) {
    this.ctx = ctx;
    this.storage = ctx.storage;
  }

  // Called via RPC
  async schedule() {
    // If there is no alarm currently set, set one in 10 seconds
    let currentAlarm = await this.storage.getAlarm();

    if (currentAlarm == null) {
      this.storage.setAlarm(Date.now() + (10 * 60));
    }
  }

  // Define your logic to execute here
  async alarm() {

  }
}</code></pre><p>As you can see, it uses the underlying storage of the Durable Object to store the alarm. It&#8217;s only possible to set one alarm per Durable Object, but you can write your own logic to manage multiple alarms if you need to.</p><p>Alarms have retries baked in, so if your alarm handler throws an error, it will be automatically retried up to 6 times with exponential backoff.</p><p>If you&#8217;re wondering how you might use an alarm for a real-world use case, let&#8217;s say you&#8217;re building a website that provides stock market data. You could create a Durable Object per company, and schedule an alarm that periodically wakes up (let&#8217;s say every 10 seconds) to retrieve the latest data, storing it within the Durable Object itself.</p><p>This saves you having a cron-like task that loops through hundreds of companies, and has some nice benefits like isolating failures to just one company, if one particular company&#8217;s data isn&#8217;t available.</p><p>Speaking of storage, let&#8217;s take a look at the two flavours of storage available to you when using Durable Objects.</p><h3>Store Simple Data with Key-Value Storage</h3><p>When Durable Objects were released a few years ago, they did so with just one option for storage: a key-value store. Just for clarity, it shouldn&#8217;t be confused with Workers KV - as they are completely different in how they work.</p><p>In the case of a Durable Object, the storage is maintained by the Durable Object itself. Unlike Workers KV, it&#8217;s not globally replicated in a transactional sense, because it doesn&#8217;t need to be due to the fact the DO lives in one place at one time.</p><p>The interface itself will be familiar to anyone who has used a key-value store. Here&#8217;s a simple example using a counter to illustrate the API:</p><pre><code>export class Counter extends DurableObject {
  constructor(ctx, env) {
    super(ctx, env);
  }

  async increment() {
    let value = (await this.ctx.storage.get("value")) || 0;
    value += 1;
    await this.ctx.storage.put("value", value);
    return value;
  }
}</code></pre><p>The key lines here are <code>this.ctx.storage.get("value")</code> and <code>this.ctx.storage.put</code>, which do exactly as they sound: get data and store data.</p><p>Alongside get and put, there&#8217;s delete to remove data, and list to retrieve all the key-value pairs.</p><p>It works well for simple use cases, but can become unwieldy if you need to store more complex data, or even just a large amount that isn&#8217;t suitable for key-value format. This is largely due to the fact the maximum value you can store is 128 KiB per key. Additionally, you can only store 50GB across your entire account, although this can be raised by request.</p><p>If you need more complex storage, then you&#8217;re in luck - let&#8217;s take a look at the second storage option.</p><h3>Store Relational Data with SQLite</h3><p>In September 2024, during Birthday Week, Cloudflare gave us quite the present in the form of SQLite-backed Durable Objects. <a href="https://blog.cloudflare.com/sqlite-in-durable-objects/">This announcement</a> added a second storage option alongside key-value, providing the ability to run queries against a SQLite database.</p><p>The incredibly cool part of this is the storage is effectively zero-latency, as the SQLite database exists in the same thread as your Durable Object. This means you can create schemas, retrieve data and insert data pretty much instantaneously.</p><p>When compared to the key-value storage, the limits are significantly higher for SQLite-backed Durable Objects. You can store up to 10GB per Durable Object, with no limit on the amount of data you can store across your account (note: the free plan limits you to 5GB).</p><p>This sort of relational data format unlocks the ability to treat your key domain entities as living objects, at least in my opinion. There&#8217;s naturally a conversation to be had around vendor lock-in, but imagine being able to represent your key domain entities and have their lifecycle managed by a Durable Object.</p><p>Let&#8217;s say you&#8217;re a chat company, something akin to Zendesk. The key entity in your domain is likely a chat, and with Durable Objects each chat would be its own Durable Object instance.</p><p>All the history would be stored in the Durable Object, and as we&#8217;ll dive into next, the client can connect to a chat via WebSockets directly into the Durable Object. Additionally, let&#8217;s say you want to send a feedback request email 24 hours after the last message is sent - simply define an alarm, and the DO can send that email itself.</p><p>It takes some getting used to, but there&#8217;s something really unique about seeing your application behave as if it&#8217;s a living ecosystem. It&#8217;s a little like the first time you go to see the a theatre show or the ballet, you&#8217;re in awe at the coordination that just seems to magically happen before your eyes.</p><p>So what does a SQLite-backed Durable Object look like? Let&#8217;s take a look:</p><pre><code>export class FlightSeating extends DurableObject {
  sql = this.ctx.storage.sql;

  // Call when the flight is first created to set up the seat map.
  initializeFlight(seatList) {
    this.sql.exec(`
      CREATE TABLE seats (
        seatId TEXT PRIMARY KEY,  -- e.g. "3B"
        occupant TEXT             -- null if available
      )
    `);

    for (let seat of seatList) {
      this.sql.exec(`INSERT INTO seats VALUES (?, null)`, seat);
    }
  }

  // Get a list of available seats.
  getAvailable() {
    let results = [];

    // Query returns a cursor.
    let cursor = this.sql.exec(`SELECT seatId FROM seats WHERE occupant IS NULL`);

    // Cursors are iterable.
    for (let row of cursor) {
      // Each row is an object with a property for each column.
      results.push(row.seatId);
    }

    return results;
  }</code></pre><p>As you can see, you make use of the storage object on the context object within the Durable Object, this time using sql. It uses a cursor for access, which is straightforward to use, alongside regular insert and create statements that are standard within SQLite. The full API can be seen <a href="https://developers.cloudflare.com/durable-objects/api/storage-api/#sql-api">here</a>.</p><p>Additionally, SQLite-backed Durable Objects come with point-in-time recovery. You can restore your SQLite database to any point in time in the last 30 days, using a <a href="https://developers.cloudflare.com/durable-objects/api/storage-api/#pitr-point-in-time-recovery-api">straightforward API</a>.</p><p>Now, you might be thinking, what if I need to add or update the schemas in a Durable Object? Well, thanks to fact that the queries are lightening fast, I actually tend to do any migrations in the constructor when needed on-the-fly.</p><blockquote><p>The constructor will be called each time the Durable Object is woken up. So let&#8217;s say you get a Durable Object with an ID of test, the constructor will be called, and then any subsequent requests won&#8217;t trigger the constructor.</p><p>As a Durable Object will hang around in memory for a little while, if you make lots of requests in quick succession, the constructor will only be called once. As a nice little caching strategy, this means you can store instance variables as a form of <em>very </em>temporary cache, where they will only be present until the Durable Object is evicted from memory.</p></blockquote><p>I&#8217;d like to see some tooling for this baked into the Durable Object API, in the same way D1 databases track which migrations have been applied, it would be helpful if Durable Objects could do this, and then (optionally) auto-apply any migrations needed.</p><p>If you want to reduce the amount of code you have lying around, you could have a script that loops over all your Durable Objects and applies any schema changes, as an alternative strategy - but it will take a while if you have a lot of them, and naturally use up quite a lot of requests.</p><p>It&#8217;s worth noting that while key-value-based Durable Objects will be around for a while, the recommendation is to use SQLite-backed Durable objects, which also still have access to a key-value store.</p><p>We touched on WebSocket support in this section, so let&#8217;s dive a little deeper into that.</p><h3>Built-in WebSocket Support</h3><p>I&#8217;ve mentioned multiplayer a few times, and in the last section mentioned a chat application that could use WebSockets to enable real-time communication. Durable Objects are perfect for this use case because they come with baked-in WebSocket support.</p><p>Before I used Durable Objects, I&#8217;d never used WebSockets, and from what I&#8217;d heard, they could be a bit of a pain to deal with. Now, whether that&#8217;s true or not, I can&#8217;t say as I&#8217;ve never had a reason to use anything beside a Durable Object, it&#8217;s that easy.</p><p>You can genuinely setup a WebSocket-based application in a few lines of code in the server-side, and just use typical connection code on the client side. The interface is really clean too:</p><pre><code>export class WebSocketHibernationServer extends DurableObject {
  async fetch(request) {
    const webSocketPair = new WebSocketPair();
    const [client, server] = Object.values(webSocketPair);

    this.ctx.acceptWebSocket(server);

    return new Response(null, {
      status: 101,
      webSocket: client,
    });
  }

  async webSocketMessage(ws, message) {
    ws.send(
      `[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`,
    );
  }

  async webSocketClose(ws, code, reason, wasClean) {
    ws.close(code, "Durable Object is closing WebSocket");
  }
}</code></pre><p>To quickly explain what&#8217;s happening here, a Worker would invoke the Durable Object using the fetch function when it wants to establish a WebSocket connection. The response contains the client-side WebSocket, as well as a HTTP 101 header which indicates the client wants to switch the protocol from HTTP.</p><p>The Durable Object simply calls <code>acceptWebSocket</code>, and that&#8217;s all there is to it. When a message is received from the client, the <code>webSocketMessage</code> method will be called, and you can handle the incoming message as required.</p><p>You can connect a large number of clients to a single Durable Object instance, with no published limit from Cloudflare. The number of effective WebSockets you can connect to a single instance is going to depend how busy each WebSocket is, and what happens when each message is received.</p><p>Keep in mind that the Durable Object can &#8220;only&#8221; handle 1,000 requests per second at peak, so you may need to shard your Durable Objects to ensure they remain performant.</p><p>Going back to the chat example, let&#8217;s say you have 5 people connected to a single chat. You&#8217;ll want to relay any message sent to all clients, which is easy to do by calling <code>this.ctx.getWebSockets</code>, looping over each one and sending a message.</p><p>You might be thinking, wait, what if I have all the clients connected to my Durable Object, isn&#8217;t that going to cost a fortune as the Durable Object will be active continuously?</p><p>Well, want to know something else cool? Durable Objects, will hibernate between handling WebSocket messages. Let&#8217;s say you have a chat, and no one has sent any messages for 30 minutes, but they have the tab open in their browser. The Durable Object has been hibernating the entire time, and only wakes up when a message is sent. Therefore, you only pay for the wall time (duration) your Durable Object uses to handle incoming WebSocket messages.</p><p>This is a pretty killer feature, and honestly the interface is so simple, I think it&#8217;s really well done, and I thoroughly enjoying building WebSocket-based applications these days.</p><h3>Agents SDK</h3><p>It wouldn&#8217;t be a post in 2025 without some AI, would it?</p><p>It&#8217;s honestly worth checking out <a href="https://agents.cloudflare.com/">agents.cloudflare.com</a> for an overview of the capabilities Cloudflare provides for building agents. Not only is the website beautiful, it does an excellent job of explaining some AI concepts alongside how Cloudflare enables them.</p><p>In effect, the Agents SDK from Cloudflare wraps Durable Objects to provide key capabilities needed to create AI agents. When you think back to what I said about Durable Objects being excellent ways to create living objects in your ecosystem, this is exactly what the Agents SDK is doing for AI agents.</p><p>It comes with baked-in React hooks as well, so you can easily connect clients to your AI agents. If you&#8217;ve been living under a rock lately, and somehow missed the hype, MCP is all the rage right now.</p><p>I&#8217;ll be honest, I&#8217;m not entirely sold on MCP living up to the hype just yet, it feels a little like a bubble currently. I can totally see how it could be huge in the future, but for now, it feels mainly like companies are riding the hype wave and tangible use cases for MCP are few-and-far between.</p><p>Ignoring that little tangent, the Agents SDK comes with MCP support - both in terms of connecting your AI agents to remote MCP servers, as well as hosting your own MCP servers.</p><p>Honestly, the SDK comes with so much, so here&#8217;s a quick roundup:</p><ul><li><p>Server-side and client-side agent classes</p></li><li><p>WebSockets</p></li><li><p>State synchronization</p></li><li><p>Scheduling</p></li><li><p>SQLite support (using SQLite-backed Durable Objects)</p></li><li><p>MCP support</p></li><li><p>Server-Sent Events</p></li><li><p>Browse the web using Puppeteer, via <a href="https://developers.cloudflare.com/browser-rendering/">Browser Rendering</a></p></li></ul><p>And to be honest, there&#8217;s more but I highly recommend checking out the website for more information plus the <a href="https://developers.cloudflare.com/agents/">docs</a>.</p><h2>How can I observe what my Durable Object is doing?</h2><p>One of the key aspects to using any technology is how easy is it to understand what it&#8217;s doing, and investigating issues when they arise?</p><p>I will admit, one area that is in need of some love is the observability for Durable Objects. It has steadily improved, especially with the introduction of <a href="https://developers.cloudflare.com/workers/observability/logs/workers-logs/">Workers Logs</a>, which allows you to query and filter logs across your Workers and Durable Objects.</p><p>With the <a href="https://blog.cloudflare.com/cloudflare-acquires-baselime-expands-observability-capabilities/">acquisition of Baselime</a>, the observability aspect of the Cloudflare platform has steadily improved year-on-year, and I know there are more things in the works.</p><p>The data side is more tricky, however. How do you view what each Durable Object contains within its storage? Well, while it&#8217;s not particularly easy right now, there is growing tooling for it.</p><p>Outerbase was acquired by Cloudflare this year, and they already provide <a href="https://www.outerbase.com/developers/outerbase-studio/">Outerbase Studio</a> that can be <a href="https://github.com/outerbase/browsable-durable-object">configured to access Durable Objects</a> using SQL. With the acquisition, I&#8217;m hoping this functionality finds its way into the Cloudflare ecosystem, hopefully allowing you to query data held across your Durable Objects.</p><h2>How much does running a Durable Object cost?</h2><p>At this point, hopefully you&#8217;re excited to try out Durable Objects. Before you do though, you probably want to know how much it&#8217;s going to cost you, right?</p><p>Well, I&#8217;ve got you covered. The good news is Durable Objects are recently available on the free tier, meaning you get 100,000 requests per day for free and 13,000 GB-s per day too.</p><p>If you&#8217;re confused by the GB-s, you&#8217;re not alone. The GB-s metric means <strong>1 GB used continuously for 1 second</strong>. It's calculated by multiplying the amount of storage or memory used (in GB) by the duration it's utilized (in seconds).</p><p>Keep in mind you&#8217;re charged for the maximum memory available, not the memory you actually allocate. That means on the free plan you can run a little over one Durable Object for the entire day, and not get charged. Assuming my calculations are correct, you get just under 29 hours of Durable Object duration per day for free.</p><p>Let&#8217;s say you really love Durable Objects, you build all your applications with it, and suddenly you&#8217;re processing a ton through them. Here&#8217;s what it&#8217;s going to cost you:</p><ul><li><p>$0.15 per million requests</p></li><li><p>$12.50 per million GB-s</p></li></ul><p>Helpfully, there&#8217;s a reduced cost for WebSocket messages, which aren&#8217;t billed 1:1 in terms of requests:</p><blockquote><p>There is no charge for outgoing WebSocket messages, nor for incoming WebSocket protocol pings. For compute requests billing-only, a 20:1 ratio is applied to incoming WebSocket messages to factor in smaller messages for real-time communication. For example, 100 WebSocket incoming messages would be charged as 5 requests for billing purposes.</p></blockquote><p>There are separate charges for the storage used too. Starting out with the key-value option, which doesn&#8217;t have a free plan, but does come included in the $5/month Workers Paid plan:</p><ul><li><p>1 million read requests/month (+$0.20/million after)</p></li><li><p>1 million write requests/month (+$1.00/million after)</p></li><li><p>1 million delete requests/month (+$1.00/million after)</p></li><li><p>1 GB of stored data (+ $0.20/ GB-month after)</p></li></ul><p>Moving onto the SQLite-backed Durable Objects, which do have a free tier:</p><ul><li><p>5 million rows read/day</p></li><li><p>100,000 rows write/day</p></li><li><p>5GB total storage</p></li></ul><p>The limits then get significantly bumped on the Workers Paid plan, with additional charges beyond that:</p><ul><li><p>25 billion rows read/month (+$0.001 / million rows after)</p></li><li><p>50 million rows written/month (+$1.00 / million rows after)</p></li><li><p>5GB total storage (+$0.20/GB-month)</p></li></ul><p>That&#8217;s a lot of billing information I know, but I think it&#8217;s good to consider and dive into a little. We&#8217;ll round out this post by looking at real-world examples and some use cases featuring Durable Objects.</p><h2>Real-world Examples and Case Studies</h2><p>I&#8217;m going to share a few websites that I&#8217;ve come across that use Durable Objects. I&#8217;d honestly love to see more companies who are using them share their stories, both the good and the bad, as the blog articles sharing insights are few and far between.</p><p>While Durable Objects are still spreading in terms of usage, I know for sure they are used far more widely than it visible online. Having said that, here&#8217;s a few:</p><ul><li><p><a href="https://www.tldraw.com/">tldraw</a> is a white boarding experience, offering an infinite canvas and uses WebSockets to synchronize all client activity.</p></li><li><p><a href="https://www.gitlip.com/">Gitlip</a> is a collaborative coding platform, utilizing Durable Objects to house the underlying code for each repository. There&#8217;s an excellent deep dive on how they built it <a href="https://www.gitlip.com/blog/infinite-git-repos-on-cloudflare-workers">here</a>.</p></li><li><p><a href="https://blog.cloudflare.com/building-waiting-room-on-workers-and-durable-objects/">Waiting Room</a> is a product from Cloudflare, and I&#8217;m highlighting this because a ton of Cloudflare products are effectively built on Durable Objects. This blog article provides good insights into how and why they are using them for this common use case</p></li></ul><blockquote><p><em>Enjoyed the article and want to get a head start developing with Cloudflare? I&#8217;ve published a book, Serverless Apps on Cloudflare, that introduces you to all of the platform&#8217;s key offerings by guiding you through building a series of applications on Cloudflare.<br><br>Buy now: <a href="https://pragprog.com/titles/apapps/serverless-apps-on-cloudflare/">eBook</a> | P<a href="https://www.amazon.com/Serverless-Apps-Cloudflare-Solutions-Infrastructure/dp/B0DFNTSMHP">aperback</a></em></p></blockquote><h2>Wrapping Up</h2><p>That brings us to the end of my ultimate guide to Durable Objects, if you made it this far, thank you for reading and I hope you found the content useful.</p><p>I truly think Durable Objects allow you to build applications in completely different and novel ways, and while they take some getting used to, they are incredibly powerful and I really hope we see more and more examples of them used in the wild.</p><p>If you have any questions or think I missed something, let me know and I&#8217;ll do my best to respond and update the article, and likewise if you think something is incorrect!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.ashleypeacock.co.uk/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Flared Up! Subscribe for free to receive new posts and keep update to date with everything Cloudflare.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[What's New on Cloudflare's Developer Platform (April)]]></title><description><![CDATA[AutoRAG gets smarter, KV goes bulk, Python Workers cron support, and Queues throughput upgrade]]></description><link>https://blog.ashleypeacock.co.uk/p/whats-new-on-cloudflares-developer</link><guid isPermaLink="false">https://blog.ashleypeacock.co.uk/p/whats-new-on-cloudflares-developer</guid><dc:creator><![CDATA[Ashley Peacock]]></dc:creator><pubDate>Sat, 03 May 2025 12:03:41 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde712f5-8b61-44e7-9bec-989b9a997e51_2446x2446.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to a recap of everything announced in April on the Cloudflare developer platform, which will exclude Developer Week, as I covered that extensively <a href="https://flaredup.substack.com/p/developer-week-2025-recap-everything?r=5i6ho6">here</a>.</p><p>It can be hard to keep up to date on everything shipped, so I&#8217;m hoping this bite-sized post can keep you in the know, and of course I&#8217;ll add my own commentary about the changes too.</p><p>I&#8217;m still working out exactly what topics and cadence I&#8217;ll share on FlaredUp, but I&#8217;d like to post a new piece fortnightly at the very least. There will be a variety of pieces, including recaps of the latest developments, case studies, deep dives on Cloudflare technologies, as well as tutorials. Some pieces, like this one, will be relatively small, but others - such as the Developer Week post shared above - will be a lot larger.</p><p>Ultimately, I&#8217;m doing this in my spare time, so it will always be best-effort, but I&#8217;m hoping it can become <em>the</em> source for high quality content on the Cloudflare developer platform.</p><p>That&#8217;s enough about the Substack, let&#8217;s dive into the latest developments.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.ashleypeacock.co.uk/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Flared Up! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><h2>&#128270; Metadata Filter Support Comes to AutoRAG</h2><p>For those who didn&#8217;t catch Developer Week, AutoRAG was one of the new products announced. I dived into a lot of detail about it <a href="https://flaredup.substack.com/i/161254492/simplifying-rag-pipelines-with-autorag">here</a>, but in short it&#8217;s a service that manages every aspect required to build a RAG pipeline.</p><p><em>Every </em>might be a stretch considering it&#8217;s in beta, and there are some features that it&#8217;s lacking, but if you&#8217;re looking for a starting point to build a RAG-based solution, AutoRAG is going to do the heavy lifting for you.</p><p>Plus, Cloudflare has a track record of releasing early in beta, and rapidly iterating and adding features, as we are about to see. I have no doubts that more features will come, and it will truly cover every aspect required for RAG pipelines.</p><p>To summarise AutoRAG, you upload all the information you want to be able to retrieve during RAG to an R2 bucket, create a new AutoRAG that links to that R2 bucket, and Cloudflare handles the rest. It will retrieve the content from R2, parse it, chunk it, generate embeddings, store and index them in Vectorize, and provide a simple interface (SDK/API) to then retrieve content at runtime.</p><p>When AutoRAG was released, one key feature it was missing was the ability to filter for certain information before calculating similarity scores. Not being able to filter meant you needed to create multiple AutoRAGs to segregate data, but that would become quickly untenable. This would be particularly true in a multi-tenant application - as you&#8217;d need to continually add AutoRAG bindings to your Worker, and that&#8217;s just not going to scale.</p><p>With the addition of filtering, you&#8217;re able to filter by folder and timestamp. This allows you to group related information into folders within R2 - or designate a folder per tenant - and then specify the folder you wish to use at runtime, or only include records after a given timestamp for example.</p><p>This is a good first step for filtering, but I think it really needs multi-folder support (at present, you can only filter on a single folder, and it doesn&#8217;t include subfolders). It does however unlock multi-tenancy, and that has been a hotly-requested feature, so kudos to Cloudflare for adding it so quickly after release.</p><p>Additionally, being able to tag a single document with multiple pieces of metadata seems pretty vital too - otherwise you might end up duplicating information all over the place just to adequately filter. I&#8217;ve worked with AWS Bedrock Knowledge Bases extensively, and they allow you to provide a metadata file alongside each document, which works pretty well. I&#8217;d like to see something similar here to make the filtering a little more advanced and more easily configurable.</p><p>Having said that, it&#8217;s exceptionally easy to implement AutoRAG. I managed to rip out so much code from one of my applications, which involves RAG, and simply replace it with AutoRAG in a matter of minutes.</p><h2><strong>&#128218; Workers KV now has Support for Bulk Reads</strong></h2><p>For the longest time, you could only read one key at a time from Workers KV. This meant that, should you need to read multiple keys, you&#8217;d need to parallelise them yourself - or do them sequentially and take the performance hit.</p><p>If you&#8217;re not familiar with Workers KV, it&#8217;s one of the oldest Cloudflare developer products and provides a globally-distributed key-value store. In terms of its interface, you can think of it in the same way you would Redis. That is where the similarities end though, because unlike Redis, which stores data in-memory, Workers KV runs off of disk.</p><p>That naturally makes it slower, but it comes with some benefits. Workers KV is replicated globally automatically, versus Redis you&#8217;re going to need to configure read replicas and likely manage that yourself.</p><p>Getting into the weeds of how Workers KV operates is a little beyond this post, but I&#8217;d recommend reading the <a href="https://developers.cloudflare.com/kv/concepts/how-kv-works/">product page</a> if you&#8217;re interested. It&#8217;s designed for high-read use cases, and with KV being replicated globally, that makes the reads incredibly quick still, as your data will be cached close to end users.</p><p>Writes will always take a little longer, as those need to always go to the central data stores. It&#8217;s also worth noting KV operates an eventually consistent model, so it is possible to write-then-read (from another part of the world from the write) and read an older value.</p><p>Back to the latest changes, and it&#8217;s now possible to read up to 100 keys from KV in a single request. Considering you could parallelise the requests in the past, why is this a big deal?</p><p>Because Workers have a built-in limit of 6 simultaneous outbound connections, meaning if you need to read more than 6 keys, you&#8217;ll effectively only be able to read 6 in parallel and any requests beyond that will be queued. That&#8217;s going to make pulling a high number of keys incredibly slow, even if the keys are cached close to where your request originates.</p><p>This problem goes away with bulk reads though, as you can read 100 keys with a single outbound request, removing the need to do any form of parallelisation yourself (unless you&#8217;re reading <em>a lot </em>of keys<em>)</em> and getting around the outbound connection limit.</p><p>The SDK update is really clean to enable this, as it&#8217;s as simple as passing an array of keys to a <code>get</code> request rather than a single string:</p><pre><code>const keys = ["key-a", "key-b", "key-c", ...]
const values = await env.KV_NAMESPACE.get(keys);</code></pre><p>This is one of those really nice quality of life updates that often get overlooked, but help to move the platform forward and remove blockers to adopting the platform.</p><blockquote><p><em>Enjoying the article and want to get a head start developing with Cloudflare? I&#8217;ve published a book, Serverless Apps on Cloudflare, that introduces you to all of the platform&#8217;s key offerings by guiding you through building a series of applications on Cloudflare.<br><br>Buy now: <a href="https://pragprog.com/titles/apapps/serverless-apps-on-cloudflare/">eBook</a> | P<a href="https://www.amazon.com/Serverless-Apps-Cloudflare-Solutions-Infrastructure/dp/B0DFNTSMHP">aperback</a></em></p></blockquote><h2><strong>&#9201;&#65039; Python Workers can now Utilise Cron Triggers</strong></h2><p>If you need to run tasks on a schedule with Cloudflare, the easiest way is to use a cron trigger. You could use <a href="https://developers.cloudflare.com/workflows/">Workflows</a> or <a href="https://developers.cloudflare.com/durable-objects/">Durable Objects</a> too, but for simple, recurring tasks, a cron trigger works pretty nicely.</p><p>For as long as I can remember, cron triggers have been supported on JavaScript-based Cloudflare Workers. Until recently, JavaScript (and TypeScript) were the only native languages supported for Cloudflare Workers.</p><p>However, <a href="https://blog.cloudflare.com/python-workers/">last year</a>, Cloudflare added Python as a native option for writing Workers. Python Workers are slowly catching up to the capabilities available for their JS counterparts, and the latest addition is the ability to execute cron triggers with Python.</p><blockquote><p>It&#8217;s worth noting, you can also write Workers in any language that supports <a href="https://developers.cloudflare.com/workers/runtime-apis/webassembly/">WebAssembly</a> too!</p></blockquote><p>As with most things on the Cloudflare developer platform, the developer experience (DX) for implementing a cron trigger couldn&#8217;t be simpler. There&#8217;s no faffing about with crontabs, you simply configure the cron&#8217;s schedule in your Worker&#8217;s wrangler.toml:</p><pre><code>[triggers]
crons = [ "*/3 * * * *", "*/15 * * * *" ]</code></pre><p>And then add a function to your Python Worker that will be called whenever a cron schedule occurs:</p><pre><code>from workers import handler

@handler
async def on_scheduled(event, env, ctx):
    if event.cron == "*/3 * * * *":
        await do_something_3()
    elif event.cron == "*/15 * * * *":
        await do_something_15()

    print("cron processed")
</code></pre><p>It&#8217;s as simple as that. The <code>on_scheduled</code> function will be called on the schedule you set, and you can execute whatever code you need, with access to the <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/">bindings</a> your Worker has too.</p><blockquote><p>If the Python code above is invalid, blame ChatGPT. I took the JS code from one of my projects and had it convert it. Besides that code snippet, and as always, every word in this post is written by hand with no AI involvement.</p></blockquote><h2><strong>&#128200; Increased limits for Queues pull consumers</strong></h2><p>This one is pretty straightforward, so I won&#8217;t spend too much on it, but if you&#8217;re using, or look at using, Cloudflare Queues from your existing infrastructure, your available throughput just got a significant increase.</p><p>Cloudflare Queues are exactly what they sound like: message queues. You publish a message to a queue, and then a consumer can pull messages off the queue and process them as needed. As with everything on the developer platform, they are serverless, so you just create a queue and Cloudflare handles the rest.</p><p>The most common approach to consume from a queue is using a Worker. Much like the cron triggers above, you simply add a <code>queue</code> function to your Worker, bind a queue to that Worker, and Cloudflare will invoke that method whenever messages are available.</p><p>The throughput for Worker-based consumers has been 5,000 messages/second per queue for a while now, but there is a second way you can consume messages from a queue - HTTP-based pull consumers. Rather than deploying a Worker, you could use your existing infrastructure to poll the queue, using Cloudflare&#8217;s REST API for messages.</p><p>Previously, pull consumers were rate limited to 1,200 requests / 5 minutes across all queues - significantly lower than the throughput available for a Worker consumer. With this change though, pull-based consumers now have the same throughput available as Workers, at 5,000 messages/second per queue.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.ashleypeacock.co.uk/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.ashleypeacock.co.uk/subscribe?"><span>Subscribe now</span></a></p><h2>Wrapping Up</h2><p>That wraps up everything in April outside of Developer Week. As you can imagine, there was a huge focus on Developer Week drops, so it&#8217;s unsurprising the rest of April was relatively quiet.</p><p>Even so, the additions above are still meaningful and push the platform forward, and this is what happens every single month.</p><p>I&#8217;ll try to do one of these every two weeks, but they may push out to a monthly cadence depending on how much content is available, and what other content I&#8217;m working on.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.ashleypeacock.co.uk/p/whats-new-on-cloudflares-developer?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.ashleypeacock.co.uk/p/whats-new-on-cloudflares-developer?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><blockquote><p><em>Enjoying the article and want to get a head start developing with Cloudflare? I&#8217;ve published a book, Serverless Apps on Cloudflare, that introduces you to all of the platform&#8217;s key offerings by guiding you through building a series of applications on Cloudflare.<br><br>Buy now: <a href="https://pragprog.com/titles/apapps/serverless-apps-on-cloudflare/">eBook</a> | P<a href="https://www.amazon.com/Serverless-Apps-Cloudflare-Solutions-Infrastructure/dp/B0DFNTSMHP">aperback</a></em></p></blockquote>]]></content:encoded></item><item><title><![CDATA[Developer Week 2025 Recap: Everything Cloudflare Just Shipped]]></title><description><![CDATA[The Announcements, Innovations, and What They Mean for Developers]]></description><link>https://blog.ashleypeacock.co.uk/p/developer-week-2025-recap-everything</link><guid isPermaLink="false">https://blog.ashleypeacock.co.uk/p/developer-week-2025-recap-everything</guid><dc:creator><![CDATA[Ashley Peacock]]></dc:creator><pubDate>Mon, 14 Apr 2025 11:52:43 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde712f5-8b61-44e7-9bec-989b9a997e51_2446x2446.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The past week has been a whirlwind if you&#8217;re following the developments over at Cloudflare. For the unaware, Cloudflare has several Innovation Weeks each year focussed on a specific area of the platform.</p><p>A few weeks ago it was Security Week, and this week it was my personal favourite: Developer Week. Alongside Birthday Week later in the year, this is when we typically hear about all the latest and greatest features &amp; products coming to Cloudflare&#8217;s Developer Platform.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.ashleypeacock.co.uk/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Flared Up! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>This week&#8217;s Developer Week did not disappoint, and I am going to go over everything announced, explain what they are, why they are important and what you can use them for.</p><p>To give you an idea of the scale of the announcements, I will cover:</p><ul><li><p>9 new products, 7 of those are available to use now</p></li><li><p>12 updates &amp; new features to existing products</p></li><li><p>3 products &amp; features maturing from beta to GA</p></li></ul><p>We have a lot to cover, so let&#8217;s dive in.</p><blockquote><p><em>For avoidance of doubt, this is entirely written by hand, outside of AI providing some definitions for some of the Realtime stuff</em></p></blockquote><h3>&#128640; New Products</h3><p>We&#8217;ll kick off the recap with all the new products announced this week. There&#8217;s something for everyone here, from Containers to VPCs to Secrets Store to Real Time and everything in-between.</p><h4>Cloudflare&#8217;s Container Platform Launches in June 2025</h4><p>During Cloudflare&#8217;s Birthday Week in September 2024, they surprised the world with an announcement not many were expecting: you would soon be able to run containerised workloads on Cloudflare&#8217;s global network.</p><p>In contrast to their existing offering, which is focussed on lightweight, serverless workloads, this opened the door to a whole host of new applications on Cloudflare&#8202;&#8212;&#8202;including applications you&#8217;re currently running on other cloud providers such as AWS and Azure.</p><p>As you can imagine, there was <em>a lot </em>of hype around this announcement. Containers are the go-to mechanism for packaging up applications these days, and when combined with Cloudflare&#8217;s focus on developer experience, it could really revolutionise the market.</p><p>Fast forward to this week&#8202; and &#8202;we got another update on containers with more implementation details, this time with a release date for the open beta: June 2025.</p><p>So what are containers going to look like on Cloudflare? Well, for the most part, much like how you use the rest of the platform. The interface is exactly the same as a Durable Object (more on that later):</p><pre><code>import { Container } from "cloudflare:workers";

// create a new ID for a container
const id = env.CODE_EXECUTOR.idFromName(sessionId);
const sandbox = env.CODE_EXECUTOR.get(id);

// execute a request on the container
return sandbox.fetch("/some-endpoint");

// define your container using the Container class
export class CodeExecutor extends Container {
  defaultPort = 8080;
  sleepAfter = "1m";
}</code></pre><p>When the container receives its first fetch request, it will boot up and handle that request. It will then stay alive for as long as you configure in your class that extends Container.</p><p>Much like Cloudflare Workers, the container will be spun up as close to where the request originates as possible&#8202;&#8212;&#8202;reducing latency between your Workers and any containers.</p><p>Anyone who has worked with containers before knows that you need a set of instructions on how to build the container, with the most common being Docker and a Dockerfile.</p><p>That&#8217;s exactly how it will work on Cloudflare, with any OCI compatible container image supported. You simply run <code>wrangler deploy</code> , and Cloudflare will build and push your container image to their registry, ready to be used by your application.</p><p>Now, you might be thinking: what about cold starts? Well, depending how you configure your containers, you may experience cold starts while the container boots.</p><p>However, there is configuration available to set a minimum number of instances&#8202;&#8212;&#8202;this means Cloudflare pre-warms your containers so they are ready to serve requests immediately. Alongside prewarming, you can also set a CPU threshold that will define when your containers scale up to meet demand&#8202;&#8212;&#8202;meaning autoscaling is built-in to the platform.</p><p>Earlier I mentioned that the interface was that of a Durable Object, so it might not surprise you to hear that each container instance has a companion Durable Object. In essence, the Durable Object is acting as a sidecar, allowing you to communicate with and manage its companion container.</p><p>For example, from the Durable Object, you can:</p><ul><li><p>Start a container</p></li><li><p>Stop a container</p></li><li><p>Execute a bash script</p></li><li><p>Define callbacks for some events (e.g. container stops, container errors)</p></li><li><p>Allow WebSocket connections to your container</p></li></ul><p>Before moving on, let&#8217;s touch on pricing quickly, with these costs being currently advertised:</p><ul><li><p>Memory: $0.0000025 per GB-second</p></li><li><p>CPU: $0.000020 per vCPU-second</p></li><li><p>Disk $0.00000007 per GB-second</p></li></ul><p>Unlike the rest of the Cloudflare platform, there will be charges for egress. You&#8217;ll get 1TB of transfer for free every month, and then the pricing after that is TBC.</p><p>[<a href="https://blog.cloudflare.com/cloudflare-containers-coming-2025/">full article</a>]</p><blockquote><p><em>Enjoying the article and want to get a head start developing with Cloudflare? I&#8217;ve published a book, Serverless Apps on Cloudflare, that introduces you to all of the platform&#8217;s key offerings by guiding you through building a series of applications on Cloudflare.<br><br>Buy now: <a href="https://pragprog.com/titles/apapps/serverless-apps-on-cloudflare/">eBook</a> | P<a href="https://www.amazon.com/Serverless-Apps-Cloudflare-Solutions-Infrastructure/dp/B0DFNTSMHP">aperback</a></em></p></blockquote><h4>Global Virtual Private Clouds Available Later This Year</h4><p>On the face of it, the announcement of VPCs might not <em>seem</em> the most exciting thing ever.</p><p>However, I&#8217;ve had countless conversations with companies, small and large, that want to use Cloudflare&#8217;s Developer Platform and they simply can&#8217;t&#8202;&#8212;&#8202;because their AWS resources, for example, are within a VPC that Cloudflare Workers cannot securely connect to.</p><p>You can expose your resources running in an AWS VPC to the wider internet, but if it&#8217;s an internal application, you&#8217;re then exposing your internal application simply to connect it to Cloudflare&#8202;&#8212;&#8202;which is far from ideal. You could also toy around with Cloudflare Tunnels and Zero Trust, but that&#8217;s a lot of work, and has to be done for each application.</p><p>In terms of connecting from a Cloudflare Worker to a private API in an external VPC, it will be handled using a new binding:</p><pre><code>const response = await env.WORKERS_VPC_RESOURCE.fetch(&#8220;/api/users/342&#8221;)</code></pre><p>Likewise, you can connect from an external VPC to a Workers VPC using standardised URL patterns (e.g. https://&lt;account_id&gt;.r2.cloudflarestorage.com.cloudflare-workers-vpc.com).</p><p>Along a similar vein, at present, your Workers on Cloudflare all run globally&#8202;&#8212;&#8202;there are no VPCs. With the introduction of Workers VPC though, you can now segment your Workers into VPCs, significantly improving security.</p><p>That means you can, if you wish, segment different applications into their own VPCs&#8202;&#8212;&#8202;prevent access to its internal data stored on services such as R2 from other systems.</p><p>If you&#8217;re keen to get your hands on the new VPC functionality, you&#8217;ll have to wait for more news later in the year, when I suspect it will be available in some form of beta.</p><p>[<a href="https://blog.cloudflare.com/workers-virtual-private-cloud/">full article</a>]</p><h4>Simplifying RAG Pipelines with AutoRAG</h4><p>If you&#8217;ve ever worked on a RAG pipeline, you&#8217;ll know what a nightmare it is to setup. I actually spoke about this exact problem at Cloudflare Connect in London this week&#8202;&#8212;&#8202;as I&#8217;ve worked with RAG a lot in my day job.</p><p>On the face of it, you just need some documents, some embeddings and a vector database, right?</p><p>Well, if only it were that simple. What if your source documents are 100&#8217;s of pages long? You need to chunk them, and likely ensure there are overlaps between the chunks. What if your source documents are in a variety of formats? You need a parser for each one, potentially including image recognition if you have images. That&#8217;s just the initial ingestion, what about ensuring your source documents stay in sync with your data stores in an efficient manner, and ensuring deleted documents are removed from your vector database?</p><p>I could go on, but you get the idea. The asynchronous aspect of RAG, managing the underlying data, is surprisingly complex&#8202;&#8212;&#8202;and not something you want to build yourself.</p><p>Luckily for you and I, Cloudflare has released AutoRAG in beta, providing everything need to get started with adding RAG to your application. On the ingestion side, it handles:</p><ul><li><p>Chunking</p></li><li><p>Parsing</p></li><li><p>Indexing</p></li><li><p>Storage</p></li><li><p>Retrieval</p></li><li><p>LLM Generation</p></li></ul><p>All that&#8217;s required from your side is an R2 bucket containing the information you want to be available to your LLM, and AutoRAG will handle the rest&#8202;&#8212;&#8202;including keeping all the data in sync and re-indexing when needed.</p><p>Under the hood, AutoRAG uses R2, Vectorize and Workers AI in combination to provide that RAG pipeline.</p><p>On the application side, you can either opt to retrieve the raw chunks via AutoRAG and manually call an LLM with that context, or you can have AutoRAG call an LLM for you as part of a single call and pass the retrieved context automatically.</p><p>At present, only Workers AI models are available for embedding and LLM generation, so if you want to use OpenAI or Anthropic models, you&#8217;ll just want to make use of the retrieval aspect for now.</p><p>Keep in mind, as with all the products that are heading into beta, that they are just that&#8202;&#8212;&#8202;a beta, an early look, and not the finished products. You&#8217;ll likely experience low limits (such as 1MB max file sizes for AutoRAG) and some errors here and there&#8202;&#8212;&#8202;but these things will be ironed out before each product graduates to being generally available (GA).</p><p>I&#8217;ve actually tried this one out already, and I managed to implement RAG into an existing AI-powered application in literally 15 minutes, as AutoRAG just does so much of the heavy lifting.</p><p>In terms of pricing, it&#8217;s not explicitly mentioned in the article, but my assumption would be you&#8217;ll just pay for the underlying resources you use from R2, Vectorize and Workers AI, with no costs for using AutoRAG itself&#8202;&#8212;&#8202;but that&#8217;s just my intuition, rather than an official line.</p><p>[<a href="https://blog.cloudflare.com/introducing-autorag-on-cloudflare/">full article</a>]</p><h4>Managed Apache Iceberg Tables with R2 Catalog</h4><p>Switching gears a little, and moving over the data side of the application lifecycle, we have what could be a significant entry in the data warehouse space.</p><p>You could argue this is adding a feature to R2 versus a new product, but I think it&#8217;s a completely different use case being unlocked, so I granted it a space in this section.</p><p>If you&#8217;ve ever seen your company&#8217;s bill for the data storage used to provide data to the likes of Snowflake, AWS Redshift and Databricks, you&#8217;ll be surprised how expensive it can be.</p><p>Last year, AWS announced <a href="https://aws.amazon.com/about-aws/whats-new/2024/12/amazon-s3-tables-apache-iceberg-tables-analytics-workloads/">S3 Tables</a> which also provide Iceberg capabilities on top of S3&#8202;&#8212;&#8202;the first time cloud object storage had been able to do this, and now Cloudflare has entered the game with their offering.</p><p>This isn&#8217;t my area of expertise, so rather than attempt to explain, I&#8217;ll let Cloudflare explain with this excerpt from their article:</p><blockquote><p><em>If you&#8217;re not already familiar with it, Iceberg is an open table format built for large-scale analytics on datasets stored in object storage. With R2 Data Catalog, you get the database-like capabilities Iceberg is known for&#8202;&#8212;&#8202;<a href="https://en.wikipedia.org/wiki/ACID">ACID</a> transactions, schema evolution, and efficient querying&#8202;&#8212;&#8202;without the overhead of managing your own external catalog.</em></p><p><em>R2 Data Catalog exposes a standard Iceberg REST catalog interface, so you can connect the engines you already use, like <a href="https://py.iceberg.apache.org/">PyIceberg</a>, <a href="https://www.snowflake.com/">Snowflake</a>, and <a href="https://spark.apache.org/">Spark</a>. And, as always with R2, there are no egress fees, meaning that no matter which cloud or region your data is consumed from, you won&#8217;t have to worry about growing data transfer costs.</em></p></blockquote><p>One thing that did immediately strike me was how much more cost-effective this will be than most other solutions, thanks to R2&#8217;s zero egress fees. If you&#8217;re using S3 currently, which does charge for egress, you could save a pretty penny by switching to R2.</p><p>On the Cloudflare side, opting your R2 bucket into being a data catalog is a one-liner with Wrangler (or use the dashboard):</p><pre><code>npx wrangler r2 bucket catalog enable my-bucket</code></pre><p>Once that&#8217;s complete, you can then connect using whatever mechanism you typically use&#8202;&#8212;&#8202;such as <a href="https://py.iceberg.apache.org/">PyIceberg</a> and <a href="https://arrow.apache.org/docs/index.html">PyArrow</a>.</p><p>R2 Data Catalog is in open beta, and free while in open beta too. The pricing for the R2 buckets themselves remains the same as R2 today, with some additional charges (once the beta period ends) for the data catalog feature. Here&#8217;s their current thinking on pricing, but this is TBC:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LZkE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2d7744f-bdb9-42e2-b311-04c6e396151c_1308x698.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LZkE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2d7744f-bdb9-42e2-b311-04c6e396151c_1308x698.png 424w, https://substackcdn.com/image/fetch/$s_!LZkE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2d7744f-bdb9-42e2-b311-04c6e396151c_1308x698.png 848w, https://substackcdn.com/image/fetch/$s_!LZkE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2d7744f-bdb9-42e2-b311-04c6e396151c_1308x698.png 1272w, https://substackcdn.com/image/fetch/$s_!LZkE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2d7744f-bdb9-42e2-b311-04c6e396151c_1308x698.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LZkE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2d7744f-bdb9-42e2-b311-04c6e396151c_1308x698.png" width="1308" height="698" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d2d7744f-bdb9-42e2-b311-04c6e396151c_1308x698.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:698,&quot;width&quot;:1308,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!LZkE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2d7744f-bdb9-42e2-b311-04c6e396151c_1308x698.png 424w, https://substackcdn.com/image/fetch/$s_!LZkE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2d7744f-bdb9-42e2-b311-04c6e396151c_1308x698.png 848w, https://substackcdn.com/image/fetch/$s_!LZkE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2d7744f-bdb9-42e2-b311-04c6e396151c_1308x698.png 1272w, https://substackcdn.com/image/fetch/$s_!LZkE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2d7744f-bdb9-42e2-b311-04c6e396151c_1308x698.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">R2 Data Catalog Proposed Pricing</figcaption></figure></div><p>During the beta period, Cloudflare will be looking at improving the performance of R2&#8217;s Data Catalog with compaction and table optimisations, alongside expanding query-engine compatibility with the Iceberg REST Catalog spec.</p><p>[<a href="https://blog.cloudflare.com/r2-data-catalog-public-beta/">full article</a>]</p><h4>Simplify Secret Management with Secrets Store</h4><p>If you&#8217;ve ever used Cloudflare Workers, you&#8217;ll know that secrets are set on a per-project basis. They can either be plaintext or encrypted, but critically, they cannot be shared between projects.</p><p>If you have a lot of Workers&#8202;&#8212;&#8202;and some people have a serious number of Workers&#8202;&#8212;&#8202;and you need a secret across a number of Workers, it becomes a management nightmare. Let&#8217;s say you need to rotate an API key across 100 Workers&#8202;&#8212;&#8202;that&#8217;s going to be a lot of manual work, not to mention prone to human error.</p><p>Furthermore, anyone who can modify a Worker could also modify the secrets&#8202;&#8212;&#8202;potentially causing outages but also not respecting good security practises, where people should have as little access as they need.</p><p>This has been the state of play for quite some time, but with Secrets Store, these two problems will be no more. First announced in mid 2023, Secrets Store, as the name suggests, will allow you to create centralised stores for your secrets.</p><p>These can then be shared across Workers via bindings, meaning if you need to rotate a secret, you can do so seamlessly across all your Workers.</p><p>Additionally, Secrets Store brings RBAC into the fold, with three roles to provide fine-grained access to secrets store based on the needs of each user in your organisation</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!D4LK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f4d278-f590-4b95-936d-67472f20780a_1596x586.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!D4LK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f4d278-f590-4b95-936d-67472f20780a_1596x586.png 424w, https://substackcdn.com/image/fetch/$s_!D4LK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f4d278-f590-4b95-936d-67472f20780a_1596x586.png 848w, https://substackcdn.com/image/fetch/$s_!D4LK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f4d278-f590-4b95-936d-67472f20780a_1596x586.png 1272w, https://substackcdn.com/image/fetch/$s_!D4LK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f4d278-f590-4b95-936d-67472f20780a_1596x586.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!D4LK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f4d278-f590-4b95-936d-67472f20780a_1596x586.png" width="1456" height="535" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a1f4d278-f590-4b95-936d-67472f20780a_1596x586.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:535,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!D4LK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f4d278-f590-4b95-936d-67472f20780a_1596x586.png 424w, https://substackcdn.com/image/fetch/$s_!D4LK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f4d278-f590-4b95-936d-67472f20780a_1596x586.png 848w, https://substackcdn.com/image/fetch/$s_!D4LK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f4d278-f590-4b95-936d-67472f20780a_1596x586.png 1272w, https://substackcdn.com/image/fetch/$s_!D4LK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f4d278-f590-4b95-936d-67472f20780a_1596x586.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Secrets Store Roles</figcaption></figure></div><p>This means the ability to create, update and delete secrets can be restricted to a few people&#8202;&#8212;&#8202;rather than your entire engineering team. You can also scope each secret to a given product, to further enhance security. For now, there&#8217;s only Workers, but in future I suspect it will extend to the Container platform and Snippets, as two likely candidates.</p><p>Accessing a secret is nice and simple:</p><pre><code>const openAIkey = await env.open_AI_key.get();</code></pre><p>This does mean there is a latency hit for using Secrets Store, as I suspect it will need to make a network request&#8202;&#8212;&#8202;but hopefully this is minimal, and in future, hopefully it&#8217;s possible to inject secrets directly into a Worker at runtime, in the same way it works today.</p><p>During the beta, you can create up to 20 secrets for free, with pricing for more keys to be announced at a later date&#8202;&#8212;&#8202;I suspect when Secrets Store goes GA.</p><p>[<a href="https://blog.cloudflare.com/secrets-store-beta/">full article</a>]</p><h4>Workers Observability Launches with Logs, Metrics &amp; Queries</h4><p>If you cast your mind back a year ago, observability on Cloudflare was sorely missing. There were real-time logs, that weren&#8217;t persisted anywhere on the Cloudflare platform, or you could push them to an external provider (which is typically quite expensive).</p><p>That made debugging exceptionally difficult. That&#8217;s why a year ago, during Developer Week, Cloudflare announced the acquisition of Baselime&#8202;&#8212;&#8202;an observability platform focussed on serverless platforms.</p><p>After meeting the guys behind Baselime at a conference, I actually hooked all my Workers up to their platform so I could observe my Workers&#8202;&#8212;&#8202;all before the acquisition.</p><p>Since then, the same team has been working on bringing observability to Cloudflare as a first-class citizen&#8202;&#8212;&#8202;with Workers Logs being released in beta a few months later at Birthday Week in 2024.</p><p>This week, we got to see the next evolution of Cloudflare&#8217;s observability journey, which included three drops:</p><ul><li><p><strong>Workers Metrics Dashboard (Beta)</strong>: A single dashboard to view metrics and logs from all of your Workers</p></li><li><p><strong>Query Builder (Beta)</strong>: Construct structured queries to explore your logs, extract metrics from logs, create graphical and tabular visualizations, and save queries for faster future investigations.</p></li><li><p><strong>Workers Logs: </strong>Now Generally Available, with a public API and improved invocation-based grouping.</p></li></ul><p>Until this week, you could only see logs for an individual Worker at a time. Thanks to the new observability dashboard, you can see an aggregated view of all your logs across your Cloudflare Workers.</p><p>To make this aggregated view more powerful, a query builder has been added to allow you to comb through your logs and find what you need. You get everything you&#8217;d typically expect with a query builder for logs including visualisations (count, sum etc.), filters, search, group by and more.</p><p>Another really nice improvement is the ability to see the CPU and wall time of your Workers, which is also queryable. And lastly, you can view logs on a per-invocation basis. For example, you may want to dive into all the logs for a given invocation to see what happened&#8202;&#8212;&#8202;you can do that now.</p><p>[<a href="https://blog.cloudflare.com/introducing-workers-observability-logs-metrics-and-queries-all-in-one-place/">full article</a>]</p><h4>Streaming Ingestion with Pipelines</h4><p>Stick with me&#8202;&#8212;&#8202;we&#8217;re almost there for new products, with 3 to go.</p><p>Moving onto another product that was announced last Developer Week, we have Pipelines entering open beta (restricted to Workers Paid, $5/month). Using Pipelines, you&#8217;re able to ingest large volumes of data (100MB/s, I believe) using Cloudflare, and they&#8217;ll push it all to the R2 bucket you specify.</p><p>Imagine you need to ingest clickstream data from all your customers, where you may have a significant number of concurrent users on your site&#8202;&#8212;&#8202;each click, page navigation and action is recorded from every device. This can generate a large amount of data, and ingesting it can be tricky&#8202;&#8212;&#8202;but not with Pipelines.</p><p>You don&#8217;t need to worry about any of the underlying infrastructure, you simply create a Pipeline and link it to an R2 bucket:</p><pre><code>npx wrangler pipelines create my-clickstream-pipeline --r2-bucket my-bucket</code></pre><p>And that will give you a URL which you can fire your data at, it&#8217;s as simple as that.</p><p>Now, the chances are you probably want to do something with that data&#8202;&#8212;&#8202;not just let it sit on R2. Maybe you want to transform it, maybe you want to load it into another system for analysis.</p><p>Either way, Cloudflare has acquired Arroyo to make that a reality&#8202;&#8212;&#8202;with Arroyo&#8217;s offerings being brought into the Cloudflare estate. They are not available today, but will be in the future.</p><p>There&#8217;s cohesion with this announcement and the R2 Data Catalog too, as in the future you&#8217;ll be able to ingest the data straight into an R2 bucket with Data Catalog enabled, and then be able to immediately query that data.</p><p>In terms of what&#8217;s next, besides transformations, there&#8217;s integrating Workers as UDFs (User-Defined Functions), adding new sources like Kafka clients, and extending Pipelines with new sinks (beyond R2).</p><p>If you&#8217;re curious about pricing, there is no cost for Pipelines during the beta, but you will pay the R2 costs. Beyond beta, this is the proposed pricing:</p><ul><li><p><strong>Ingestion:</strong> First 50 GB per month included in Workers Paid, $0.02 per additional GB</p></li><li><p><strong>Delivery to R2: </strong>First 50 GB per month included in Workers Paid, $0.02 per additional GB</p></li></ul><p>With Cloudflare making more and more products available on the free plan, you&#8217;ll also be pleased to hear that will happen as the beta progresses.</p><p>[<a href="https://blog.cloudflare.com/cloudflare-acquires-arroyo-pipelines-streaming-ingestion-beta/">full article</a>]</p><h4>Introducing RealtimeKit: Realtime Audio &amp; Video Experiences</h4><p>Onto our penultimate new product, and it&#8217;s something quite different again to anything we&#8217;ve seen so far in this recap. I think it speaks to the depth and breadth of Cloudflare&#8217;s offering, and how much it&#8217;s grown recently.</p><p>So what exactly is Realtime? In short, it&#8217;s a suite of products to help you integrate real-time audio and video into your applications. Cloudflare Realtime brings together their SFU, STUN, and TURN services, along with a new RealtimeKit SDK.</p><p>That&#8217;s a lot of acronyms, so what exactly are they?</p><p>SFU (Selective Forwarding Unit) routes media streams between participants without decoding them, STUN (Session Traversal Utilities for NAT) helps devices discover their public IP addresses for peer-to-peer connectivity, and TURN (Traversal Using Relays around NAT) relays media through a server when direct peer-to-peer connections fail due to strict NAT or firewall settings.</p><p>In essence, it&#8217;s complicated infrastructure that is required for these real-time use cases, and setting them up yourself would be difficult and a maintenance nightmare. With Cloudflare&#8217;s offering, you can let them handle all the infrastructure, and you just plug them into your applications as-needed.</p><p>Speaking of plugging into your applications, there&#8217;s also an SDK available to make integrating these things easier. Interestingly, it&#8217;s only available in closed beta, with the sign up form <a href="https://www.cloudflare.com/cloudflare-realtimekit-signup/?_gl=1*v7rkzp*_gcl_aw*R0NMLjE3Mzg1NDEwNzYuQ2p3S0NBaUF6UHk4QmhCb0Vpd0Fibk05TzRsR3BvWFVpY05fSTlnejJlQWM2VU1sTHNLSFpVWFc4Y2c0MmJQNjkwdkVKVmdRY0NobnZob0NHZ1VRQXZEX0J3RQ..*_gcl_dc*R0NMLjE3Mzg1NDEwNzYuQ2p3S0NBaUF6UHk4QmhCb0Vpd0Fibk05TzRsR3BvWFVpY05fSTlnejJlQWM2VU1sTHNLSFpVWFc4Y2c0MmJQNjkwdkVKVmdRY0NobnZob0NHZ1VRQXZEX0J3RQ..*_gcl_au*NzIzNzUwOTI2LjE3NDQ0NzQ4NTM.*_ga*NjkyMTA1MTUuMTczNDgwMTc1NQ..*_ga_SQCRB0TXZW*MTc0NDQ3OTI4Mi4xMC4xLjE3NDQ0ODA5ODQuNjAuMC4w">here</a>.</p><p>The SDK is quite vast, containing mobile SDKs (iOS, Android, React Native, Flutter), SDKs for the Web (React, Angular, vanilla JS, WebComponents), and server side services (recording, coordination, transcription).</p><p>I&#8217;ve never had to implement such things myself, but if I did, I&#8217;d absolutely give Realtime a go as it seems to do so much heavy lifting for me. There&#8217;s a ton of technical detail and further explanations in the announcement post, which I highly recommend giving a read.</p><p>[<a href="https://blog.cloudflare.com/introducing-cloudflare-realtime-and-realtimekit/">full article</a>]</p><h4>Deploy to Cloudflare from any GitHub/GitLab Repo</h4><p>Onto the last one, and this one is small but mighty. Getting people to use your platform can be tricky&#8202;&#8212;&#8202;especially when the bigger players have a significant moat and head start on your.</p><p>Therefore, it&#8217;s critical to make it as easy as possible for people to try out your platform, removing as many barriers to entry as possible.</p><p>That&#8217;s why I like the new deploy to Cloudflare button, that&#8217;s available for anyone to use now in GitHub and GitLab. With one line of Markdown, you can add this button to your repository, and anyone can deploy your Worker to Cloudflare instantly.</p><p>Once clicked, it will do the following:</p><ol><li><p>Creates a new Git repository on your GitHub/ GitLab account</p></li><li><p>Automatically provisions resources the app needs (including dependent resources like R2, D1 etc.)</p></li><li><p>Configures Workers Builds (CI/CD)</p></li><li><p>Adds preview URLs to each pull request</p></li></ol><p>There isn&#8217;t much more to say around this one, it&#8217;s definitely a nice quality of life update, and certainly beats following a set of instructions in a README.</p><p>[<a href="https://blog.cloudflare.com/deploy-workers-applications-in-seconds/">full article</a>]</p><h3>&#128142; Major Product Updates</h3><p>Are you still with me? I know, that was a lot. If you made it this far, thank you, and let me repay you with yet more content drops across a range of existing Cloudflare products &amp; services.</p><p>In this section, we have everything from AI to Static Assets to Workflows to Hyperdrive to Durable Objects and much, much more. We&#8217;ve got a lot to cover, so let&#8217;s get going!</p><h4>Agents SDK&#8202;&#8212;&#8202;Remote MCP &amp; Auth</h4><p>It wouldn&#8217;t be a Developer Week recap without plenty of AI, and we&#8217;re kicking it off with some AI-focussed updates to kick things off.</p><p>Earlier this year, Cloudflare released the <a href="https://developers.cloudflare.com/agents/">Agents SDK</a> to make building AI agents easier&#8202;&#8212;&#8202;backed by the power of the Cloudflare platform.</p><p>The SDK can be used not only to make AI Agents, but also remote MCP servers. Companies such as PayPal have already used this exact SDK to deploy their own MCP server, just to show you how quickly MCP and this SDK is growing in popularity.</p><p>However, until this week, you couldn&#8217;t power-up your AI agents by giving them the ability to connect to remote MCP servers. But good news&#8202;&#8212;&#8202;now you can, with the new MCPClientManager class.</p><p>Better yet, this class handles everything you need to manage the lifecycle of connecting to remote MCP servers, including transport, connection management, capability discovery, real-time updates, namespacing and authentication (via Stytch, Auth0, and WorkOS).</p><p>While the focus was on enabling AI agents to connect to remote MCP servers, there was a nice quality of life update to MCP servers built on Cloudflare too&#8202;&#8212;&#8202;they are now able to hibernate between requests, making them significantly cheaper to run. This is thanks to the power of Durable Objects, which are able to hibernate when not handling any requests.</p><p>If you&#8217;re curious what agent capabilities you can build with this SDK, I highly recommend checking out <a href="https://agents.cloudflare.com/">agents.cloudflare.com</a>&#8202;&#8212;&#8202;not only does it do a great job of highlighting AI use cases and ways to solve them, it&#8217;s incredibly jazzy too.</p><p>[<a href="https://blog.cloudflare.com/building-ai-agents-with-mcp-authn-authz-and-durable-objects/">full article</a>]</p><h4>Workers AI&#8202;&#8212;&#8202;quicker, batch support, new models, LoRA &amp; New Dash</h4><p>Continuing the AI theme, we&#8217;re onto Workers AI&#8202;&#8212;&#8202;Cloudflare&#8217;s global inference platform for AI workloads. This means your inference tasks run significantly closer to the end user than other platforms, with Cloudflare having GPUs in close to 200 cities worldwide.</p><p>This platform is now 2&#8211;4x quicker, thanks to a series of improvements involving speculative decoding, prefix caching, an updated inference backend, and more.</p><p>Wondering what those fancy-sounding things are? Me too, so I asked ChatGPT to summarise them in a sentence:</p><blockquote><p><em>Speculative decoding in LLMs accelerates text generation by having a smaller, faster model predict possible continuations that a larger model then verifies, while prefix caching reuses previously computed hidden states to avoid redundant calculations when generating text from shared prompts.</em></p></blockquote><p>Well, that clears that up nicely&#8202;&#8212;&#8202;and to ensure there was no impact to the quality of the responses, there was extensive A/B testing before this has been applied. For now, it&#8217;s only applied to cf/meta/llama-3.3&#8211;70b-instruct-fp8-fast, but presumably will be rolled out wider over time.</p><p>Moving onto another topic, let&#8217;s talk about batch processing. Until this week, you could only synchronous call models hosted on Workers AI&#8202;&#8212;&#8202;even if you didn&#8217;t have an immediate need for the response, or needed all responses to be returned before you processed them.</p><p>Now however, you can pass an additional parameter when calling Workers AI, and it will queue the request rather than return immediately. You can then poll for the response via API or the AI binding.</p><p>In terms of why you might use this piece of functionality, consider that sometimes GPUs get overloaded and can&#8217;t handle your response&#8202;&#8212;&#8202;or take a long time to do so. If you don&#8217;t immediately need a response, it&#8217;ll be better to schedule it via the batch functionality, which will handle retries automatically.</p><p>Switching topic one again, we&#8217;re onto LoRAs&#8202;&#8212;&#8202;which are now available on more models.</p><p>In essence, a Low Rank Adaptation (LoRA) adapter allows people to take a trained adapter file and use it in conjunction with a model to alter the response of a model. Think of it like a cheaper alternative to fine-tuning, where you can make a model more suited to your use case.</p><p>LoRA support is/will be available for the following models:</p><ul><li><p><a href="https://developers.cloudflare.com/workers-ai/models/llama-3.2-11b-vision-instruct/">@cf/meta/llama-3.2&#8211;11b-vision-instruct</a> (soon)</p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/llama-3.3-70b-instruct-fp8-fast/">@cf/meta/llama-3.3&#8211;70b-instruct-fp8-fast</a> (soon)</p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/llama-guard-3-8b/">@cf/meta/llama-guard-3&#8211;8b</a></p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/llama-3.1-8b-instruct-fast/">@cf/meta/llama-3.1&#8211;8b-instruct-fast</a> (soon)</p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/deepseek-r1-distill-qwen-32b/">@cf/deepseek-ai/deepseek-r1-distill-qwen-32b</a> (soon)</p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/qwen2.5-coder-32b-instruct">@cf/qwen/qwen2.5-coder-32b-instruct</a></p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/qwq-32b">@cf/qwen/qwq-32b</a></p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/mistral-small-3.1-24b-instruct">@cf/mistralai/mistral-small-3.1&#8211;24b-instruct</a> (soon)</p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/gemma-3-12b-it">@cf/google/gemma-3&#8211;12b-it</a> (soon)</p></li></ul><p>While we&#8217;re talking about models, there are some new brand ones too (and I stole the descriptions from the blog post rather than rehashing them):</p><ul><li><p><a href="https://developers.cloudflare.com/workers-ai/models/mistral-small-3.1-24b-instruct">@cf/mistralai/mistral-small-3.1&#8211;24b-instruct</a>: a 24B parameter model achieving state-of-the-art capabilities comparable to larger models, with support for vision and tool calling.</p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/gemma-3-12b-it">@cf/google/gemma-3&#8211;12b-it</a>: well-suited for a variety of text generation and image understanding tasks, including question answering, summarization and reasoning, with a 128K context window, and multilingual support in over 140 languages.</p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/qwq-32b">@cf/qwen/qwq-32b</a>: a medium-sized reasoning model, which is capable of achieving competitive performance against state-of-the-art reasoning models, e.g., DeepSeek-R1, o1-mini.</p></li><li><p><a href="https://developers.cloudflare.com/workers-ai/models/qwen2.5-coder-32b-instruct">@cf/qwen/qwen2.5-coder-32b-instruct</a>: the current state-of-the-art open-source code LLM, with its coding abilities matching those of GPT-4o.</p></li></ul><p>One last thing for Workers AI before we move on: there is a shiny new dashboard to help you understand your AI workloads that shows your usage in traditional measurements (tokens, seconds) versus the Neurons that Cloudflare has been using&#8202;&#8212;&#8202;I really like this one as it makes it a lot easier to reason about your usage, as people are used to using tokens, for example.</p><p>[<a href="https://blog.cloudflare.com/workers-ai-improvements/">full article</a>]</p><h4>Durable Objects&#8202;&#8212;&#8202;Available in the Free Tier</h4><p>I cannot understate how excited I am for this announcement. For as long as Durable Objects have existed, they have been gated behind the Workers Paid plan. By no means a large barrier to entry, but if you simply wanted to try them out, $5 was probably going to put you off.</p><p>With Durable Objects underpinning the Agents SDK, and being very unique and magical, you won&#8217;t find anything like these anywhere else, it&#8217;s a no-brainer to make them available on the free plan.</p><p>For the uninitiated, Durable Objects are effectively defined in your code as normal-looking classes. However, they are far from normal, as at runtime you&#8217;re able to spin up effectively unlimited instances of these classes&#8202;&#8212;&#8202;but the twist is they are not instantiated in the same process as the calling code, it spins up a remote server where the code is executed.</p><p>You can spin up as many of these as you need on-demand, with some Cloudflare customers having millions of instances of Durable Objects. Each instance is unique, and there can only ever be one instance with a given ID active at any one time globally, making them perfect for multiplayer and coordination.</p><p>They come packed with features too, including:</p><ul><li><p>Durable storage (Key-value, SQLite)&#8202;&#8212;&#8202;they hibernate between requests, but their storage remains intact between restarts.</p></li><li><p>WebSockets built-in&#8202;&#8212;&#8202;you can genuinely connect via WebSockets in a few lines of code, and they hibernate between WebSocket messages, making them incredibly cost-efficient.</p></li><li><p>Alarms, to handle scheduled tasks</p></li><li><p>The ability to make RPC calls to a Durable Object from other Cloudflare products such as Workers</p></li></ul><p>At first, it can be hard to get your head around Durable Objects, or even explain them to anyone. But, I highly recommend you give them a try, as they are incredibly powerful. And as you just learned, they are now available to try for <em>free</em>. The free plan is pretty generous too:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GdZO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09c2844d-5a67-4f12-af03-734c4724a8fd_1538x680.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GdZO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09c2844d-5a67-4f12-af03-734c4724a8fd_1538x680.png 424w, https://substackcdn.com/image/fetch/$s_!GdZO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09c2844d-5a67-4f12-af03-734c4724a8fd_1538x680.png 848w, https://substackcdn.com/image/fetch/$s_!GdZO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09c2844d-5a67-4f12-af03-734c4724a8fd_1538x680.png 1272w, https://substackcdn.com/image/fetch/$s_!GdZO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09c2844d-5a67-4f12-af03-734c4724a8fd_1538x680.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GdZO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09c2844d-5a67-4f12-af03-734c4724a8fd_1538x680.png" width="1456" height="644" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/09c2844d-5a67-4f12-af03-734c4724a8fd_1538x680.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:644,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GdZO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09c2844d-5a67-4f12-af03-734c4724a8fd_1538x680.png 424w, https://substackcdn.com/image/fetch/$s_!GdZO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09c2844d-5a67-4f12-af03-734c4724a8fd_1538x680.png 848w, https://substackcdn.com/image/fetch/$s_!GdZO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09c2844d-5a67-4f12-af03-734c4724a8fd_1538x680.png 1272w, https://substackcdn.com/image/fetch/$s_!GdZO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09c2844d-5a67-4f12-af03-734c4724a8fd_1538x680.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Durable Objects&#8202;&#8212;&#8202;Pricing</figcaption></figure></div><p>[<a href="https://blog.cloudflare.com/building-ai-agents-with-mcp-authn-authz-and-durable-objects/#durable-objects-on-free-tier">full article</a>] (part of the Agent MCP post, but I felt like it deserves its own!)</p><h4>D1&#8202;&#8212;&#8202;Support for Global Read Replication</h4><p>Moving away from AI and onto databases&#8202;&#8212;&#8202;we&#8217;ll cover D1 first, and then take a look at what&#8217;s new with Hyperdrive.</p><p>If you&#8217;ve not heard of D1 before, it&#8217;s Cloudflare&#8217;s SQLite offering. Well, technically, they have two, as I just explained that Durable Objects have SQLite capabilities too!</p><p>When to use each is a topic in its own right, but considering D1 now has read replicas globally, the quick answer is use D1 when you need that global availability. As a Durable Object exists in one location, you may experience high latency when making calls from the other side of the world, at least when compared to D1.</p><p>Winding back a little, D1 has been around for quite a while now, with its beta landing in September 2023. It&#8217;s perfect to store things like configuration, user data, session data and other app-wide data.</p><p>However, it had a significant drawback, in that your database resided in a single location. This made reads potentially slow, as they would have to travel significant distances if the user request originated the other side of the world to your database.</p><p>The good news is, this is a problem of the past, with read replicas now being available to everyone. It&#8217;s in beta, and you can enable on it a per-database basis in the dashboard.</p><p>Before you do though, you need to understand two concepts to ensure your reads perform as you expect:</p><ul><li><p>Sessions are required to ensure sequential consistency. Whenever you run a query, you do so via a session with a unique ID, ensuring that all your queries go to the same replica. This ensures you get consistent results you expect, as the replicas will asynchronously sync their data between themselves. You can either opt to start a session from the primary (ensuring data is the latest) or a replica (where the data may lag behind the primary).</p></li><li><p>Bookmarks are references to a point within a session, and need to be passed to all your queries after the initial query. This ensures that your query is served by a database that is <em>at least </em>up to date as your last query using this bookmark.</p></li></ul><p>It sounds a little fiddly, and will take some getting used to, but unfortunately with a global platform comes global problems, and this is one of them. It may take some getting used to, and it will be interesting how difficult the sessions and bookmarks are to deal with in a real-world application.</p><p>If you want more information, I recommend diving into <a href="https://developers.cloudflare.com/d1/best-practices/read-replication/">the docs</a> or reading the full article linked below.</p><p>[<a href="https://blog.cloudflare.com/d1-read-replication-beta/">full article</a>]</p><h4>Hyperdrive&#8202;&#8212;&#8202;MySQL support &amp; Free Tier</h4><p>Moving onto the service with possibly the <em>coolest</em> name at Cloudflare, we have Hyperdrive, which was first released in September 2023. Hyperdrive is Cloudflare&#8217;s service for speeding up your database queries against region-locked databases.</p><p>Much like D1 read replication, Hyperdrive is solving somewhat of a unique challenge that Cloudflare faces. With its platform being global, and your Workers being spun up in over 330 cities around the world, what happens when your Worker needs to connect to a database that&#8217;s tied to a single location?</p><p>In short, it becomes quite slow&#8202;&#8212;&#8202;especially if you&#8217;re executing multiple queries within a single request. That&#8217;s because each query will need to go from say Australia to Europe every single time, and additionally, you&#8217;ll need to connect to the database each time a Worker is spun up, which is quite often each time a new request comes in as Workers don&#8217;t typically hang around for long.</p><p>If you&#8217;re in a more traditional cloud setup, your compute is probably sitting in the same region as your database, so the latency is actually pretty small between your application and the database. However, there will be high latency from the end user to the actual compute&#8202;&#8212;&#8202;even for retrieving static assets like images and CSS.</p><p>This is where Hyperdrive comes in, which has two primary features:</p><ol><li><p>It acts as a connection pooler, so rather than every instance of your Worker having to connect to the database separately and each time it&#8217;s started, Hyperdrive keeps your database connections alive and ready to be used. Considering it takes several round trips to establish a database connection, this is a huge saving on latency.</p></li><li><p>It has the ability to cache non-mutating queries (e.g. reads) so that for repeated queries, you don&#8217;t need to go fetch from your origin database, drastically improving performance by using Cloudflare&#8217;s global cache. This is configurable, and you can disable it if you wish.</p></li></ol><p>Ultimately, it&#8217;s a no-brainer to use Hyperdrive if you&#8217;re connecting to a database that&#8217;s locked away in a single region.</p><p>As an aside, you&#8217;ll want to enable <a href="https://developers.cloudflare.com/workers/configuration/smart-placement/">Smart Placement</a> too, which tactically works out the best place to spin up your Worker. By default, the Worker will be spun up as close to the end user as possible. However, if you&#8217;re making several database queries, or API calls to externally-hosted systems, Cloudflare can move your Worker to a location that will reduce latency between your Worker and any downstream systems.</p><p>Alright back to Hyperdrive, and what&#8217;s new for Developer Week?</p><p>To start, it now supports MySQL. Up until now, it&#8217;s only supported Postgres, so if you have an existing MySQL database you need to connect to, you&#8217;re in luck.</p><p>In terms of compatibility, it supports MySQL and MySQL-compatible databases (e.g. PlanetScale, Azure, Google Cloud SQL, AWS RDS and Aurora) along with the following database drivers (mysql2, mysql, Drizzle, Kysely).</p><p>Secondly, you can now use Hyperdrive on the free tier. I&#8217;m a huge fan of adding as much as possible to the free tier, as it allows anyone to try out a service without any commitment.</p><p>You get 100,000 database queries per day on the free tier, any beyond that will error out. Just for reference, if you do need more throughput than that, you get unlimited queries on the $5/month Workers Paid plan.</p><p>[<a href="https://blog.cloudflare.com/how-hyperdrive-speeds-up-database-access/">Hyperdrive explainer</a> | <a href="https://blog.cloudflare.com/building-global-mysql-apps-with-cloudflare-workers-and-hyperdrive/">MySQL announcement</a>]</p><h4>Workflows&#8202;&#8212;&#8202;Increased Throughput, New APIs &amp; Generally Available</h4><p>Up next, we&#8217;re going to cover one of Cloudflare&#8217;s newest products: Workflows. I probably don&#8217;t need to explain too much about what they do, as the name is pretty self-explanatory.</p><p>In essence, Workflows are Cloudflare&#8217;s take on Durable Execution. You can define a Workflow, that has a series of steps, and Cloudflare will execute that Workflow, including recovering from errors and automatially retrying failed steps.</p><p>Furthermore, it will keep track of what steps passed on any prior runs, and automatically rehydrate any state needed from steps that successfully completed.</p><p>For example, let&#8217;s say you have a Workflow that downloads a CSV file on a daily basis and inserts each row into a database. You could do this with a simple scheduled task, but what happens if a database query fails in the middle of the job?</p><p>If it&#8217;s just a temporary blip, you can retry, but what if your actual database is down, or the host for the CSV file is unavailable? Now you either need something to retry the entire job, or in the case of it failing part-way through, you don&#8217;t want to insert duplicate records, so now you need to handle that too, which is a bit of a pain.</p><p>All these failure cases are a pain to deal with, but not with Workflows. With Workflows, you could have one step that downloads the CSV and stores it in the Workflow&#8217;s state, and then trigger a step for every single row in the CSV to insert it into the database.</p><p>If there were 500 rows, and it failed on row 400, the entire Workflow would re-run&#8202;&#8212;&#8202;but it would know that the first 400 steps succeeded and not re-run those, and pick up where it left off.</p><p>Just to preempt, I know 500 rows isn&#8217;t a lot and you could likely do one big insert. But what if you&#8217;re dealing with 500,000 rows? Going to be a little bit trickier.</p><p>Retries and how long to wait between retries is all configurable, and the APIs are really easy to use as they are all written in vanilla JavaScript. You can also have a Workflow pause for a set amount of time too (or a date/time in the future).</p><p>In terms of what&#8217;s new for Developer Week, my favourite addition is the new waitForEvent API. As soon as I started using Workflows, I wished for a way to pause a Workflow until an event outside my Workflow happened (so much so, I wrote a <a href="https://github.com/apeacock1991/workflows-wait-for-action">little hack</a> for it with Durable Objects).</p><p>To explain what this enables, let&#8217;s say your Workflow takes care of your user onboarding flow. The user signs up, confirms their email, and then we create their account and send them a welcome email.</p><p>We don&#8217;t want to send that welcome email until the user has confirmed their account, but we do want to send the actual confirm account email. Workflows would be perfect for this, as if the email sending fails to confirm their account, that&#8217;s going to be really frustrating&#8202;&#8212;&#8202;so we can rely on its retries to ensure it&#8217;s sent.</p><p>The Workflow needs to wait for the user to click the link in the email before sending the welcome email though, so at that point we can make use of the new waitForEvent functionality.</p><p>Once the user clicks the link, we can send an &#8220;event&#8221; to our Workflow and it can resume and send the welcome email&#8202;&#8212;&#8202;super simple, and super reliable. In terms of sending that event, you can either use a binding from a Worker, or use the Cloudflare REST API.</p><p>Alongside the new API, there&#8217;s a few more quality of life updates:</p><ul><li><p><strong>Concurrent limits increased:</strong> Initially the limit was really low, but as the product has moved through beta, the limits have been raised, and it now stands at a pretty respectable 4,500. This limit will go higher throughout the year, expected to reach 10,000 concurrent workflows by the end of the year.</p></li><li><p><strong>Improved observability:</strong> you can now see CPU time for each Workflow instance in the dashboard.</p></li><li><p><strong>Vitest support: </strong>to enable you to run tests locally and in CI/CD pipelines, there&#8217;s now Vitest support for Workflows.</p></li></ul><p>[<a href="https://blog.cloudflare.com/workflows-ga-production-ready-durable-execution/">full article</a>]</p><h4>Static Asset Workers&#8202;&#8212;&#8202;Frameworks Go Generally Available</h4><p>During Birthday Week last year, Cloudflare started the migration of Pages to Workers with the release of Static Asset Workers in beta. For as long as I can remember, Pages has been the go-to option for hosting static and full-stack websites on Cloudflare.</p><p>However, there were always some drawbacks with it, such as not being able to use Durable Objects without creating a separate Worker, and more recently, not having access to Workers Logs.</p><p>To solve this problem, the direction from Cloudflare is to move static asset hosting into Workers, in the form of <a href="https://developers.cloudflare.com/workers/static-assets/">Static Asset Workers</a>. This was confirmed this week with the announcement that no new features would be added to Pages.</p><p>If you&#8217;re currently hosting sites on Pages, there&#8217;s no need to worry about support suddenly disappearing. However, investment, optimisations, and feature work will be focussed solely on Workers when it comes to hosting websites going forward.</p><p>Fast forward 7 months, and Static Asset Workers are now generally available, alongside a number of <a href="https://developers.cloudflare.com/workers/frameworks/">frameworks</a> that it supports:</p><ul><li><p>React Router v7 (Remix)</p></li><li><p>Astro</p></li><li><p>Hono</p></li><li><p>Vue.js</p></li><li><p>Nuxt</p></li><li><p>Svelte (SvelteKit)</p></li><li><p>Qwik</p></li><li><p>Gatsby</p></li><li><p>Docusaurus</p></li><li><p>Angular</p></li></ul><p>This means you can now safely migrate projects that rely on these frameworks from Pages to Workers, and reap all the benefits of wider support for the rest of the Cloudflare developer platform.</p><p>If you want to have a go at that migration, there&#8217;s a guide available <a href="https://developers.cloudflare.com/workers/static-assets/migrate-from-pages/">here</a>.</p><p>There are some notable frameworks that are not moving to GA, with Next.js and Solid both staying in beta for now. With Next.js notoriously difficult to host on infrastructure outside of Vercel, Cloudflare is working to make <a href="https://opennext.js.org/">OpenNext</a> work seamlessly on Cloudflare with an <a href="https://developers.cloudflare.com/workers/frameworks/framework-guides/nextjs/">adapter</a>.</p><p>That adapter is now hitting its first major milestone: v1 is available, but still in beta form for now. Most of the Next.js 15 features are supported, including:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3Xij!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59e2ac4b-d8b7-48dd-97dc-2ee7e63554f2_1572x810.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3Xij!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59e2ac4b-d8b7-48dd-97dc-2ee7e63554f2_1572x810.png 424w, https://substackcdn.com/image/fetch/$s_!3Xij!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59e2ac4b-d8b7-48dd-97dc-2ee7e63554f2_1572x810.png 848w, https://substackcdn.com/image/fetch/$s_!3Xij!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59e2ac4b-d8b7-48dd-97dc-2ee7e63554f2_1572x810.png 1272w, https://substackcdn.com/image/fetch/$s_!3Xij!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59e2ac4b-d8b7-48dd-97dc-2ee7e63554f2_1572x810.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3Xij!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59e2ac4b-d8b7-48dd-97dc-2ee7e63554f2_1572x810.png" width="1456" height="750" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/59e2ac4b-d8b7-48dd-97dc-2ee7e63554f2_1572x810.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:750,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!3Xij!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59e2ac4b-d8b7-48dd-97dc-2ee7e63554f2_1572x810.png 424w, https://substackcdn.com/image/fetch/$s_!3Xij!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59e2ac4b-d8b7-48dd-97dc-2ee7e63554f2_1572x810.png 848w, https://substackcdn.com/image/fetch/$s_!3Xij!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59e2ac4b-d8b7-48dd-97dc-2ee7e63554f2_1572x810.png 1272w, https://substackcdn.com/image/fetch/$s_!3Xij!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59e2ac4b-d8b7-48dd-97dc-2ee7e63554f2_1572x810.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Next.js 15 Features Supported By Next.js on Cloudflare</figcaption></figure></div><p>Cloudflare will continue iterating on the adapter until it&#8217;s ready to hit GA, which I suspect will happen later this year. I really like this initiative as it removes the vendor lock to Vercel that has typically existed for many years around Next.js.</p><p>Before moving onto the next section, there&#8217;s one more thing worth mentioning around serving sites via Workers, and that&#8217;s Vite support on Cloudflare hitting v1, alongside support React Router v7.</p><p>[<a href="https://blog.cloudflare.com/full-stack-development-on-cloudflare-workers/">Static Assets GA</a> | <a href="https://blog.cloudflare.com/deploying-nextjs-apps-to-cloudflare-workers-with-the-opennext-adapter/">OpenNext v1</a> | <a href="https://blog.cloudflare.com/introducing-the-cloudflare-vite-plugin/">Vite v1</a>]</p><h4>Snippets Are Generally Available</h4><p>Time for a bit of a history lesson on this one, as it really hits home how far the Cloudflare developer platform has come. When I first heard of Cloudflare Workers, many years ago, they were released as a way to make simple changes to HTTP requests&#8202;&#8212;&#8202;modify headers, add cookies, things like that.</p><p>From memory, it was only possible to edit them via a UI too, which was particularly terrifying, but I honestly might be misremembering that. Either way, it&#8217;s fair to say you couldn&#8217;t do much with a Worker at that time.</p><p>Fast forward to 2025, and as we just covered, Workers can now serve static assets, including a ton of popular frameworks, but they can also be created with any language that supports WebAssembly, they can be APIs, they can be cron jobs, they can consume messages from a Queue, it&#8217;s fair to say they are incredibly versatile.</p><p>The original use case still exists though, such as making some simple modifications to a HTTP request or response, and for that a Worker is now a little overkill&#8202;&#8212;&#8202;it can certainly do it, but Snippets now cater for the original purpose that Workers were created for.</p><p>If you need to transform headers dynamically, fine-tune caching, rewrite URLs, retry failed requests, replace expired links, throttle suspicious traffic, or validate authentication tokens, that&#8217;s where Snippets shine.</p><p>So why would you use Snippets over a Worker? That one is easy, it comes down to cost. With a Worker, you pay for the number of requests you handle and the CPU you use. With Snippets, they are free on any paid plan (Pro, Business &amp; Enterprise&#8202;&#8212;&#8202;they are not currently available on Workers Paid).</p><p>If you&#8217;re wondering when to use a Worker and when to use a Snippet, Cloudflare&#8217;s got you covered:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!F3D6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd3f0412-e83f-416d-8558-0b1af62cb335_1564x1282.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!F3D6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd3f0412-e83f-416d-8558-0b1af62cb335_1564x1282.png 424w, https://substackcdn.com/image/fetch/$s_!F3D6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd3f0412-e83f-416d-8558-0b1af62cb335_1564x1282.png 848w, https://substackcdn.com/image/fetch/$s_!F3D6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd3f0412-e83f-416d-8558-0b1af62cb335_1564x1282.png 1272w, https://substackcdn.com/image/fetch/$s_!F3D6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd3f0412-e83f-416d-8558-0b1af62cb335_1564x1282.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!F3D6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd3f0412-e83f-416d-8558-0b1af62cb335_1564x1282.png" width="1456" height="1193" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fd3f0412-e83f-416d-8558-0b1af62cb335_1564x1282.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1193,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!F3D6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd3f0412-e83f-416d-8558-0b1af62cb335_1564x1282.png 424w, https://substackcdn.com/image/fetch/$s_!F3D6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd3f0412-e83f-416d-8558-0b1af62cb335_1564x1282.png 848w, https://substackcdn.com/image/fetch/$s_!F3D6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd3f0412-e83f-416d-8558-0b1af62cb335_1564x1282.png 1272w, https://substackcdn.com/image/fetch/$s_!F3D6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd3f0412-e83f-416d-8558-0b1af62cb335_1564x1282.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Snippets vs Workers</figcaption></figure></div><p>While not available just yet, later in the year you&#8217;ll also be able to access values from Secrets Store from Snippets too.</p><p>[<a href="https://blog.cloudflare.com/snippets/">full article</a>]</p><h4>Super Slurper&#8202;&#8212;&#8202;5x Faster With A New Architecture</h4><p>You&#8217;ll perhaps be relieved to know we&#8217;re at the end of the recap of Developer Week 2025, with this being the final entry.</p><p>If Hyperdrive has the coolest name of any Cloudflare service, then Super Slurper takes the award for the most fun (although its companion, <a href="https://developers.cloudflare.com/r2/data-migration/sippy/">Sippy</a>, is a close second).</p><p>Super Slurper is used in combination with R2 to migrate contents from S3-compatible storage on other cloud providers, such as AWS and Google Cloud Storage.</p><p>It&#8217;s an all-at-once migration, so it will migrate all the objects in one big migration. This is in comparison to Sippy, which does it object-by-object as you request them from an external source.</p><p>One of the barriers to entry for moving data across clouds is the difficulty in doing so, but with Super Slurper, it should be a breeze. And better yet, it&#8217;ll now be 5x faster to move your data across thanks to a re-architecture of the tool, making use of Workers, Durable Objects and Queues.</p><p>If you&#8217;re new to the Cloudflare platform, you&#8217;ll probably not realise that the vast majority of Cloudflare&#8217;s offerings involve &#8220;dogfooding&#8221; their own products. This is another example of that, with Super Slurper moving from Kubernetes to a native Cloudflare solution.</p><p>There&#8217;s a ton of detail around how they achieve this in the blog post, which I&#8217;d recommend checking out, as I personally really enjoy the technical depth that a lot of Cloudflare&#8217;s blog posts go into.</p><p>[<a href="https://blog.cloudflare.com/making-super-slurper-five-times-faster/">full article</a>]</p><h3>Closing Thoughts</h3><p>That brings us to the end of my Developer Week recap, and this is definitely one of my favourite Developer Weeks so far.</p><p>There&#8217;s just so much variety, and you can see that the platform just goes from strength-to-strength, allowing you to build quicker, more efficiently and lets you focus on what you do best: shipping.</p><p>I want my developer platform of choice to get in the way as little as possible, while still doing as much of the heavy-lifting as possible. In my view, that&#8217;s exactly what Cloudflare does.</p><p>I can effortlessly create products and services, with excellent developer experience when developing locally, and push them to production in a few seconds. I also know I can rely on the platform to be stable, and take care of things like scaling and high availability for me.</p><p>Above all else, building on the platform is just <em>fun </em>and a joy, I highly recommend you give it a go for yourself&#8202;&#8212;&#8202;and try out some of the developments announced this week!</p><blockquote><p><em>Enjoyed the article and want to get a head start developing with Cloudflare? I&#8217;ve published a book, Serverless Apps on Cloudflare, that introduces you to all of the platform&#8217;s key offerings by guiding you through building a series of applications on Cloudflare.</em></p><p><em>Buy now: <a href="https://pragprog.com/titles/apapps/serverless-apps-on-cloudflare/">eBook</a> | P<a href="https://www.amazon.com/Serverless-Apps-Cloudflare-Solutions-Infrastructure/dp/B0DFNTSMHP">aperback</a></em></p></blockquote><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.ashleypeacock.co.uk/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Flared Up! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>