<?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:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[bunny.net Blog]]></title><description><![CDATA[Performance tips, updates, tricks and insights into our Content Delivery Network.]]></description><link>https://bunny.net/blog/</link><image><url>https://bunny.net/blog/favicon.png</url><title>bunny.net Blog</title><link>https://bunny.net/blog/</link></image><generator>Ghost 5.82</generator><lastBuildDate>Mon, 27 Jul 2026 18:13:45 GMT</lastBuildDate><atom:link href="https://bunny.net/blog/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[HopStart cohort #3: the next startups building on bunny.net]]></title><description><![CDATA[HopStart is our startup program with a straightforward goal: let early-stage founders get on with building, without infrastructure eating into their time and budget. Two rounds in, we're back with a third cohort.]]></description><link>https://bunny.net/blog/hopstart-cohort-3-the-next-startups-building-on-bunny-net/</link><guid isPermaLink="false">6a61b4b7160dc403fbfcf5c1</guid><category><![CDATA[News]]></category><dc:creator><![CDATA[Marek Nalikowski]]></dc:creator><pubDate>Thu, 23 Jul 2026 06:35:42 GMT</pubDate><media:content url="https://bunny.net/blog/content/images/2026/07/Hop-Start-Cohort.png" medium="image"/><content:encoded><![CDATA[<img src="https://bunny.net/blog/content/images/2026/07/Hop-Start-Cohort.png" alt="HopStart cohort #3: the next startups building on bunny.net"><p>HopStart is our startup program with a straightforward goal: let early-stage founders get on with building, without infrastructure eating into their time and budget. Two rounds in, we&apos;re back with a third cohort.</p><p>As always, each cohort has three winners. Each team receives up to <strong>$50,000 USD in credits</strong> to spend on bunny.net, plus hands-on time with our team and an early look at features before we release them. Choosing only three is never easy. What ultimately tips the scale for us is whether a team is solving a real problem for its users, and whether bunny.net is a genuine fit for how they build.</p><p>Here are the three startups joining HopStart cohort #3.</p><h2 id="1st-place-screenity">1st place: Screenity</h2><p><strong>Founder:</strong> Alyssa X</p><p><strong>Year founded:</strong> 2025</p><p><strong>URL:</strong> <a href="https://www.screenity.io/">https://www.screenity.io/</a></p><p><strong>Credits received:</strong> $50,000 for one year</p><p><strong>In the founder&apos;s words:</strong> &quot;Screenity is the European alternative to Loom that goes well beyond recording a clip and sending a link. It&apos;s a screen recorder and video editor all in one, working almost like a deck. It&apos;s privacy-friendly all the way down, and is hosted entirely in Europe (unheard of in this space!), all built independently by one person.&quot;</p><h2 id="2nd-place-ciphera">2nd place: Ciphera</h2><p><strong>Founder:</strong> Usman Baig</p><p><strong>Year founded:</strong> 2024</p><p><strong>URL:</strong> <a href="https://www.ciphera.net/">https://www.ciphera.net/</a></p><p><strong>Credits received:</strong> $25,000 for one year</p><p><strong>In the founder&apos;s words:</strong> &quot;Ciphera is a privacy-first analytics and identity platform built for website owners who refuse to compromise between actionable data and user privacy. Our flagship product, Pulse, is a cookie-free web analytics tool that gives site owners the metrics they need &#x2014; pageviews, referrers, geolocation, and device breakdowns &#x2014; without collecting personal data or requiring consent banners. The impact is straightforward: we&apos;re making it economically viable for small and mid-sized websites to stop feeding the ad-tech surveillance pipeline.&quot;</p><h2 id="3rd-place-orkestr">3rd place: orkestr</h2><p><strong>Founder:</strong> Stefan Iancu</p><p><strong>Year founded:</strong> 2026</p><p><strong>URL:</strong> <a href="https://orkestr.eu/">https://orkestr.eu/</a></p><p><strong>Credits received:</strong> $10,000 for one year</p><p><strong>In the founder&apos;s words:</strong> &quot;orkestr is a platform-as-a-service that removes DevOps overhead for developers, freelancers, and small teams. Push to Git, and we handle builds, deploys, databases, SSL, and scaling. No Kubernetes, no infrastructure engineer required. Teams that need real data sovereignty are forced to choose between developer experience and compliance, and usually end up self-hosting, which means hiring DevOps talent they cannot afford. We close that gap: orkestr runs entirely on EU infrastructure with a fully EU sub-processor chain, so customers get Heroku-grade simplicity without legal exposure to non-EU jurisdiction.&quot;</p><h2 id="applying-for-cohort-4">Applying for cohort #4</h2><p>Applications for the next cohort are open. If you&apos;re building something you&apos;re proud of and can see bunny.net fitting into it, submit your application using <a href="https://bunny.net/HopStart/#HopStart-Form">the short form</a> before <strong>August 31</strong>.</p><p>If you applied before and didn&apos;t make it, it&apos;s worth trying again. Every application round is different, and yours may be a stronger fit this time.</p><p>We&apos;ll announce cohort #4 in October.</p><p>Looking forward to seeing what you&#x2019;re building!</p>]]></content:encoded></item><item><title><![CDATA[New Bunny Shield protections for critical WordPress vulnerabilities]]></title><description><![CDATA[New managed WAF rules in Bunny Shield block exploit attempts targeting critical WordPress CVEs before they reach your origin.]]></description><link>https://bunny.net/blog/new-bunny-shield-protections-for-critical-wordpress-vulnerabilities/</link><guid isPermaLink="false">6a5f7738160dc403fbfcf5ac</guid><category><![CDATA[Security]]></category><dc:creator><![CDATA[Joe Connolly]]></dc:creator><pubDate>Tue, 21 Jul 2026 14:29:06 GMT</pubDate><media:content url="https://bunny.net/blog/content/images/2026/07/image--37-.png" medium="image"/><content:encoded><![CDATA[<img src="https://bunny.net/blog/content/images/2026/07/image--37-.png" alt="New Bunny Shield protections for critical WordPress vulnerabilities"><p>Two recently disclosed vulnerabilities have drawn significant attention across the security community, including a critical unauthenticated remote code execution (RCE) vulnerability and a related SQL injection vulnerability.</p><p>To help protect customers while they update their WordPress installations, Bunny Shield now includes new WAF protections for both CVEs, blocking known exploitation attempts before they reach your origin.</p><p>These protections help reduce your exposure, but they are not a substitute for patching. WordPress has released security updates for the affected versions, and updating your installation should remain your highest priority.</p><h2 id="affected-versions">Affected versions</h2><p>WordPress has released fixes in the following versions:</p><ul><li><strong>7.0.2</strong></li><li><strong>6.9.5</strong></li><li><strong>6.8.6</strong></li><li><strong>7.1 Beta 2</strong></li></ul><p>Versions earlier than <strong>6.8</strong> are not affected.</p><p>The WordPress project is treating these vulnerabilities with the highest priority and is automatically deploying updates to supported installations wherever possible. Even so, it&apos;s worth confirming that your sites have successfully updated and are running a patched release.</p><h2 id="understanding-the-vulnerabilities">Understanding the vulnerabilities</h2><p>The advisory includes two related vulnerabilities affecting different parts of WordPress.</p><h3 id="cve-2026-60137sql-injection">CVE-2026-60137 - SQL Injection</h3><p>This vulnerability affects <strong>WordPress 6.8 and later</strong> and allows specially crafted input to manipulate database queries.</p><p><strong>Severity:</strong> High</p><h3 id="cve-2026-63030unauthenticated-remote-code-execution">CVE-2026-63030 - Unauthenticated Remote Code Execution</h3><p>This vulnerability affects <strong>WordPress 6.9 and later</strong>. Under specific conditions, if a persistent object cache is not enabled, an attacker can execute arbitrary code through the WordPress REST API batch endpoint without authentication.</p><p>No login or user interaction is required to exploit this vulnerability.</p><p><strong>Severity:</strong> Critical</p><p>Because the RCE vulnerability builds on the SQL injection vulnerability, the available fixes differ slightly between versions:</p><ul><li><strong>6.8.6</strong> fixes the SQL injection vulnerability</li><li><strong>6.9.5</strong>, <strong>7.0.2</strong>, and <strong>7.1 Beta 2</strong> address both vulnerabilities</li></ul><h2 id="how-bunny-shield-protects-your-site">How Bunny Shield protects your site</h2><p>Bunny Shield now includes dedicated managed WAF rules for both CVEs.</p><p>While our existing WAF engine already detected many exploit attempts targeting these CVEs, we&apos;ve added dedicated managed rules to improve detection accuracy and provide more precise protection against the disclosed attack paths.</p><p>These rules inspect incoming requests before they reach your WordPress installation, identifying and blocking known exploit attempts targeting both vulnerabilities.</p><p>If your website is already protected by Bunny Shield, no additional configuration is required.</p><h2 id="protection-while-you-update">Protection while you update</h2><p>Security updates are always the permanent fix, but deploying them isn&apos;t always instantaneous. Large WordPress fleets, maintenance windows, compatibility testing, and operational requirements can all delay updates, leaving a window of opportunity for attackers.</p><p>Bunny Shield helps close that gap by identifying and blocking malicious requests at the edge before they ever reach your application. This provides an additional layer of protection against publicly known exploit techniques while you complete your update rollout.</p><p>This approach, often referred to as virtual patching, reduces the risk of exploitation during the period between a vulnerability being disclosed and every affected system being updated. It complements software updates, but never replaces them.</p><h2 id="what-you-should-do">What you should do</h2><p>If you manage WordPress sites, we recommend taking the following steps:</p><ul><li>Update to a patched version of WordPress as soon as possible.</li><li>Verify that Bunny Shield is enabled for your website.</li><li>Review your security logs for blocked requests related to these vulnerabilities</li><li>Continue monitoring WordPress security advisories for any additional guidance.</li></ul><h2 id="staying-ahead-of-new-attack-techniques">Staying ahead of new attack techniques</h2><p>Public vulnerability disclosures are often followed by new proof-of-concept exploits and variations that attempt to bypass existing detections. As those techniques evolve, so do our protections.</p><p>We&apos;ll continue monitoring these vulnerabilities and updating Bunny Shield&apos;s managed protections as new attack techniques emerge, helping keep your applications protected as the threat landscape evolves.</p><h2 id="protect-your-sites-with-bunny-shield">Protect your sites with Bunny Shield</h2><p>While keeping WordPress up to date is your best defense, Bunny Shield adds an extra layer of protection by blocking known exploit attempts before they reach your origin.</p><p>If you&apos;re already using Bunny Shield, these managed protections are available automatically. If you&apos;re not, you can get started in minutes and protect your applications against WordPress vulnerabilities, bots, DDoS attacks, and a wide range of web threats.</p><p>Enable Bunny Shield today to protect your WordPress sites against emerging threats, bots, DDoS attacks, and other common web attacks. <a href="https://dash.bunny.net/auth/login">Log in</a> to your bunny.net account, or <a href="https://dash.bunny.net/auth/register">sign up</a> today to start shielding your websites.</p>]]></content:encoded></item><item><title><![CDATA[Introducing Bunny Sandbox: a disposable room where AI can build]]></title><description><![CDATA[Spin up a fully isolated cloud environment with an AI agent already inside, in one command. Let it run real code, ship what it builds, and throw the room away when you are done.]]></description><link>https://bunny.net/blog/introducing-bunny-sandbox-a-disposable-room-where-ai-can-build/</link><guid isPermaLink="false">6a58ddb1160dc403fbfcf564</guid><category><![CDATA[Compute]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Amir Keshavarz]]></dc:creator><pubDate>Thu, 16 Jul 2026 13:59:11 GMT</pubDate><media:content url="https://bunny.net/blog/content/images/2026/07/bunny.net-Sandboxes-support-added-to-CLI.png" medium="image"/><content:encoded><![CDATA[<img src="https://bunny.net/blog/content/images/2026/07/bunny.net-Sandboxes-support-added-to-CLI.png" alt="Introducing Bunny Sandbox: a disposable room where AI can build"><p>In 2026, AI agents do not just suggest code. They run it. They install packages, execute shell commands, edit files, clone repos, and reach out across the network, all on their own. That autonomy is the entire point. An agent that has to stop and ask permission before every command is not saving you much time.</p><p>But to be useful, an agent needs real access. And nobody is reading every command it runs. Most of the time, that is fine. The agent does exactly what you asked and moves on. The problem is what happens the rest of the time. Every so often an agent deletes the wrong directory, leaks a key it should never have seen, pulls a package that turns out to be malicious, or gets talked into something by a poisoned web page it read halfway through a task.</p><p>Here is the pragmatic way to think about it. The odds of any single run going wrong are low. But you are not doing a single run. You are doing hundreds or thousands, and the cost when one goes wrong is not small. It is your laptop, your production credentials, or your customers&apos; data. A low chance of an unbounded loss is still a real, recurring risk. You do not want to bet your environment on it every time.</p><p>So people improvise. They run the agent on their own machine and hope for the best. They hand-roll a throwaway VM and wire up the isolation by hand every time. Or they clamp the agent down so tightly it can no longer do the thing they hired it for. None of these is a good answer.</p><p>The good answer is old and boring: give the code its own room. Let the agent do whatever it needs in there. If it makes a mess, the mess stops at the walls. A sandbox is a disposable room where AI can do real work, and if it wrecks the place, you throw the room away.</p><h2 id="we-have-been-building-rooms-like-this-for-a-long-time">We have been building rooms like this for a long time</h2><p>Running other people&apos;s code, somewhere in the world, quickly and cheaply, is not a new problem for us. It is a problem bunny.net has worked on from the start. Our Magic Containers already deploy your apps across a global network of servers spanning six continents, from Ashburn to Tokyo to S&#xE3;o Paulo to Sydney, and they distribute those apps automatically based on where your traffic actually is.</p><p>A sandbox, it turns out, is that same capability with two additions: an AI agent already sitting inside, and a room you can throw away when you&apos;re done. So we built it.</p><h2 id="meet-bunny-sandbox">Meet Bunny Sandbox</h2><p>Bunny Sandbox uses Magic Containers underneath. In one command, you spin up a fully isolated cloud environment with an AI coding agent like Claude Code already installed, and more agents on the way. The agent does real work inside the box, and you can put that work on the internet just as easily. Same isolation and edge power as Magic Containers, none of the setup.</p><p>Every sandbox is a clean Ubuntu container with Node.js, Bun, Python, and Claude Code ready to go, plus a 10 GB persistent workspace so your files survive a restart. You do not manage a server. You do not wire up isolation. You do not clean up afterwards. You get a room, the agent gets to work, and when you are done, the room goes away.</p><h2 id="spin-one-up-from-your-terminal">Spin one up from your terminal</h2><p>Here is the whole loop, from nothing to a live URL, in the bunny.net CLI.</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
<span style="color:#8A8A8A;"># Install the CLI</span>
<span style="color:#ECEC93;">curl</span> -fsSL <span style="color:#BDE052;">&lt;https://cli.bunny.net/install.sh&gt;</span> | <span style="color:#ECEC93;">sh</span>

<span style="color:#8A8A8A;"># Log in</span>
bunny <span style="color:#ECEC93;">login</span>

<span style="color:#8A8A8A;"># Create an isolated sandbox (Claude Code is already inside)</span>
bunny sandbox <span style="color:#ECEC93;">create</span> my-sandbox
</pre>
<br>
<!--kg-card-end: html-->
<p>That gives you a fresh, isolated environment on our edge in seconds. You get back an SSH address and a public hostname that goes live once you expose a port. The CLI remembers your sandboxes locally, so you can reconnect any time without re-creating.</p>
<!--kg-card-begin: html-->
<p>
  Claude Code needs one thing before it can get to work: your own Anthropic credentials. Either bake your API key in at create time, or just run <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">claude</code> inside the box and log in once. Both stick around across restarts.
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
<span style="color:#8A8A8A;"># .env holds ANTHROPIC_API_KEY</span>
bunny sandbox <span style="color:#ECEC93;">create</span> my-sandbox <span style="color:#ECEC93;">--env-file</span> .env
</pre>
<br>
<!--kg-card-end: html-->
<p>Now put it to work.</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
<span style="color:#8A8A8A;"># Drop into the box and let the agent build</span>
bunny sandbox <span style="color:#ECEC93;">ssh</span> my-sandbox

<span style="color:#8A8A8A;"># Or fire off a one-off command without opening a session</span>
bunny sandbox <span style="color:#ECEC93;">exec</span> my-sandbox <span style="color:#ECEC93;">--</span> <span style="color:#ECEC93;">npm</span> test
</pre>
<br>
<!--kg-card-end: html-->
<p>Need to move files? One path is local, the other points at the sandbox:</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
<span style="color:#8A8A8A;"># Copy a file into the sandbox</span>
bunny sandbox <span style="color:#ECEC93;">cp</span> ./app.js my-sandbox:/workplace/app.js

<span style="color:#8A8A8A;"># And pull one back out</span>
bunny sandbox <span style="color:#ECEC93;">cp</span> my-sandbox:/workplace/out.log ./out.log
</pre>
<br>
<!--kg-card-end: html-->
<p>When the agent builds something worth showing, expose a port as a public HTTPS URL. When you are finished, destroy the whole thing.</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
<span style="color:#8A8A8A;"># Put the dev server on port 3000 online</span>
bunny sandbox <span style="color:#ECEC93;">url</span> <span style="color:#ECEC93;">add</span> my-sandbox <span style="color:#D1949E;">3000</span>

<span style="color:#8A8A8A;"># Done? Throw the room away.</span>
bunny sandbox <span style="color:#ECEC93;">delete</span> my-sandbox
</pre>
<br>
<!--kg-card-end: html-->
<p>No control plane to babysit, no separate platform to wire up. When you delete a sandbox, it&apos;s gone, and the Magic Containers app underneath goes with it. One thing to keep in mind: a sandbox keeps running, and keeps costing you, until you delete it, so tear down the ones you&apos;re finished with.</p><figure class="kg-card kg-image-card"><img src="https://bunny.net/blog/content/images/2026/07/sandbox-demo--3-.jpg" class="kg-image" alt="Introducing Bunny Sandbox: a disposable room where AI can build" loading="lazy" width="1250" height="670" srcset="https://bunny.net/blog/content/images/size/w600/2026/07/sandbox-demo--3-.jpg 600w, https://bunny.net/blog/content/images/size/w1000/2026/07/sandbox-demo--3-.jpg 1000w, https://bunny.net/blog/content/images/2026/07/sandbox-demo--3-.jpg 1250w" sizes="(min-width: 720px) 720px"></figure><h2 id="you-can-drive-it-from-code-too">You can drive it from code too</h2>
<!--kg-card-begin: html-->
<p>
  The same operations (and more) are available as a library. Install <a href="https://www.npmjs.com/package/@bunny.net/sandbox"><code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">@bunny.net/sandbox</code></a>, and you can create a sandbox, write files into it, run commands, expose a port, and tear it down, all from your own program. That is how you go from spinning up one sandbox by hand to orchestrating many of them: a test matrix, a fleet of agent workers, a sandbox per user.
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
<span style="color:#569CD6;">import</span> { Sandbox } <span style="color:#569CD6;">from</span> <span style="color:#BDE052;">&quot;@bunny.net/sandbox&quot;</span>;

<span style="color:#569CD6;">const</span> sandbox = <span style="color:#569CD6;">await</span> Sandbox.create({
  apiKey: process.env.BUNNYNET_API_KEY,
  name: <span style="color:#BDE052;">&quot;hi-server&quot;</span>,
  region: <span style="color:#BDE052;">&quot;AMS&quot;</span>,
});

<span style="color:#569CD6;">await</span> sandbox.writeFiles([
  {
    path: <span style="color:#BDE052;">&quot;server.js&quot;</span>,
    content: Buffer.from(<span style="color:#BDE052;">`
      const http = require(&quot;http&quot;);
      http.createServer((req, res) =&gt; {
        res.writeHead(200, { &quot;Content-Type&quot;: &quot;text/plain&quot; });
        res.end(&quot;hi&quot;);
      }).listen(3000, () =&gt; console.log(&quot;listening on 3000&quot;));
    `</span>),
  },
]);

<span style="color:#8A8A8A;">// Start the server detached so it runs in the background.</span>
<span style="color:#569CD6;">const</span> server = <span style="color:#569CD6;">await</span> sandbox.runCommand({
  cmd: <span style="color:#BDE052;">&quot;node&quot;</span>,
  args: [<span style="color:#BDE052;">&quot;server.js&quot;</span>],
  detached: <span style="color:#569CD6;">true</span>,
});

<span style="color:#8A8A8A;">// Expose port 3000 as a public CDN URL.</span>
<span style="color:#569CD6;">const</span> url = <span style="color:#569CD6;">await</span> sandbox.exposePort(<span style="color:#D1949E;">3000</span>);
console.log(<span style="color:#BDE052;">`Open: ${url}`</span>);

<span style="color:#8A8A8A;">// Kill the server after 60 seconds.</span>
setTimeout(() =&gt; server.kill(), <span style="color:#D1949E;">60_000</span>);

<span style="color:#8A8A8A;">// Blocks until the server process exits (via the kill above).</span>
<span style="color:#569CD6;">const</span> finished = <span style="color:#569CD6;">await</span> server.wait();
console.log(finished.exitCode);

<span style="color:#8A8A8A;">// Tear down the sandbox now that the server is done.</span>
<span style="color:#569CD6;">await</span> sandbox.delete();
</pre>
<br>
<!--kg-card-end: html-->
<h2 id="isolation-you-do-not-have-to-think-about">Isolation you do not have to think about</h2><p>This is the part that lets you stop babysitting the agent.</p><p>Every Bunny Sandbox runs on gVisor. In plain terms, gVisor slips a user-space kernel between the code and the real host. To anything running inside, the sandbox looks and behaves like an ordinary Linux machine. But that code never talks directly to our kernel. It talks to gVisor, which decides what is allowed. This is a real boundary, not a normal container with the lights dimmed.</p><p>Every sandbox is walled off from every other one and from the host underneath it. An agent that goes off the rails is trapped in a room it cannot escape. That is the whole promise: the blast radius of a bad command is the sandbox, and only the sandbox.</p><h2 id="from-idea-to-public-url-without-the-detour">From idea to public URL without the detour</h2><p>Because Bunny Sandbox uses Magic Containers and runs on the same global edge, whatever the agent builds can go live on the same network that already serves traffic worldwide. There is no export step, no second platform, no repackaging. One command turns a port inside the box into a real, shareable HTTPS URL. Build it and ship it in the same room.</p><h2 id="pay-for-the-room-while-you-are-in-it">Pay for the room while you are in it</h2><p>Sandboxes are disposable by design: spin up, let the agent work, tear down. A sandbox is billed at standard Magic Containers pricing:</p><ul><li><strong>Compute:</strong> $0.02 per CPU core per hour, billed per second, up to 8 cores</li><li><strong>Memory:</strong> $0.005 per GB per hour, billed in 64 MB increments</li><li><strong>Workspace:</strong> $0.10 per GB per month on the allocated 10 GB</li><li><strong>Anycast IP:</strong> $2 per month, billed per minute (every sandbox gets one, for SSH and public URLs)</li></ul><p>Because it&apos;s per-second and per-use, cost tracks what the agent actually does. A couple of active hours of coding work run on the order of cents of compute and memory, plus a little IP time. Heavier workloads cost more, idle ones less. Delete a sandbox and it stops costing you anything.</p><h2 id="when-you-actually-need-this-and-when-you-do-not">When you actually need this (and when you do not)</h2><p>We will be straight with you, because a sandbox is a tool, not a religion.</p><p>You probably do not need a sandbox to run code you wrote and trust on your own laptop. Carry on. Sandboxes earn their keep in three situations: when the code is untrusted or agent-generated, when you are running many of these at once, or when you want to hand a running environment to your own users without handing them your infrastructure.</p><p>And one honest caveat. Isolation is a boundary, not a force field. It contains what runs inside. It does not turn bad code into good code or a leaked secret back into a safe one. Give each sandbox only the credentials the task genuinely requires. The room keeps the mess in. It is still on you not to put your crown jewels in the room.</p><h2 id="we-are-just-getting-started">We are just getting started</h2><p>Bunny Sandbox is live today, and this is the first version. Here is what is coming:</p><ul><li>More agents. Claude Code is where we started, not where we stop. We are expanding to more coding agents so you can pick the one that fits the job.</li><li>Deeper integration with the rest of bunny.net, so a sandbox can reach for the services you already use without extra plumbing.</li><li>More code syncing, so moving work between your machine and the box stops being a chore.</li><li>A built-in gateway, so putting your work online no longer means paying for a separate IP address.</li><li>More SDKs. Python and Rust are on the way, so you can drive sandboxes from those languages too.</li></ul><p>Tell us what you want to build in a box, and we will help build the box.</p><h2 id="try-it">Try it</h2><p>Want to let an agent run without watching every command it types? Give it a room. Create your first Bunny Sandbox in a couple of commands. The <a href="https://docs.bunny.net/cli/commands/sandbox" rel="noreferrer">docs</a> have the rest.</p><p>The CLI is open source, and the code&apos;s on <a href="https://github.com/BunnyWay/cli">GitHub</a> &#x2014; thanks to everyone who&apos;s contributed so far. Come tell us what you&apos;re building in <a href="https://discord.com/invite/bunnynet">Discord</a>.</p><p>AI needs a safe place to build. Now it has one on a network built to make the internet hop faster. Happy building!</p>]]></content:encoded></item><item><title><![CDATA[Manage DNS from the terminal with the bunny.net CLI]]></title><description><![CDATA[Last month we made Bunny DNS free. Today we’re making it even better. You (and your agent) can now manage and automate DNS from your terminal with the bunny.net CLI.]]></description><link>https://bunny.net/blog/manage-dns-from-the-terminal-with-bunny-net-cli/</link><guid isPermaLink="false">6a4f99f5160dc403fbfcf4eb</guid><category><![CDATA[News]]></category><dc:creator><![CDATA[Jamie Barton]]></dc:creator><pubDate>Thu, 09 Jul 2026 13:46:34 GMT</pubDate><media:content url="https://bunny.net/blog/content/images/2026/07/bunny.net-dns-added-to-CLI.png" medium="image"/><content:encoded><![CDATA[<img src="https://bunny.net/blog/content/images/2026/07/bunny.net-dns-added-to-CLI.png" alt="Manage DNS from the terminal with the bunny.net CLI"><p>The terminal has moved way beyond local dev. It&#x2019;s where applications get deployed, infrastructure gets automated, CI pipelines run, and, increasingly, where agents take action on your behalf. That raises the bar for what a CLI has to be. A thin wrapper around an API isn&apos;t enough. It needs to be predictable, scriptable, discoverable, and easy for both people and agents.</p><p>That&apos;s been the focus of the bunny.net CLI since it launched. The <a href="https://bunny.net/blog/introducing-the-bunny-net-cli/">first release</a> shipped with full Database support, letting you create databases and query them without leaving the terminal. A few weeks later, <a href="https://bunny.net/blog/build-and-deploy-scripts-at-the-edge-with-the-bunny-net-cli/">Edge Scripting landed</a>, so you could scaffold, deploy, and manage serverless functions the same way. Since then, we&apos;ve kept folding more of the platform into the same workflow.</p><p>Today, <a href="https://docs.bunny.net/dns">Bunny DNS</a> joins it.</p><p>Bunny DNS is our authoritative DNS hosting, and the routing engine behind our global CDN. It hosts your zones and records like any DNS provider, but it&apos;s also scriptable, so you can run your own code at query time to decide how a name answers, instead of just returning static records. The CLI can now manage both your records and the scripts that answer them.</p><h2 id="a-two-minute-tour"><strong>A two-minute tour</strong></h2><p>The goal we set ourselves for this release was simple: take a domain from nothing to live on bunny.net, pointed at something real, without opening the dashboard. Here&apos;s the whole arc.</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
<span style="color:#8A8A8A;"># Create the zone</span>
bunny dns zones <span style="color:#ECEC93;">add</span> example.com
&#x2713; Created DNS zone example.com (ID: <span style="color:#D1949E;">819216</span>).

Now update your nameservers at &lt;your registrar&gt; to:

 kiki.bunny.net
 coco.bunny.net

<span style="color:#8A8A8A;"># Verify nameservers are detected</span>
bunny dns zones <span style="color:#ECEC93;">ns</span> example.com
&#x2713; Nameservers detected and pointing to Bunny DNS for example.com.

<span style="color:#8A8A8A;"># Point the apex at your origin, and www straight at a pull zone</span>
bunny dns records <span style="color:#ECEC93;">add</span> example.com <span style="color:#BDE052;">&apos;@&apos;</span> A <span style="color:#D1949E;">198.51.100.1</span>
bunny dns records <span style="color:#ECEC93;">add</span> example.com www PULLZONE my-site
</pre>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>Once your registrar picks up the new nameservers, the domain is live: the apex resolved directly, and <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">www</code> served from your pull zone across the bunny.net network. Your DNS and CDN are configured together in four commands.</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>That&apos;s the scripted path. When you&apos;d rather explore, run <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny dns records add example.com</code> with no type and the CLI walks you through it, including presets that set up a whole group of records at once:</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
&#x2714; What would you like to add? &#x203A; A preset (email providers, verification, security)
? Choose a preset: &#x203A; - Use arrow-keys. Return to submit.
&#x276F;   Google Workspace  Email
    Gmail-hosted email: MX, SPF, optional DKIM and DMARC.
    Microsoft 365 (Outlook)  Email
    Zoho Mail  Email
    Mailgun  Email
    Resend  Email
    Proton Mail  Email
    Bluesky handle  Verification
    DMARC policy  Email security
    CAA (restrict certificate issuance)  Email security
    No email (anti-spoofing)  Email security
&#x2714; Google DKIM TXT value (Admin console) (blank to skip) &#x2026;
&#x2714; DMARC report email (blank to skip) &#x2026;
This will add <span style="color:#D1949E;">2</span> record(s) to &lt;yourdomain&gt;:

 Type   Name   Value                                 Priority
 MX     @      smtp.google.com                       <span style="color:#D1949E;">1</span>
 TXT    @      v=spf1 include:_spf.google.com ~all

? Add these <span style="color:#D1949E;">2</span> record(s)? &#x203A; (Y/n)
&#x2713; Applied Google Workspace to &lt;yourdomain&gt;: <span style="color:#D1949E;">2</span> record(s) added.
</pre>
<br>
<!--kg-card-end: html-->
<p>Choosing Google Workspace fills in the MX and SPF records for you, then asks for the two things it can&apos;t infer: an optional DKIM value from your Google admin console, and an optional DMARC report address. DKIM and DMARC are the parts of email security people most often skip, because they&apos;re easy to get wrong when configured by hand, so folding them into the preset means new domains start with sender authentication already in place.<br><br>The CLI shows exactly which records it will create and waits for a yes before writing any of them. Presets cover the other major mail providers too, including Microsoft 365, Zoho, Mailgun, Resend, and Proton Mail, alongside domain verification and standalone security records like DMARC and CAA.<br><br>Presets are the kind of thing a human reaches for while setting a domain up. An agent can skip straight past them with explicit flags.</p><h2 id="dns-was-always-meant-to-be-automated">DNS was always meant to be automated</h2><p>DNS sits at the start of almost every project. Before a request reaches your application, your CDN, your edge script, or your origin, DNS decides where it goes. That makes it one of the most important parts of your infrastructure, and one of the most useful things to automate.<br><br>Creating a zone for a new project, adding records as part of a deployment, pointing a hostname at a script to route requests, exporting a zone before you change it, tearing down temporary records when a preview environment is destroyed. Each is part of a workflow rather than a one-off dashboard task, and now they&apos;re all commands you can script, run in CI, or hand to an agent.<br><br>And automating it doesn&apos;t run up a usage bill. Since <a href="https://bunny.net/blog/were-making-bunny-dns-free/" rel="noopener noreferrer">Bunny DNS dropped per-query pricing</a>, DNS hosting is now included for up to 500 domains per account, with no per-query charges and no query limits. No matter how often your pipelines and agents hit it, the queries are free.<br><br>Here are the DNS commands the CLI now supports.</p><h2 id="zones">Zones</h2>
<!--kg-card-begin: html-->
<p>You&apos;ve already seen <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">zones add</code>, which creates a zone and prints the bunny.net nameservers to set at your registrar. The rest of the lifecycle is where you&apos;d expect:</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
bunny dns zones <span style="color:#ECEC93;">list</span>
bunny dns zones <span style="color:#ECEC93;">show</span> example.com
bunny dns zones <span style="color:#ECEC93;">remove</span> example.com
</pre>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>A zone is identified by its domain name or its numeric ID, so <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">example.com</code> and the zone ID are interchangeable wherever a command takes one.</p>
<!--kg-card-end: html-->
<h3 id="link-a-directory-to-a-zone">Link a directory to a zone</h3>
<!--kg-card-begin: html-->
<p>Passing the domain on every command gets repetitive. Link the working directory to a zone once and the rest of the <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">dns</code> commands know which zone you mean:</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
bunny dns zones <span style="color:#ECEC93;">link</span> example.com
</pre>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>This writes a small <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">.bunny/dns.json</code> manifest into your project. From then on, zone and record commands run against the linked zone without naming it. It&apos;s the same pattern as <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny db link</code>, <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny scripts link</code>, and soon <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny storage link</code>, and as with those, the manifest is per-developer state, so add <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">.bunny/</code> to your <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">.gitignore</code>. When a command falls back to the interactive picker, it offers to link the directory for you.</p>
<!--kg-card-end: html-->
<h2 id="records">Records</h2>
<!--kg-card-begin: html-->
<p>Records live inside a zone. <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">records add</code> takes the name, type, and value, with the zone apex written as <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">&apos;@&apos;</code>:</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
bunny dns records <span style="color:#ECEC93;">add</span> example.com api A <span style="color:#D1949E;">198.51.100.1</span>
bunny dns records <span style="color:#ECEC93;">add</span> example.com <span style="color:#BDE052;">&apos;@&apos;</span> MX mail.example.com <span style="color:#D1949E;">10</span>
bunny dns records <span style="color:#ECEC93;">list</span> example.com
</pre>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>Run <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny dns records add</code> with the type left off and it walks you through an interactive wizard instead. Bunny DNS supports the record types you&apos;d expect alongside a few bunny-specific ones, including <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">PULLZONE</code> to point a name straight at a pull zone and <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">SCRIPT</code> to answer it with Scriptable DNS.</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>Updating and removing records works by record ID, which you can read from <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">records list</code>, or you can leave the ID off and pick from a list. Only the fields you pass are changed:</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
bunny dns records <span style="color:#ECEC93;">update</span> example.com <span style="color:#D1949E;">123</span> <span style="color:#ECEC93;">--value</span> <span style="color:#D1949E;">198.51.100.2</span>
bunny dns records <span style="color:#ECEC93;">remove</span> example.com <span style="color:#D1949E;">123</span>
</pre>
<!--kg-card-end: html-->
<h3 id="migrate-a-zone-with-bind-files">Migrate a zone with BIND files</h3><p>Moving a domain onto Bunny DNS, or taking a backup before a big change, comes down to two commands. Export writes a standard BIND zone file, and import reads one back in:</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
bunny dns records <span style="color:#ECEC93;">export</span> example.com <span style="color:#ECEC93;">--save</span>
bunny dns records <span style="color:#ECEC93;">import</span> example.com ./example.com.zone
</pre>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p><code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">--save</code> writes <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">example.com.zone</code> alongside you. Omit it to print the zone to stdout and pipe it wherever you like.</p>
<!--kg-card-end: html-->
<h3 id="dnssec-and-zone-settings">DNSSEC and zone settings</h3><p>Turn on DNSSEC and the CLI prints the DS record to hand to your registrar:</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
bunny dns zones dnssec <span style="color:#ECEC93;">enable</span> example.com
</pre>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p><a href="https://bunny.net/blog/were-making-bunny-dns-free/">Bunny DNS implements DNSSEC</a> without exposing your zone&apos;s structure, so this is one you can switch on without the usual hesitation. Query logging and per-zone statistics are a command away too, with <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny dns zones logging</code> and <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny dns zones stats</code>.</p>
<!--kg-card-end: html-->
<h2 id="scriptable-dns-from-the-cli">Scriptable DNS from the CLI</h2>
<!--kg-card-begin: html-->
<p>Scriptable DNS runs your code at query time to decide how a name should answer, which is how you build geo routing, weighted answers, failover, and closest-region selection. It&apos;s a separate runtime from Edge Scripts, so it has its own command group under <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny dns scripts</code>.</p>
<!--kg-card-end: html-->
<p>The flow mirrors Edge Scripting. Scaffold a project from an example, deploy the script, and attach it to a hostname:</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
bunny dns scripts <span style="color:#ECEC93;">init</span> geo-router <span style="color:#ECEC93;">--example</span> geo <span style="color:#ECEC93;">--deploy</span>
cd geo-router
bunny dns scripts <span style="color:#ECEC93;">deploy</span> handleQuery.js
bunny dns scripts <span style="color:#ECEC93;">attach</span> example.com geo <span style="color:#ECEC93;">--script</span> &lt;id&gt;
</pre>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p><code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">init</code> drops a <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">handleQuery</code> entry file. The starters cover the common routing shapes (<code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">geo</code>, <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">closest</code>, <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">weighted</code>, <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">failover</code>, and <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">pullzone</code>), plus an <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">empty</code> one to begin from scratch:</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
<span style="color:#8A8A8A;">/// &lt;reference types=&quot;@bunny.net/scriptable-dns-types&quot; /&gt;</span>

<span style="color:#8A8A8A;">/** @param {DnsRequest} query */</span>
<span style="color:#569CD6;">export</span> <span style="color:#569CD6;">default</span> <span style="color:#569CD6;">function</span> handleQuery(query) {
  <span style="color:#569CD6;">const</span> { geoLocation, serverZone } = query.request;

  <span style="color:#569CD6;">return</span> <span style="color:#569CD6;">new</span> TxtRecord(
    <span style="color:#BDE052;">`country=${geoLocation.country} zone=${serverZone} asn=${geoLocation.asn}`</span>,
    <span style="color:#D1949E;">30</span>,
  );
}
</pre>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>That reference line at the top is new too: <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">@bunny.net/scriptable-dns-types</code> ships ambient types for the Scriptable DNS runtime, so your editor autocompletes queries, records, and helpers even in plain JavaScript. No more guessing, no docs tab.</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>A script does nothing to your DNS until it backs a record, which is what <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">attach</code> handles: it adds a <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">SCRIPT</code> record so <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">geo.example.com</code> is answered by your code.</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>Because that record changes how a name resolves, <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">attach</code> always asks before it writes, and attaching at the apex prints a louder warning and lists anything already there first. In a pipeline, where there&apos;s no one to confirm, it refuses to write unless you pass <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">--force</code>.</p>
<!--kg-card-end: html-->
<p>That turns DNS into something you build with. Records in a table become routing logic that lives alongside the rest of your infrastructure.</p><h2 id="works-with-your-agent-and-ci">Works with your agent and CI</h2><p>An agent driving a tool needs three things: to discover what it can do, to call it and get a predictable result, and to recover when something goes wrong. The DNS commands are built for all three, the same way the rest of the CLI is.</p>
<!--kg-card-begin: html-->
<p>Discovery comes from a consistent command structure and the skill files we ship alongside the CLI. An agent doesn&apos;t have to reverse-engineer the dashboard. It reads the skill, finds that zones, records, and scripts each follow the same shape, and knows that <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny dns &lt;command&gt; --help</code> will spell out the flags.</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>Reliable calls come from being flags-first and deterministic. Pass the domain, the record, and the values and you get the same result every time, with no prompt waiting in the way. Linked manifests mean an agent isn&apos;t juggling zone IDs in its context, and <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">--output json</code> turns any command into something it can parse rather than scrape.</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>Recovery comes from how the CLI fails. Errors are written in plain language with a hint at the fix, exit codes are meaningful, and under <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">--output json</code> the error comes back as structured JSON too. An agent can tell a fixable mistake from an internal one and respond, instead of retrying blindly. Prompts and pickers are TTY-aware so they step out of the way in CI, and destructive commands like <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">records remove</code> and <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">scripts attach</code> take <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">--force</code> for the same reason.</p>
<!--kg-card-end: html-->
<p>The result is that the commands you run are the commands your automation runs, and the commands your agent runs. No separate integration to maintain, no protocol to configure.</p><h2 id="one-cli-for-the-platform">One CLI for the platform</h2><p>Every release brings another part of bunny.net into the workflow developers already use:</p><ul><li>Databases</li><li>Edge Scripts</li><li>DNS and Scriptable DNS</li><li>Agentic workflows across all of them</li></ul><p>Coming up next are Magic Containers and Storage.</p><p>Get started:</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
npm <span style="color:#ECEC93;">install</span> <span style="color:#ECEC93;">-g</span> @bunny.net/cli
bunny <span style="color:#ECEC93;">login</span>
bunny dns zones <span style="color:#ECEC93;">add</span> example.com
</pre>
<br>
<!--kg-card-end: html-->
<p>The CLI is open source and built in public. If something&apos;s broken, <a href="https://github.com/BunnyWay/cli/issues" rel="noopener noreferrer">open an issue</a>. If you&apos;ve built something with it, come and tell us about it in <a href="https://discord.com/invite/bunnynet" rel="noopener noreferrer">Discord</a>.</p>]]></content:encoded></item><item><title><![CDATA[What hundreds of billions of requests reveal about edge security in 2026]]></title><description><![CDATA[Hundreds of billions of requests pass through the bunny.net edge network every day from websites and applications around the world. That gives us a unique view into how attacks are evolving, what malicious traffic really looks like, and the techniques attackers continue to rely on.]]></description><link>https://bunny.net/blog/what-hundreds-of-billions-of-requests-reveal-about-edge-security-in-2026/</link><guid isPermaLink="false">6a465ac8160dc403fbfcf4c9</guid><category><![CDATA[Security]]></category><dc:creator><![CDATA[Joe Connolly]]></dc:creator><pubDate>Thu, 02 Jul 2026 13:00:00 GMT</pubDate><media:content url="https://bunny.net/blog/content/images/2026/07/bunny-edge-security-report.png" medium="image"/><content:encoded><![CDATA[<img src="https://bunny.net/blog/content/images/2026/07/bunny-edge-security-report.png" alt="What hundreds of billions of requests reveal about edge security in 2026"><p>Hundreds of billions of requests pass through the bunny.net edge network every day from websites and applications around the world. That gives us a unique view into how attacks are evolving, what malicious traffic really looks like, and the techniques attackers continue to rely on.</p><p>To share those insights, we&apos;ve published the <strong>bunny.net 2026 Edge Security Report</strong>.</p><p>Built from <strong>149 days of production telemetry</strong> across our global edge network, the report explores the attack trends, traffic patterns, and architectural lessons we&apos;ve observed while helping protect applications at internet scale.</p><p><a href="https://bunny.net/edge-security-report-2026/" rel="noreferrer"><strong>Hop into the 2026 Edge Security Report &#x2192;</strong></a></p><h2 id="what-youll-learn">What you&apos;ll learn</h2><p>Rather than relying on surveys or industry estimates, every finding is backed by real production traffic observed across the bunny.net edge network.</p><p>The report covers:</p><ul><li>The biggest edge security trends shaping 2026.</li><li>The attack techniques we see most often in production.</li><li>How automated traffic is changing, and what that means for modern security.</li><li>Common architectural pitfalls that can leave applications exposed.</li><li>Real-world attack case studies and the lessons they reveal.</li></ul><p>Whether you&apos;re responsible for infrastructure, security, or platform engineering, the report gives you a data-backed view of how today&#x2019;s attacks behave in production, so you can stay one hop ahead.</p><p><a href="https://bunny.net/edge-security-report-2026/" rel="noreferrer"><strong>Download the full Edge Security Report and explore the findings for yourself.</strong></a></p>]]></content:encoded></item><item><title><![CDATA[We’re making Bunny DNS free: because a faster internet won’t build itself]]></title><description><![CDATA[Your DNS bill shouldn't scale with your traffic. So we removed query fees from Bunny DNS entirely. No per-request billing, no query limits, free DNS hosting for up to 500 domains, and nothing critical locked behind some enterprise plan.]]></description><link>https://bunny.net/blog/were-making-bunny-dns-free/</link><guid isPermaLink="false">6a3a7966160dc403fbfcf4a9</guid><category><![CDATA[News]]></category><dc:creator><![CDATA[Dejan Grofelnik Pelzel]]></dc:creator><pubDate>Wed, 24 Jun 2026 05:58:00 GMT</pubDate><media:content url="https://bunny.net/blog/content/images/2026/06/DNS_-Free-DNS-blog-post--1-.png" medium="image"/><content:encoded><![CDATA[<img src="https://bunny.net/blog/content/images/2026/06/DNS_-Free-DNS-blog-post--1-.png" alt="We&#x2019;re making Bunny DNS free: because a faster internet won&#x2019;t build itself"><p>At bunny.net, our mission has always been ambitious but focused: <strong>help make the internet hop faster.</strong></p><p>To do that, we&#x2019;ve built a massive global network spanning 119 locations and counting. Today, this network powers over 1.5 million websites and consistently delivers some of the fastest content delivery around the globe. But while deploying thousands of servers globally is an impressive feat on its own, the hardware itself does not explain how bunny.net is able to deliver such an impressive level of performance.</p><p>The real secret hides under the hood, embedded in the routing engine that directs every request, every user, and sends traffic exactly where it needs to go. That engine is Bunny DNS.</p><h2 id="from-internal-engine-to-200-billion-customer-queries-per-month">From internal engine to 200 billion customer queries per month</h2><p>Originally, <a href="https://docs.bunny.net/dns">Bunny DNS</a> was built with one simple goal: to build the most advanced routing engine possible, capable of analyzing every DNS query and directing it to the optimal destination for serving your content. Even to this day, it&#x2019;s what makes Bunny CDN achieve it&#x2019;s exceptional performance.</p><p>Four years ago, we took everything we had learned from designing and running this system and turned it into a product our users could use themselves. With Bunny DNS, we&#x2019;ve upgraded DNS from being a basic record lookup table into a globally distributed, smart routing engine. Instead of just returning static records, it allows developers to use latency data, health checks, and even JavaScript to dynamically determine exactly where requests should go.</p><p>We applied our traditional mantra. We made it affordable, scalable, and added a generous free tier. The response was incredible, and today, Bunny DNS powers over 300,000 domains and handles nearly 200 billion queries every single month.</p><p>But as we looked closer at our mission, we realized something needed to change.</p><h2 id="why-dns-shouldnt-cost-extra">Why DNS shouldn&apos;t cost extra</h2><p>Dealing with infrastructure costs is already notoriously complex. You shouldn&apos;t have to stress about pricing tiers or whether a sudden spike of a million queries is going to result in an unpredictable bill.</p><p>If we truly believe in our mission to help make the internet hop faster, then the fundamental system that sits between your users and your services shouldn&apos;t be a premium add-on. It should be accessible to everyone.</p><p><strong>So, we&#x2019;ve eliminated DNS query fees entirely.</strong></p><p>Bunny DNS no longer charges for DNS queries and includes free DNS hosting for up to 500 domains per account. There are no query limits, no per-request billing, and no critical features hidden behind enterprise plans. (Yes, that includes smart records and health monitoring too.)</p><p>As with all bunny.net services, accounts using the platform are subject to our standard $1/month minimum spend, but DNS itself no longer incurs any usage-based charges.</p><h2 id="the-entry-point-for-everything-else">The entry point for everything else</h2><p>Making Bunny DNS free doesn&apos;t mean we&apos;re losing interest in it. Quite the opposite. More than ever, we view DNS as the core product that glues our entire platform together. It&#x2019;s the starting line for everything else your application does.</p><p>Getting that starting line set up is now easier than ever. If you&apos;re migrating from somewhere else, our new automatic zone scanning checks your domain&apos;s most common record names and types, reconstructing your zone so you only have to make a few tweaks instead of starting from scratch. (You can also just upload a BIND file if you prefer.)</p><p>Once your records are in place, the real magic happens. With 1-Click Acceleration, you can enable the CDN directly from your DNS records. We&#x2019;ll spin up a Pull Zone behind the scenes and instantly start routing requests through our edge network. Once traffic is flowing, 1-Click Security lets you enable Bunny Shield instantly to filter traffic at the edge, blocking common exploits and absorbing DDoS attacks before they ever touch your origin server.</p><p>Performance, security, and routing are now unified in one place, rather than stitched together after the fact. Our goal is to keep evolving this with even more advanced record types.</p><h2 id="beyond-making-it-free-we%E2%80%99re-also-making-it-better">Beyond making it free, we&#x2019;re also making it better</h2><p>Saving on costs is great, but ultimately, what drives us at bunny.net is building incredible products. While all of that was happening, we&#x2019;ve also been making steady changes to DNS itself. There haven&#x2019;t been many major releases, but rather a lot of smaller improvements over time.</p><h3 id="ipv6-is-no-longer-optional">IPv6 is no longer optional</h3><p>More and more networks default to IPv6 now, especially on mobile. So we made sure everything on our side just works in a dual-stack world.</p><p>If you&#x2019;re using Bunny DNS, your nameserver records already resolve over both IPv4 and IPv6. There&#x2019;s nothing to configure, nothing to migrate. It just works the way it probably should have a while ago.</p><h3 id="we%E2%80%99ve-added-dnssec-without-the-usual-trade-offs">We&#x2019;ve added DNSSEC, without the usual trade-offs</h3><p>DNSSEC is one of those things people want in theory but hesitate to adopt in practice.</p><p>Part of that is the complexity, but part of it is also that traditional DNSSEC can expose information about your zone that you might not want to share.</p><p>We implemented DNSSEC with NSEC Black Lies to get around that. You still get the validation and protection against tampering, but without making it easy for someone to walk your entire domain structure.</p><p>It&#x2019;s one of those details most people won&#x2019;t notice directly, but it does change how comfortable you can be with <a href="https://docs.bunny.net/dns/dnssec">turning DNSSEC on</a>.</p><h3 id="we%E2%80%99ve-modernized-record-types">We&#x2019;ve modernized record types</h3><p>DNS has moved far beyond simple address records. Modern applications increasingly rely on DNS not just to point a name at an IP address, but to describe how clients should connect, how certificates should be validated, and how security settings should be managed over time.</p><p>That&#x2019;s why we&#x2019;ve been expanding Bunny DNS with support for more advanced record types.</p><p>We&#x2019;ve added support for HTTPS and SVCB records, which let you hint how clients should connect to your services. TLSA records are there if you&#x2019;re using DANE and want tighter control over certificate validation. And CDS and CDNSKEY help automate DNSSEC key management so you&#x2019;re not rotating things by hand.</p><p>None of this is particularly flashy, but it&#x2019;s the kind of stuff you end up needing once you move past a basic setup.</p><h2 id="helping-you-build-faster">Helping you build faster</h2><p>We take our mission seriously, and the &#x201C;help&#x201D; part of it is perhaps the most important. By dropping usage charges for DNS and integrating it deeply with the rest of our stack, starting with CDN and <a href="https://docs.bunny.net/shield">Shield</a>, we want to help you build faster, safer, and more resilient applications without worrying about arbitrary limits.</p><p>In a world where everyone simply wants to ship as many features as fast as possible, we&#x2019;re focused on something else: making Bunny DNS incredible to use and seamlessly integrated into everything else we do, so you can build faster, sleep easier, and ultimately create build better user experiences for everyone.</p><p>If you haven&#x2019;t tried Bunny DNS in a while, now&#x2019;s a good time to take another look. You can add your zones, point your domains, and leave it at that, or start layering on CDN and Shield when you need them.</p><p>It&#x2019;s free now, so you can <a href="https://dash.bunny.net/auth/login">log in</a> or <a href="https://dash.bunny.net/auth/register">sign up</a> and start using it straight away.</p>]]></content:encoded></item><item><title><![CDATA[Bring Bunny Storage into DatoCMS without the copy-paste]]></title><description><![CDATA[Bunny Storage and DatoCMS now work together natively. The bunny.net Picker is available in the DatoCMS plugin directory: browse folders, search, drag to upload, and preview files without ever leaving your content editor.]]></description><link>https://bunny.net/blog/bring-bunny-storage-into-datocms-without-the-copy-paste/</link><guid isPermaLink="false">6a2fcc87160dc403fbfcf46f</guid><category><![CDATA[News]]></category><dc:creator><![CDATA[Jamie Barton]]></dc:creator><pubDate>Mon, 15 Jun 2026 10:28:18 GMT</pubDate><media:content url="https://bunny.net/blog/content/images/2026/06/Bring-Bunny-Storage-into-DatoCMS--without-the-copy-paste--2-.png" medium="image"/><content:encoded><![CDATA[<img src="https://bunny.net/blog/content/images/2026/06/Bring-Bunny-Storage-into-DatoCMS--without-the-copy-paste--2-.png" alt="Bring Bunny Storage into DatoCMS without the copy-paste"><p>Picking the right asset shouldn&apos;t mean juggling two dashboards.</p><p>Until recently, using Bunny Storage with DatoCMS looked something like this:</p><ul><li>Log into Storage</li><li>Find the file</li><li>Copy the path</li><li>Switch to DatoCMS</li><li>Paste it into a JSON field <em>and hope you got the URL structure right</em></li><li><em>Repeat for every asset</em></li></ul><p>It worked. It just didn&apos;t feel like it should. Content editors deserve better.</p>
<!--kg-card-begin: html-->
<figure class="kg-card kg-embed-card" style="width:100%; max-width:100%; margin:2rem 0;">
  <div style="position:relative; width:100%; max-width:100%; padding-bottom:56.25%; height:0; overflow:hidden;">
    <iframe src="https://www.youtube.com/embed/VamXy_d-fH8" title="YouTube video player" style="position:absolute; top:0; left:0; width:100%; height:100%; border:0;" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen>
    </iframe>
  </div>
</figure>
<!--kg-card-end: html-->
<p>The setup came out of a conversation with a customer who&apos;d built their own version using Edge Scripting and manual JSON fields. The pieces were all there, but the day-to-day experience of choosing a file was rough on editors and even rougher to maintain across projects. We kept thinking about it and decided to build something better for everyone using Bunny Storage with DatoCMS.</p><p>The result is the <a href="https://www.datocms.com/marketplace/plugins/i/@bunny.net/datocms-plugin" rel="noreferrer"><strong>bunny.net Picker</strong></a>, a DatoCMS plugin that turns asset selection into a single, native step inside your content editor.</p><p>Once installed, editors get a proper file picker right where they need it. Browse folders in your Storage Zone. Search the current folder. Drag files in to upload. Preview images and media before selecting. Open the selected file in a new tab when you want to double-check it. Everything happens inside DatoCMS against your live Storage Zone.</p><p>When you set up your DatoCMS schema, you choose how each field should behave. Add a JSON field, then pick the presentation that fits the content:</p><ul><li><strong>bunny.net Asset</strong> for fields that hold a single file</li><li><strong>bunny.net Assets</strong> for fields that hold several</li></ul><figure class="kg-card kg-image-card"><img src="https://bunny.net/blog/content/images/2026/06/CleanShot-2026-04-28-at-10.50.11@2x.png" class="kg-image" alt="Bring Bunny Storage into DatoCMS without the copy-paste" loading="lazy" width="1630" height="1407" srcset="https://bunny.net/blog/content/images/size/w600/2026/06/CleanShot-2026-04-28-at-10.50.11@2x.png 600w, https://bunny.net/blog/content/images/size/w1000/2026/06/CleanShot-2026-04-28-at-10.50.11@2x.png 1000w, https://bunny.net/blog/content/images/size/w1600/2026/06/CleanShot-2026-04-28-at-10.50.11@2x.png 1600w, https://bunny.net/blog/content/images/2026/06/CleanShot-2026-04-28-at-10.50.11@2x.png 1630w" sizes="(min-width: 720px) 720px"></figure><p>Either presentation accepts any file type your Storage Zone permits, so the same picker works for hero images, product photography, PDFs, video, audio, fonts, or anything else you keep in bunny.net.</p><p>For developers, the plugin stores a clean, predictable JSON value in the field. A single asset looks like this after parsing:</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
{
  <span style="color:#D1949E;">&quot;path&quot;</span>: <span style="color:#BDE052;">&quot;folder/image.jpg&quot;</span>,
  <span style="color:#D1949E;">&quot;filename&quot;</span>: <span style="color:#BDE052;">&quot;image.jpg&quot;</span>,
  <span style="color:#D1949E;">&quot;size&quot;</span>: <span style="color:#D1949E;">153240</span>,
  <span style="color:#D1949E;">&quot;contentType&quot;</span>: <span style="color:#BDE052;">&quot;image/jpeg&quot;</span>,
  <span style="color:#D1949E;">&quot;url&quot;</span>: <span style="color:#BDE052;">&quot;https://my-zone.b-cdn.net/folder/image.jpg&quot;</span>,
  <span style="color:#D1949E;">&quot;guid&quot;</span>: <span style="color:#BDE052;">&quot;8e3b3b4e-1f6a-4b2a-9b8a-2f1e6d7c8a9b&quot;</span>,
  <span style="color:#D1949E;">&quot;lastChanged&quot;</span>: <span style="color:#BDE052;">&quot;2025-04-30T10:21:00.000&quot;</span>
}
</pre>
<br>
<!--kg-card-end: html-->
<p>Multi-asset fields return an array of the same shape:</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
[
  {
    <span style="color:#D1949E;">&quot;path&quot;</span>: <span style="color:#BDE052;">&quot;folder/image.jpg&quot;</span>,
    <span style="color:#D1949E;">&quot;filename&quot;</span>: <span style="color:#BDE052;">&quot;image.jpg&quot;</span>,
    <span style="color:#D1949E;">&quot;size&quot;</span>: <span style="color:#D1949E;">153240</span>,
    <span style="color:#D1949E;">&quot;contentType&quot;</span>: <span style="color:#BDE052;">&quot;image/jpeg&quot;</span>,
    <span style="color:#D1949E;">&quot;url&quot;</span>: <span style="color:#BDE052;">&quot;https://my-zone.b-cdn.net/folder/image.jpg&quot;</span>,
    <span style="color:#D1949E;">&quot;guid&quot;</span>: <span style="color:#BDE052;">&quot;8e3b3b4e-1f6a-4b2a-9b8a-2f1e6d7c8a9b&quot;</span>,
    <span style="color:#D1949E;">&quot;lastChanged&quot;</span>: <span style="color:#BDE052;">&quot;2025-04-30T10:21:00.000&quot;</span>
  }
]
</pre>
<br>
<!--kg-card-end: html-->
<p>The same value comes through the DatoCMS GraphQL API, so you can query the field like any other piece of content:</p><figure class="kg-card kg-image-card"><img src="https://bunny.net/blog/content/images/2026/06/datocms-graphql-response.png" class="kg-image" alt="Bring Bunny Storage into DatoCMS without the copy-paste" loading="lazy" width="2000" height="977" srcset="https://bunny.net/blog/content/images/size/w600/2026/06/datocms-graphql-response.png 600w, https://bunny.net/blog/content/images/size/w1000/2026/06/datocms-graphql-response.png 1000w, https://bunny.net/blog/content/images/size/w1600/2026/06/datocms-graphql-response.png 1600w, https://bunny.net/blog/content/images/size/w2400/2026/06/datocms-graphql-response.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>That&apos;s the whole integration. No edge script required, no per-project duplication, no URL stitching in the CMS.</p><p>Configuration is equally lightweight. Point the plugin at your Storage Zone name, API key, Pull Zone hostname, and region, and you&apos;re done.</p><figure class="kg-card kg-image-card"><img src="https://bunny.net/blog/content/images/2026/06/CleanShot-2026-04-28-at-05.02.36@2x.png" class="kg-image" alt="Bring Bunny Storage into DatoCMS without the copy-paste" loading="lazy" width="1424" height="1178" srcset="https://bunny.net/blog/content/images/size/w600/2026/06/CleanShot-2026-04-28-at-05.02.36@2x.png 600w, https://bunny.net/blog/content/images/size/w1000/2026/06/CleanShot-2026-04-28-at-05.02.36@2x.png 1000w, https://bunny.net/blog/content/images/2026/06/CleanShot-2026-04-28-at-05.02.36@2x.png 1424w" sizes="(min-width: 720px) 720px"></figure><p>All Bunny Storage regions are supported, including Frankfurt, London, New York, Los Angeles, Singapore, Stockholm, S&#xE3;o Paulo, Johannesburg, and Sydney. Localized JSON fields work out of the box, and existing fields using the older single-asset editor keep the same stored value format, so nothing breaks when you upgrade.</p><h2 id="image-transformations-with-bunny-optimizer">Image transformations with Bunny Optimizer</h2>
<!--kg-card-begin: html-->
<p>
  If your Pull Zone has <a href="https://docs.bunny.net/optimizer/quickstart">Bunny Optimizer</a> enabled, you can transform <strong>images</strong> on the fly by appending query parameters to the stored <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">url</code>.
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
<span style="color:#569CD6;">const</span> asset = JSON.parse(record.bunnyAsset)
<span style="color:#569CD6;">const</span> thumbnail = <span style="color:#BDE052;">`${asset.url}?width=400&amp;height=300&amp;aspect_ratio=4:3`</span>
<span style="color:#569CD6;">const</span> webp = <span style="color:#BDE052;">`${asset.url}?format=webp&amp;quality=80`</span>
</pre>
<br>
<!--kg-card-end: html-->
<p>Common parameters:</p>
<!--kg-card-begin: html-->
<ul>
  <li><code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">width</code>, <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">height</code> to resize images</li>
  <li><code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">aspect_ratio</code> to crop to a ratio, for example <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">16:9</code></li>
  <li><code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">format</code> to convert to <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">webp</code>, <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">avif</code>, <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">jpg</code>, or <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">png</code></li>
  <li><code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">quality</code> for compression quality from <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">0</code> to <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">100</code></li>
</ul>
<!--kg-card-end: html-->
<p>See the <a href="https://docs.bunny.net/optimizer/quickstart">Bunny Optimizer docs</a> for the full list of options. Optimizer <strong>must be enabled</strong> on your Pull Zone for these parameters to take effect.</p><p>Building this also turned out to be a genuinely good developer experience. The DatoCMS Plugin SDK lets you develop with React and Vite while connected locally to your project, which made iterating on the picker feel closer to building a normal app than wrestling with a CMS extension. If you want to extend the plugin or build your own, <a href="https://www.datocms.com/docs/plugin-sdk/build-your-first-plugin">their Plugin SDK docs</a> are a good place to start. All of the code is <a href="https://github.com/jamie-at-bunny/datocms-bunny-picker">open source on GitHub</a>, and <a href="http://npmjs.org/@bunny.net/datocms-plugin">available on NPM</a>.</p><p>The <a href="http://bunny.net">bunny.net</a> Picker is available now in the DatoCMS plugin directory. Install it, point it at your Storage Zone, and give your editors the picker they should have had all along.</p>]]></content:encoded></item><item><title><![CDATA[How to tell if scrapers are eating your bandwidth]]></title><description><![CDATA[If you aren't using Bunny Shield and have noticed anomalies in your bandwidth, read this guide to diagnose what's causing it.]]></description><link>https://bunny.net/blog/how-to-tell-if-scrapers-are-eating-your-bandwidth/</link><guid isPermaLink="false">6a2c02ad160dc403fbfcf455</guid><category><![CDATA[Tips and Tricks]]></category><dc:creator><![CDATA[Dino Kukic]]></dc:creator><pubDate>Fri, 12 Jun 2026 13:05:50 GMT</pubDate><media:content url="https://bunny.net/blog/content/images/2026/06/how-to-tell-if-scrapers-are-eating-your-bandwidth.png" medium="image"/><content:encoded><![CDATA[<img src="https://bunny.net/blog/content/images/2026/06/how-to-tell-if-scrapers-are-eating-your-bandwidth.png" alt="How to tell if scrapers are eating your bandwidth"><p><a href="https://bunny.net/shield/">Bunny Shield</a> makes identifying and blocking bots trivial. However, if you aren&#x2019;t using it and have noticed anomalies in your bandwidth, we&#x2019;ve prepped this guide to help you determine whether the reason is a pop star tweeting about your website or, unfortunately, a surge in bot traffic that&apos;s now costing you real money.</p><h2 id="not-all-bots-are-the-bad-guys">Not all bots are the bad guys</h2><p>Before you start blocking things, it&apos;s worth understanding what&apos;s actually out there. The term &quot;bot&quot; covers everything from the crawler that helps get your content in Google to the script copying your entire product catalog to sell to a competitor. Treating them all the same is how you accidentally remove your site from search results.</p><p>Here&apos;s the roughly sorted landscape.</p><h3 id="search-engine-crawlers">Search engine crawlers</h3><p><a href="https://developers.google.com/search/docs/crawling-indexing/googlebot">Googlebot</a>, <a href="https://www.bing.com/webmasters/help/which-crawlers-does-bing-use-8c184ec0">Bingbot</a>, and, yeah, mostly Googlebot. They crawl and index your content and then serve it for relevant queries in the search engine results page (SERP). For the most part, you want these, and blocking them might harm your site unless you don&#x2019;t need traffic coming from search engines at all. One caveat here is that Bingbot increasingly feeds Copilot, Applebot feeds Apple Intelligence, and Google&apos;s crawl feeds Gemini. So they are being used for other things as well, and if those use cases aren&apos;t an issue, this is actually a good thing. Your site is crawled once (periodically) for multiple purposes. It become an issue when you want one, but don&#x2019;t want the other.</p><h3 id="ai-search-bots">AI search bots</h3><p><a href="https://developers.openai.com/api/docs/bots#:~:text=Description%20%26%20details-,OAI%2DSearchBot,-OAI%2DSearchBot%20is">OAI-SearchBot</a>, <a href="https://developers.openai.com/api/docs/bots#:~:text=com/gptbot.json-,ChatGPT%2DUser,-OpenAI%20also%20uses">ChatGPT-User</a>, <a href="https://docs.perplexity.ai/docs/resources/perplexity-crawlers#:~:text=Description-,PerplexityBot,-PerplexityBot%20is%20designed">PerplexityBot</a>, <a href="https://support.claude.com/en/articles/8896518-does-anthropic-crawl-data-from-the-web-and-how-can-site-owners-block-the-crawler#:~:text=directed%20web%20search.-,Claude%2DSearchBot,-Claude%2DSearchBot%20navigates">Claude-SearchBot</a>, and others. These fetch your page in real time to answer a user&apos;s current question, often with a citation back to you. They&apos;re more like search crawlers than training crawlers because they can send real visitors your way.</p><h3 id="ai-training-crawlers">AI training crawlers</h3><p><a href="https://app.notion.com/p/url">GPTBot</a>, <a href="https://app.notion.com/p/url">ClaudeBot</a>, <a href="https://app.notion.com/p/url">Meta-ExternalAgent</a>, and the rest. They collect content to train large language models. They can use real bandwidth and generally don&apos;t send traffic back the way a search crawler does, though that may be changing as AI assistants increasingly cite and link their sources. Whether that&apos;s worth the bandwidth depends on how you value having your content represented in the models.</p><h3 id="agentic-and-assistant-bots">Agentic and assistant bots</h3><p>These are bots completing a task on behalf of a specific user, such as booking, buying, comparing, summarizing, and more. These often represent a real person with real intent, so blocking them can mean blocking a customer. The category is immature and hard to identify cleanly.</p><h3 id="marketing-intelligence-crawlers">Marketing intelligence crawlers</h3><p><a href="https://ahrefs.com/robot#:~:text=Our%20bots-,AhrefsBot,-User%2Dagent%20string">AhrefsBot</a>, <a href="https://www.semrush.com/bot/">SemrushBot</a>, and similar. They crawl your site to build the commercial SEO datasets behind tools marketers use to track rankings, find backlinks, and size up competitors. The value to you is indirect and slightly circular. These crawlers help build the same SEO datasets you might use to analyze competitors. Whether that&#x2019;s worth the crawl is a judgment call.</p><h3 id="social-link-preview-bots">Social / link-preview bots</h3><p><a href="https://developers.facebook.com/docs/sharing/webmasters/web-crawlers/#:~:text=with%20your%20site.-,FacebookExternalHit,-The%20primary%20purpose">FacebookExternalHit</a>, Twitterbot, LinkedInBot, Slackbot, Discordbot, etc. They fetch a page to build the preview card when someone shares your link. These are triggered by real people sharing your content. If you block them, your links may render as ugly bare URLs everywhere. They&apos;re generally low volume but potentially high value.</p><h3 id="commercial-vertical-scrapers">Commercial / vertical scrapers</h3><p>Price-comparison engines, job aggregators, review aggregators. Whether you want them depends entirely on your business. A price comparison bot is great if you want to be compared and a problem if a competitor is using it to undercut you.</p><h3 id="research-archival-crawlers">Research / archival crawlers</h3><p>The Internet Archive&apos;s crawler, academic datasets, and preservation projects. Usually benign and often a public good. Common Crawl is the one that complicates the picture a little bit because it&apos;s a long-running open dataset used widely in research, but it&apos;s also been a common source of training data for language models. This means that site owners who want to limit AI training sometimes block its crawler (<a href="https://commoncrawl.org/ccbot">CCBot</a>) too, even though its purpose is broader than that.</p><h3 id="monitoring-bots">Monitoring bots</h3><p>Uptime checkers (Pingdom, UptimeRobot), performance monitors (Catchpoint, Datadog) and your own health checks. You want the ones you recognize, especially your own. However, those are usually low volume and mostly harmless.</p><h3 id="malicious-bots">Malicious bots</h3><p>Content thieves, hostile price and data scrapers, credential-stuffing, and brute-force bots, vulnerability scanners, spam bots, and inventory-hoarding scalpers. This is usually what you want to identify, and the only hard part is detecting them because they actively try to look like everything else on this list.</p><p>So the most important thing is that any of these can be faked**.** A request claiming to be Googlebot might be a scraper wearing a sheep&#x2019;s skin.</p><h2 id="how-to-diagnose-it-from-your-logs">How to diagnose it from your logs</h2><p>Everything below runs against standard access logs. Just adjust field positions for your log format. Also, all of these are artificially generated examples, so real logs will have a little bit more nuance.</p><h3 id="growth-or-lack-of-in-analytics-sessions-and-conversions">Growth (or lack of) in analytics sessions and conversions</h3><p>Before you dig into the logs, you can just look at your bandwidth compared to analytics sessions. For example:</p>
<!--kg-card-begin: html-->
<table style="border:1px solid rgba(24, 61, 109, 0.18); border-collapse:collapse; width:100%;"> <thead> <tr style="font-weight:700; text-align:left; background:#223c6a; color:white;"> <th style="padding:12px 14px; background:rgba(255,255,255,0.03); border-bottom:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); color:white;"> </th> <th style="padding:12px 14px; border-bottom:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); color:white;"> Last month </th> <th style="padding:12px 14px; border-bottom:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); color:white;"> This month </th> <th style="padding:12px 14px; border-bottom:1px solid rgba(24, 61, 109, 0.18); color:white;"> Change </th> </tr> </thead> <tbody> <tr> <td style="padding:12px 14px; border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">Bandwidth</td> <td style="padding:12px 14px; border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">2.1 TB</td> <td style="padding:12px 14px; border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">3.8 TB</td> <td style="padding:12px 14px; vertical-align:middle; color:#183d6d;"><strong>+81%</strong></td> </tr> <tr> <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">Analytics sessions</td> <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">48,200</td> <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">49,100</td> <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;"><strong>+2%</strong></td> </tr> <tr> <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">Conversions</td> <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">1,840</td> <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">1,810</td> <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;"><strong>-2%</strong></td> </tr> </tbody> </table>
<!--kg-card-end: html-->
<p>Bandwidth is up 81% while sessions remain roughly flat. Most analytics tools run via JavaScript, and most bots don&apos;t execute JavaScript, so they&apos;re invisible to your analytics but still consume bandwidth.</p><h3 id="request-rate-per-client">Request rate per client</h3><p>Find out who&apos;s making the most requests:</p><pre><code>awk &apos;{print $1}&apos; access.log | sort | uniq -c | sort -rn | head -10
</code></pre><pre><code>  84213 47.128.44.19
  61887 47.128.44.20
  58122 47.128.44.21
   9043 66.249.66.1
   2287 81.150.12.4
    412 81.150.12.4
    389 92.40.177.22
    301 213.205.241.9
    288 51.171.38.4
    274 78.149.203.11
</code></pre><p>The top three IPs sit in one tight block, <code>47.128.44.x,</code> and each is making 50,000-80,000 requests, while your actual visitors trail off into the low hundreds. The 9,043 requests from <code>66.249.66.1</code> are from Googlebot (we&#x2019;ll go through its legitimacy later). The three at the top are a coordinated scrape from a single subnet.</p><p>One thing worth doing prior to actually eyeballing this is calculating the median requests per IP first, as this will give you a baseline for what traffic normally looks like on your website. You want to identify clients making 10 or 100 times the median.</p><h3 id="the-asset-loading-signature">The asset-loading signature</h3><p>This is one of the cleanest tells you have. When a real browser loads a page, it fetches the HTML and then everything the page references, such as CSS, JavaScript, fonts, and images. A scraper usually grabs only the HTML.</p><p>Pull everything requested by a suspect IP:</p><pre><code>grep &quot;^47.128.44.19&quot; access.log | awk &apos;{print $7}&apos; | sort | uniq -c | sort -rn | head -10
</code></pre><p>For the scraper, it&apos;s all pages, no assets:</p><pre><code>   2104 /products/12841
   2103 /products/12842
   2101 /products/12843
   2099 /products/12844
</code></pre><p>Now run the same command against a real visitor, and the shape is completely different:</p><pre><code>     14 /products/12841
      9 /css/main.a3f9.css
      9 /js/app.8c21.js
      7 /fonts/inter.woff2
     22 /images/product-12841-thumb.webp
      6 /api/cart/count
</code></pre><p>The real browser pulls the page plus its stylesheet, scripts, fonts, images, and a cart API call, so practically everything needed to actually render and use the page. The scraper pulls pages and nothing else. That ratio, HTML-only versus HTML-plus-assets, is hard to fake convincingly because faking it means doing real rendering work, which defeats the point of scraping cheaply.</p><h3 id="no-javascript-execution">No JavaScript execution</h3><p>This is closely related, but it&apos;s worth confirming separately. Most scrapers don&apos;t run JavaScript at all, so they never hit the endpoints your frontend fires after the page loads, such as analytics scripts, lazy-loaded API calls, and tracking pixels.</p><p>Count the client-side instrumentation hits from your suspect:</p><pre><code>grep &quot;^47.128.44.19&quot; access.log | grep -E &quot;/api/|/track|/analytics|/beacon&quot; | wc -l
</code></pre><p>For a scraper, this comes back as <code>0</code>. A real browser session fires these constantly, so anything above zero, often well into the dozens per session, is a strong signal of real browser activity.</p><h3 id="sequential-systematic-url-patterns">Sequential / systematic URL patterns</h3><p>Humans browse associatively. They follow what interests them, jump around, double back, and so on. Scrapers walk in straight lines.</p><p>Look at a suspect&apos;s requests in time order:</p><pre><code>grep &quot;^47.128.44.19&quot; access.log | awk &apos;{print $4, $7}&apos; | head -12
</code></pre><p>The scraper sweeps through IDs in perfect order, about two per second, with no pauses:</p><pre><code>[10:42:01] /products/12841
[10:42:01] /products/12842
[10:42:02] /products/12843
[10:42:02] /products/12844
[10:42:03] /products/12845
[10:42:03] /products/12846
[10:42:04] /products/12847
[10:42:04] /products/12848
[10:42:05] /products/12849
[10:42:05] /products/12850
[10:42:06] /products/12851
[10:42:06] /products/12852
</code></pre><p>A real visitor&apos;s path looks nothing like that:</p><pre><code>[10:41:55] /
[10:42:03] /products/winter-jacket
[10:42:31] /products/winter-jacket?color=navy
[10:42:58] /cart
[10:43:12] /products/wool-scarf
[10:44:40] /products/winter-jacket
</code></pre><p>Homepage, a product, a variant of that product, a different product, the cart, and then back to the first one, with irregular gaps of eight to forty seconds where a person was actually reading.</p><h3 id="user-agent-distribution">User-agent distribution</h3><p>Now look at what everything is claiming to be:</p><pre><code>awk -F&apos;&quot;&apos; &apos;{print $6}&apos; access.log | sort | uniq -c | sort -rn | head -12
</code></pre><pre><code> 204417 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
 184992 python-requests/2.31.0
  61003 Mozilla/5.0 (compatible; GPTBot/1.2; +https://openai.com/gptbot)
  44781 Mozilla/5.0 (compatible; ClaudeBot/1.0; +mailto:claudebot@anthropic.com)
  29550 Mozilla/5.0 (compatible; AhrefsBot/7.0; +http://ahrefs.com/robot/)
  18204 Go-http-client/2.0
  12876 Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)
   9043 Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)
   3001 Scrapy/2.11 (+https://scrapy.org)
   2422 curl/8.4.0
    880 (empty)
</code></pre><p>Reading down the list:</p><ul><li>The 204k &quot;Chrome&quot; entries look human, but this line almost certainly hides spoofed bots because impersonating Chrome is the easiest disguise there is.</li><li>184k <code>python-requests</code><strong>.</strong> That&apos;s a scripting library, and having a Python HTTP client as the second-busiest &quot;user&quot; is a lot.</li><li>GPTBot, ClaudeBot, and AhrefsBot: bots that honestly declare themselves. You can decide what to do with each by name.</li><li><code>Go-http-client</code>, <code>Scrapy</code>, <code>curl</code>, and the blank entry are almost all automation. A lot of curl requests could be individual people looking to pull something from the terminal.</li></ul><p>The honest bots label themselves, and the lazy scrapers don&apos;t bother to hide. The tricky ones might be hiding inside that first &quot;Chrome&quot; line, which is why user agent header is just one of the things to check.</p><h3 id="verifying-identity-with-a-dns-lookup">Verifying identity with a DNS lookup</h3><p>When a request claims to be a known crawler, you can verify its identity. The standard method is a reverse-then-forward DNS check.</p><p>For a request claiming to be Googlebot:</p><pre><code>$ host 66.249.66.1
1.66.249.66.in-addr.arpa domain name pointer crawl-66-249-66-1.googlebot.com.

$ host crawl-66-249-66-1.googlebot.com
crawl-66-249-66-1.googlebot.com has address 66.249.66.1
</code></pre><p>The IP reverse-resolves to a <code>googlebot.com</code> hostname, and that hostname forward-resolves back to the same IP. The round trip matches, and the domain is correct, so it&#x2019;s a verified Googlebot.</p><p>Now here&apos;s a request that also claimed to be Googlebot, from one of our scraper IPs:</p><pre><code>$ host 47.128.44.19
19.44.128.47.in-addr.arpa domain name pointer ec2-47-128-44-19.ap-southeast-1.compute.amazonaws.com.
</code></pre><p>It claimed to be Googlebot, but the IP reverse-resolves to an AWS EC2 instance in Singapore, not <code>googlebot.com</code>. Google does not crawl from EC2 instances. The user-agent was identical to the real Googlebot&apos;s, but it&#x2019;s not actually Google&#x2019;s.</p><h3 id="published-ip-ranges">Published IP ranges</h3><p>Most major crawlers publish the IP ranges they operate from, which gives you a faster check than a DNS round trip at scale. OpenAI publishes theirs as a JSON file:</p><pre><code>$ curl -s &lt;https://openai.com/gptbot.json&gt; | head
{
  &quot;creationTime&quot;: &quot;2025-10-30T11:00:00.000000&quot;,
  &quot;prefixes&quot;: [
    {
      &quot;ipv4Prefix&quot;: &quot;132.196.86.0/24&quot;
    },
    {
      &quot;ipv4Prefix&quot;: &quot;172.182.202.0/25&quot;
    },
    {
      &quot;ipv4Prefix&quot;: &quot;172.182.204.0/24&quot;
    ...
  ]
}
</code></pre><p>If a request claims to be GPTBot but its source IP isn&apos;t in OpenAI&apos;s published ranges, then it isn&apos;t GPTBot. The same approach works for Anthropic, Perplexity, Microsoft, and Google. The only maintenance cost is keeping the lists current, since they change.</p><h3 id="asn-lookup">ASN lookup</h3><p>Look at where traffic originates by network, not just by individual IP:</p><pre><code>awk &apos;{print $1}&apos; access.log | sort -u | asn-lookup | sort | uniq -c | sort -rn | head
</code></pre><pre><code>  189442  AS16509  Amazon-AES
   72103  AS14061  DigitalOcean
   41996  AS24940  Hetzner
   38201  AS5089   Virgin Media
   31774  AS2856   BT
   22018  AS5607   Sky UK
</code></pre><p>The top three sources by volume are all data centers while real consumer audiences come from residential and mobile ISPs like Virgin, BT, and Sky. So if you are seeing the most requests from AWS and Hetzner, these are likely not real users. One important thing to mention is that legitimate crawlers like Googlebot also run from data centers, so this signal isn&apos;t conclusive on its own. However, you can identify legit crawlers in different ways.</p><h3 id="geographic-and-timing-anomalies">Geographic and timing anomalies</h3><p>Requests by country:</p><pre><code>  142883  SG
   38201  GB
   12009  US
    8841  DE
</code></pre><p>This one depends entirely on your situation, but the logic is simple: if you&apos;re a UK e-commerce store and your single largest source of traffic is Singapore, by a wide margin, you&apos;re almost certainly looking at bots rather than a sudden surge of overseas customers.</p><p>Timing tells the same kind of story. Here&apos;s a suspect&apos;s requests per hour across a day:</p><pre><code>00:00  &#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;  5,012
01:00  &#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;  4,998
02:00  &#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;  5,031
03:00  &#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;  4,987
...
13:00  &#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;  5,004
...
23:00  &#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;  5,019
</code></pre><p>An almost flat line with roughly 5,000 requests an hour at 3 a.m. and 1 p.m. alike. Human behavior has some daily rhythm that makes traffic quieter overnight, build through the morning, and reach its peak in the afternoon and evening. A perfectly flat 24-hour line is a machine crawling at a constant rate. Real human traffic would look more like this:</p><pre><code>00:00  &#x2588;&#x2588;&#x2588;&#x2588;  890
03:00  &#x2588;&#x2588;  410
08:00  &#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;  3,100
13:00  &#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;  5,200
20:00  &#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;  4,600
23:00  &#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;  2,000
</code></pre><h2 id="the-issue-with-this-approach">The issue with this approach</h2><p>Every check above happened after the fact, on traffic you&apos;ve already paid for. The scraper in these examples may have run 200,000+ requests before you grepped your way to it. IPs rotate, published ranges change, new crawlers appear, and the ones trying to hide adapt to whatever you&apos;re filtering on. So it&#x2019;s a lot of work, and most of it is reactive.</p><p>So the real goal here is to understand your traffic well enough to make deliberate calls: which bots you want, which you don&apos;t, and where to draw the line. Once you can see clearly what&apos;s hitting your site, you&apos;re in a position to do something about it.</p>]]></content:encoded></item><item><title><![CDATA[Build and deploy scripts at the edge with the bunny.net CLI]]></title><description><![CDATA[You can now write JS/TS scripts, develop and test them locally, deploy to our global network, manage environment variables, and step through deployment history, all from your terminal.]]></description><link>https://bunny.net/blog/build-and-deploy-scripts-at-the-edge-with-the-bunny-net-cli/</link><guid isPermaLink="false">6a291e8b160dc403fbfcf427</guid><category><![CDATA[News]]></category><dc:creator><![CDATA[Jamie Barton]]></dc:creator><pubDate>Wed, 10 Jun 2026 08:22:55 GMT</pubDate><media:content url="https://bunny.net/blog/content/images/2026/06/deploy-scripts-at-the-edge-with-the-bunny.net-CLI.png" medium="image"/><content:encoded><![CDATA[<img src="https://bunny.net/blog/content/images/2026/06/deploy-scripts-at-the-edge-with-the-bunny.net-CLI.png" alt="Build and deploy scripts at the edge with the bunny.net CLI"><p>A few weeks ago we <a href="https://bunny.net/blog/introducing-the-bunny-net-cli/">introduced the bunny.net CLI</a>, one command-line tool for managing your entire bunny.net stack. That first release shipped with full Database support, and we promised more would follow. </p><p>Today, <a href="https://docs.bunny.net/scripting"><strong>Edge Scripting</strong></a> lands in the CLI.</p><p>You can scaffold a new script from a template, develop and test it locally with Node or Deno, deploy a built bundle to the global network, manage environment variables, and step through deployment history from your terminal.</p><h2 id="a-two-minute-tour">A two-minute tour</h2><p>If you&apos;ve already got the CLI installed and you&apos;re logged in, three commands will get you to a deployed function:</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
bunny scripts init --name my-edge-app --type standalone --template <span style="color:#BDE052;">&quot;Return JSON&quot;</span> --deploy
<span style="color:#ECEC93;">cd</span> my-edge-app
<span style="color:#ECEC93;">npm</span> run build
bunny scripts deploy dist/index.js
</pre>
<br>
<!--kg-card-end: html-->
<p>If you haven&apos;t installed the CLI yet, do that first:</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
<span style="color:#ECEC93;">npm install</span> -g @bunny.net/cli
bunny login
</pre>
<br>
<!--kg-card-end: html-->
<h2 id="scaffold-a-project-from-a-template">Scaffold a project from a template</h2>
<!--kg-card-begin: html-->
<p>
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny scripts init</code> creates a new edge script project on your machine. Run it with no flags and the CLI walks you through it interactively, asking you to pick a template, name the project, and choose whether to install dependencies:
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny scripts init

</div>
<br>
<!--kg-card-end: html-->
<p>You can also pass everything as flags, which is handy when you&apos;re in a hurry or when an agent is driving:</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
bunny scripts init \
  --name my-edge-app \
  --type standalone \
  --template <span style="color:#BDE052;">&quot;Return JSON&quot;</span>
</pre>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  Add <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">--deploy</code> to also create the remote script on bunny.net and link the directory in the same step. Without it, <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">init</code> only scaffolds the local project, and you create the remote script later with <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny scripts create</code>.
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  The built-in templates are <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">Empty</code>, <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">Return JSON</code>, and <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">Simple Middleware</code>. Each one comes with a working project layout and sensible defaults so you can build and deploy straight away.
</p>
<!--kg-card-end: html-->
<p>You can also bring your own template from a Git repo:</p>
<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny scripts init --name my-edge-app --repo user/my-template

</div>
<br>
<!--kg-card-end: html-->
<p>Custom templates always prompt before installing dependencies, since a template&apos;s install scripts run on your machine. Review the repo before you let it install.</p><h2 id="run-it-locally">Run it locally</h2><p>Edge Scripts are JavaScript and TypeScript projects built on Web-standard APIs. You develop them locally with your own editor and the debugging tools you already use.<br><br>The CLI auto-detects your package manager from the template&apos;s lockfile (bun, npm, pnpm, or yarn) and uses the template&apos;s own dev and build scripts:</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
<span style="color:#ECEC93;">cd</span> my-edge-app
<span style="color:#ECEC93;">npm install</span>
<span style="color:#ECEC93;">npm</span> run dev
</pre>
<!--kg-card-end: html-->
<p>The CLI doesn&apos;t yet ship a custom runtime emulator. It wraps the tools you&apos;d reach for anyway, so local development stays fast and predictable.</p><h2 id="link-a-directory-to-a-script">Link a directory to a script</h2><p>Once you&apos;ve got something you want to deploy, connect the working directory to a script on bunny.net:</p>
<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny scripts <span style="color:#ECEC93;">link</span>
</div>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  Run it once, and it writes a small <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">.bunny/script.json</code> manifest into your project that records which script on bunny.net this directory belongs to. From that point on, every other <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">scripts</code> command knows what you&apos;re targeting:
</p>

<ul>
  <li><code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny scripts deploy</code> deploys this project</li>
  <li><code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny scripts env set API_KEY=...</code> writes to this script&apos;s environment</li>
  <li><code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny scripts deployments list</code> shows this script&apos;s history</li>
</ul>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  The script ID stays out of your shell history, and you don&apos;t pass it on every command. Run <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny scripts link</code> with no flags to choose from a list, or <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny scripts link --id 12345</code> to link directly.
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  The manifest holds per-developer state, so it shouldn&apos;t be committed. <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny scripts init</code> adds <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">.bunny/</code> to your <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">.gitignore</code> automatically. If you ran <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny scripts link</code> or <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny scripts create</code> in an existing repo, add <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">.bunny/</code> to <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">.gitignore</code> yourself, then anyone who clones the repo links their own script.
</p>
<!--kg-card-end: html-->
<p>For those deploying scripts in CI, you can skip the project linking by passing the Edge Script ID when you deploy:</p>
<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny scripts deploy dist/index.js <span style="color:#D1949E;">12345</span>
</div>
<!--kg-card-end: html-->
<h2 id="build-and-deploy">Build and deploy</h2>
<!--kg-card-begin: html-->
<p>
  Edge Scripts deploy as a single built bundle. The CLI doesn&apos;t build for you, so run the template&apos;s own build step first, then point <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">deploy</code> at the output:
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
<span style="color:#ECEC93;">npm</span> run build
bunny scripts deploy dist/index.js
</pre>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">deploy</code> uploads the file as a new release and publishes it live by default. On success it prints the script&apos;s live hostname:
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
&#x2713; Published my-edge-app
  https://my-edge-app.bunny.run
</pre>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  If you want to upload a release without publishing it, stage it with <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">--skip-publish</code> and publish later.
</p>
<!--kg-card-end: html-->
<h2 id="put-it-on-your-own-domain">Put it on your own domain</h2>
<!--kg-card-begin: html-->
<p>
  Every script gets a <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny.run</code> URL, but you can also bring your own domain. We&apos;ve made it super easy with a new command:
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny scripts domains <span style="color:#ECEC93;">add</span> shop.example.com

</div>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny scripts domains add</code> sets up HTTPS on your own domain. bunny.net issues SSL certificates for free and can do so as soon as your DNS points to the network, so the command prints the exact CNAME record to create, along with the follow-up command to run. Once DNS has propagated, request your certificate:
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny scripts domains ssl shop.example.com

</div>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  HTTPS is enforced by default, so anyone arriving over <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">http://</code> is redirected.
</p>
<!--kg-card-end: html-->
<p>List a script&apos;s domains any time to check their SSL status:</p>
<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny scripts domains list

</div>
<!--kg-card-end: html-->
<h2 id="manage-environment-variables-and-secrets">Manage environment variables and secrets</h2><p>Most edge functions need configuration values like API tokens, feature flags, or region hints. The CLI handles two kinds. Plain <strong>variables</strong> are stored on bunny.net and can be read back. <strong>Secrets</strong> are encrypted, and their values can never be read back once set:</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
bunny scripts <span style="color:#ECEC93;">env set</span> DATABASE_URL <span style="color:#BDE052;">&quot;libsql://...&quot;</span>
bunny scripts <span style="color:#ECEC93;">env set</span> API_KEY <span style="color:#BDE052;">&quot;sk_...&quot;</span> --secret
bunny scripts <span style="color:#ECEC93;">env</span> list
bunny scripts <span style="color:#ECEC93;">env</span> remove API_KEY
</pre>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  Names are uppercased automatically, and a name can be a variable or a secret but not both. <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">env list</code> shows secret values as blank, since the API never returns them.
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  To pull plain variables into a local file for development, use <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">env pull</code>. It writes <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">NAME=VALUE</code> lines to <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">.bunny/.env</code>. Secrets are never included, so keep their source values somewhere safe:
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny scripts <span style="color:#ECEC93;">env</span> pull

</div>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  Listing supports <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">--output json</code>, useful for diffing or syncing with another tool:
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny scripts <span style="color:#ECEC93;">env</span> list --output json | jq <span style="color:#BDE052;">&apos;.[].name&apos;</span>
</div>
<!--kg-card-end: html-->
<h2 id="deployment-history">Deployment history</h2><p>Every release is recorded, and the CLI gives you a view into that history:</p>
<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny scripts deployments list

</div>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
<span style="color:#D1949E;">ID</span>       Status      Author   Released   Published
<span style="color:#D1949E;">14021</span>    &#x25CF; Live      jamie    2m ago     yes
<span style="color:#D1949E;">14008</span>    &#x25CB; Archived  jamie    1h ago     yes
<span style="color:#D1949E;">13990</span>    &#x25CB; Archived  jamie    1d ago     yes
</pre>
<!--kg-card-end: html-->
<p>The live release is marked with a filled dot, and archived releases are marked with an open one. If a release is live and the script has a linked pull zone, the hostname is printed at the end.<br><br>Need to roll back? You don&apos;t have to rebuild anything. The bundle for every past release is already on bunny.net, so grab the ID of a known-good one from that list and republish it:</p>
<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny scripts deployments publish <span style="color:#D1949E;">13990</span>
</div>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  This flips which release is live without re-uploading anything, so a bad deploy can be undone in seconds. <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">deploy</code> ships new code. <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">publish</code> re-promotes a release that has already shipped.
</p>
<!--kg-card-end: html-->
<h2 id="see-how-its-doing">See how it&apos;s doing</h2>
<!--kg-card-begin: html-->
<p>
  Once a script is serving traffic, you&apos;ll want to know how much traffic it&apos;s handling and what it&apos;s costing. <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny scripts stats</code> pulls request, CPU, and cost totals for the script and draws a per-day bar chart of requests served right in your terminal:
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
bunny scripts stats

Key                   Value
Script                bunny-cards
Period                last <span style="color:#D1949E;">30</span> days
Total Requests        <span style="color:#D1949E;">1,455</span>
Total CPU             <span style="color:#D1949E;">43,336</span>ms
Avg CPU / Execution   <span style="color:#D1949E;">29.78</span>ms
Total Cost            <span style="color:#ECEC93;">$0.00</span>

Requests served
May <span style="color:#D1949E;">18</span>, <span style="color:#D1949E;">2026</span>  &#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;    <span style="color:#D1949E;">0</span>
May <span style="color:#D1949E;">19</span>, <span style="color:#D1949E;">2026</span>  &#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;  <span style="color:#D1949E;">504</span>
May <span style="color:#D1949E;">20</span>, <span style="color:#D1949E;">2026</span>  &#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;  <span style="color:#D1949E;">383</span>
May <span style="color:#D1949E;">21</span>, <span style="color:#D1949E;">2026</span>  &#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;    <span style="color:#D1949E;">0</span>
May <span style="color:#D1949E;">22</span>, <span style="color:#D1949E;">2026</span>  &#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;    <span style="color:#D1949E;">0</span>
May <span style="color:#D1949E;">23</span>, <span style="color:#D1949E;">2026</span>  &#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;    <span style="color:#D1949E;">0</span>
May <span style="color:#D1949E;">24</span>, <span style="color:#D1949E;">2026</span>  &#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;    <span style="color:#D1949E;">0</span>
May <span style="color:#D1949E;">25</span>, <span style="color:#D1949E;">2026</span>  &#x2588;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;    <span style="color:#D1949E;">4</span>
May <span style="color:#D1949E;">26</span>, <span style="color:#D1949E;">2026</span>  &#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;    <span style="color:#D1949E;">0</span>
May <span style="color:#D1949E;">27</span>, <span style="color:#D1949E;">2026</span>  &#x2588;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;    <span style="color:#D1949E;">5</span>
May <span style="color:#D1949E;">28</span>, <span style="color:#D1949E;">2026</span>  &#x2588;&#x2588;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;   <span style="color:#D1949E;">37</span>
May <span style="color:#D1949E;">29</span>, <span style="color:#D1949E;">2026</span>  &#x2588;&#x2588;&#x2588;&#x2588;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;   <span style="color:#D1949E;">90</span>
May <span style="color:#D1949E;">30</span>, <span style="color:#D1949E;">2026</span>  &#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;    <span style="color:#D1949E;">0</span>
May <span style="color:#D1949E;">31</span>, <span style="color:#D1949E;">2026</span>  &#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;    <span style="color:#D1949E;">0</span>
Jun <span style="color:#D1949E;">1</span>, <span style="color:#D1949E;">2026</span>   &#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;    <span style="color:#D1949E;">0</span>
Jun <span style="color:#D1949E;">2</span>, <span style="color:#D1949E;">2026</span>   &#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;    <span style="color:#D1949E;">0</span>
Jun <span style="color:#D1949E;">3</span>, <span style="color:#D1949E;">2026</span>   &#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;    <span style="color:#D1949E;">0</span>
Jun <span style="color:#D1949E;">4</span>, <span style="color:#D1949E;">2026</span>   &#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;  <span style="color:#D1949E;">223</span>
Jun <span style="color:#D1949E;">5</span>, <span style="color:#D1949E;">2026</span>   &#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;  <span style="color:#D1949E;">191</span>
Jun <span style="color:#D1949E;">6</span>, <span style="color:#D1949E;">2026</span>   &#x2588;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;   <span style="color:#D1949E;">11</span>
Jun <span style="color:#D1949E;">7</span>, <span style="color:#D1949E;">2026</span>   &#x2588;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;    <span style="color:#D1949E;">5</span>
Jun <span style="color:#D1949E;">8</span>, <span style="color:#D1949E;">2026</span>   &#x2588;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;    <span style="color:#D1949E;">2</span>
</pre>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  By default, it covers the last 30 days. Narrow the window with <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">--from</code> and <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">--to</code>, or switch to an hourly breakdown when you&apos;re chasing a spike:
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
bunny scripts stats --from 2026-05-01 --to 2026-05-31
bunny scripts stats --hourly
</pre>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  Like everything else, it uses your linked script when you don&apos;t pass an ID, and <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">--output json</code> gives you the data to pipe into whatever you use to track usage.
</p>
<!--kg-card-end: html-->
<h2 id="works-with-your-agent-and-ci">Works with your agent and CI</h2>
<!--kg-card-begin: html-->
<p>
  Prompts and spinners are TTY-aware, so they disappear in CI. Commands support <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">--output json</code>, and destructive commands like <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">delete</code> and <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">env remove</code> take <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">--force</code> so they don&apos;t block on a confirmation prompt in a pipeline.
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  In CI you can skip the global install and run the CLI through <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">npx</code>:
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
# .github/workflows/deploy.yml
- <span style="color:#ECEC93;">run:</span> npm ci
- <span style="color:#ECEC93;">run:</span> npm run build
- <span style="color:#ECEC93;">run:</span> npx @bunny.net/cli scripts deploy dist/index.js ${{ secrets.SCRIPT_ID }}
  <span style="color:#ECEC93;">env:</span>
    <span style="color:#ECEC93;">BUNNYNET_API_KEY:</span> ${{ secrets.BUNNYNET_API_KEY }}
</pre>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  When you scaffold with <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny scripts init --deploy</code>, the command prints the <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">SCRIPT_ID</code> to add as a repo secret, so CI deploys to the right script without committing the link manifest.
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  AI coding assistants use the CLI directly, either from a global install or through <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">npx @bunny.net/cli</code>. We ship Edge Scripting skills alongside it, so Claude Code, Cursor, Windsurf, or any agent that runs shell commands can scaffold a project, link it, set environment variables, and deploy.
</p>
<!--kg-card-end: html-->
<h2 id="whats-next">What&apos;s next</h2><p>Edge Scripting joins Bunny Database in the CLI today. Storage, Magic Containers, and the rest of the bunny.net stack are coming next.</p><p>Get started and create your first Edge Script with the bunny.net CLI:</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">
<span style="color:#ECEC93;">npm install</span> -g @bunny.net/cli
bunny login
bunny scripts init
</pre>
<!--kg-card-end: html-->
<p>The CLI is open source and developed in public. If something&apos;s broken, <a href="https://github.com/BunnyWay/cli/issues" rel="noopener noreferrer">open an issue</a>. If you want to share what you&apos;ve built, find us in <a href="https://discord.com/invite/bunnynet" rel="noopener noreferrer">Discord</a>.</p>]]></content:encoded></item><item><title><![CDATA[Built on bunny.net - May 2026]]></title><description><![CDATA[Here's the first edition of a monthly roundup highlighting the projects, packages, and writeups we've spotted across the community]]></description><link>https://bunny.net/blog/built-on-bunny-net-may-2026/</link><guid isPermaLink="false">6a197100160dc403fbfcf3ac</guid><category><![CDATA[Tips and Tricks]]></category><dc:creator><![CDATA[Dino Kukic]]></dc:creator><pubDate>Fri, 29 May 2026 11:10:47 GMT</pubDate><media:content url="https://bunny.net/blog/content/images/2026/05/Built-on-bunny-May-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://bunny.net/blog/content/images/2026/05/Built-on-bunny-May-1.png" alt="Built on bunny.net - May 2026"><p>We pay attention to what people build on bunny.net. Some of it genuinely impresses us, and all of it deserves a shoutout. So, here&apos;s the first edition of a monthly roundup highlighting the projects, packages, and writeups we&apos;ve spotted across the community. If you&apos;re planning your own setup, there&#x2019;s plenty here worth exploring. It&#x2019;s also our way of saying thanks to the people building with bunny.net.</p><p>If you&apos;ve shipped, written, or open-sourced something, tag us or send us a message. We&apos;d love to take a look for next month&apos;s roundup.</p><pre><code class="language-python">The community packages below are built by people working 
with bunny.net, but they aren&apos;t officially maintained by us.</code></pre><h2 id="shipped-on-bunnynet">Shipped on bunny.net</h2><h3 id="kakuso"><a href="https://kaku.so/">kaku.so</a></h3><p>A statically rendered Japanese dictionary with 290k entries translated into 11 languages, plus built-in spaced repetition for practice. The scale of the project is genuinely impressive: more than 10 million files, 400 GB+ of data, and a 12-hour build process. The entire stack moved from Azure PostgreSQL to bunny.net after <a href="https://bunny.net/database/">Bunny Database</a> launched.</p><h3 id="yaanch"><a href="https://yaan.ch/">yaan.ch</a></h3><p>A drop-in replacement for hCaptcha, reCAPTCHA and Friendly Captcha. Invisible, GDPR-compliant, not proof-of-work. The marketing pages and backend run on <a href="https://bunny.net/storage/">Bunny Storage</a> and <a href="https://bunny.net/edge-scripting/">Edge Scripting</a>. The Rust anti-bot engine runs on <a href="https://bunny.net/magic-containers/">Magic Containers</a>, and the database will move to Bunny DB once it exits public preview.</p><h3 id="convex-self-hosted-on-magic-containers">Convex self-hosted on Magic Containers</h3><p><a href="https://www.linkedin.com/in/lipaonline/">Patrick Faust</a> got the Convex self-hosted runtime running on bunny.net Magic Containers and documented the process in two LinkedIn posts: <a href="https://www.linkedin.com/posts/lipaonline_big-milestone-unlocked-convex-just-approved-activity-7443538966836035584-C0h8/">one when it was approved</a>, and <a href="https://www.linkedin.com/posts/lipaonline_convex-self-hosted-is-live-on-bunny-magic-activity-7449773848343846913-Wn6H/">another when it went live</a>.</p><h3 id="nitrofilm"><a href="https://nitro.film/">Nitro.film</a></h3><p>Apple TV app built by a solo founder on <a href="https://bunny.net/stream/">Bunny Stream</a>, with much of the development process <a href="https://www.linkedin.com/posts/harrylang_buildinpublic-solofounder-claudecode-activity-7450683849157795841-ozKS">shared publicly on LinkedIn</a> as part of a &#x2018;build in public journey&#x2019;.</p><h3 id="kraken">Kraken</h3><p><a href="https://www.linkedin.com/in/robin-mb/" rel="noreferrer">Robin Dost</a> has been building <a href="https://kraken.malwarebox.eu/">Kraken</a>, a CTI platform that tracks adversary infrastructure, including domains, IPs, and dead drops, over time so analysts don&#x2019;t lose visibility after initial discovery. It&#x2019;s currently running live in evaluation and hosted in Europe on bunny.net.</p><h3 id="the-imago-platform">The Imago Platform</h3><p><a href="https://www.linkedin.com/in/helander/">Magnus Helander</a> is building the <a href="https://www.imagoplatform.ai/">Imago Platform</a> on <a href="https://bunny.net/cdn/">Bunny CDN</a>, sitting at <a href="https://www.linkedin.com/posts/helander_buildingthe-imago-platformwe-could-have-activity-7450298367915618304-hlKR/">99.995% uptime since launch</a>. He also built and shipped a Claude skill for writing Edge Scripts on bunny.net.</p><h3 id="moving-to-bunnynet">Moving to bunny.net</h3><p>Three writeups from the past month, all worth reading if you&apos;re thinking about your own setup.</p><ul><li><strong>Johanna Larsson</strong> - <a href="https://jola.dev/posts/dropping-cloudflare" rel="noreferrer">moved a Phoenix/Elixir blog to bunny.net</a> with a full walkthrough and code examples.</li><li><strong>Alec Armbruster</strong> - built a <a href="https://alec.is/posts/cloudflare-tunnels-on-bunny-net/">tunnels-style setup running on bunny.net</a>.</li><li><strong>David Drugeon-Hamon</strong> - documented a <a href="https://david.drugeon-hamon.bzh/blog/2026/04/migration-github-codeberg/">GitHub-to-Codeberg switch with bunny.net in the stack</a>.</li></ul><h2 id="shipped-for-bunnynet">Shipped for bunny.net</h2><h3 id="cdn"><strong>CDN</strong></h3><p>A community-built API client I recently came across:</p><ul><li><a href="https://pypi.org/project/bunny-cdn-sdk/"><code>bunny-cdn-sdk</code></a> - typed Python SDK and CLI covering Bunny CDN and Bunny Storage.</li></ul><h3 id="stream-and-video">Stream and video</h3><ul><li><a href="https://www.npmjs.com/package/bunnycdn-stream"><code>bunnycdn-stream</code></a> - TypeScript library for the Bunny Stream API.</li><li><a href="https://www.npmjs.com/package/playstack"><code>playstack</code></a> - React video player that supports Bunny Stream.</li><li><a href="https://www.npmjs.com/package/@cliff-studio/sanity-plugin-bunny-input"><code>sanity-plugin-bunny-input</code></a> - input component for Sanity Studio.</li></ul><h3 id="storage">Storage</h3><ul><li><a href="https://www.npmjs.com/package/@seshuk/payload-storage-bunny"><code>payload-storage-bunny</code></a> - Payload CMS adapter for Bunny Storage and Bunny Stream with auto-purging and resumable uploads via tus.</li><li><a href="https://www.npmjs.com/package/bunny-transfer"><code>bunny-transfer</code></a> - rsync-style CLI for moving files in and out of storage zones.</li><li><a href="https://www.npmjs.com/package/upload-to-bunny"><code>upload-to-bunny</code></a> - directory uploader aimed at CI pipelines.</li></ul><h3 id="shield-and-security">Shield and security</h3><ul><li><a href="https://pypi.org/project/octorules-bunny/"><code>octorules-bunny</code></a> - <a href="https://bunny.net/shield/">Shield</a> WAF rules as code, built and used in production by Doctena, an EU healthcare scheduling company.</li></ul><h3 id="working-with-ai-agents">Working with AI agents</h3><ul><li><a href="https://www.npmjs.com/package/bunnycdn-mcp"><code>bunnycdn-mcp</code></a> - MCP server for bunny.net.</li><li><code>bunny-edge</code> Claude plugin - Magnus Helander&#x2019;s Claude skill for writing Edge Scripts on bunny.net. Install with <code>/plugin install bunny-edge@mheland</code>.</li></ul><p>That&#x2019;s it for May. We&#x2019;ll be back next month with more projects, tools, and experiments from across the bunny.net community. If you&#x2019;d like to be featured, tag us on social media or share your project in our Discord community. Join our <a href="https://discord.com/invite/bunnynet?ref=bunny.net">Discord here</a>.</p>]]></content:encoded></item><item><title><![CDATA[How we sped up deploys, updates, and undeploys in Magic Containers]]></title><description><![CDATA[We reduced deploy and update latency from tens of seconds to under five by combining event-driven acceleration with control-loop reliability.]]></description><link>https://bunny.net/blog/how-we-sped-up-deploys-updates-and-undeploys-in-magic-containers/</link><guid isPermaLink="false">6a182c5d160dc403fbfcf38c</guid><category><![CDATA[Compute]]></category><dc:creator><![CDATA[Anton Zvonko Gazvoda]]></dc:creator><pubDate>Thu, 28 May 2026 12:02:48 GMT</pubDate><media:content url="https://bunny.net/blog/content/images/2026/05/Faster-Deploys--Undeploys--and-Updates-for-Magic-Containers.png" medium="image"/><content:encoded><![CDATA[<img src="https://bunny.net/blog/content/images/2026/05/Faster-Deploys--Undeploys--and-Updates-for-Magic-Containers.png" alt="How we sped up deploys, updates, and undeploys in Magic Containers"><p>Magic Containers became programmable with the introduction of the Public API. The next step was making it react in real time.</p><p>In practice, operations like deploys and updates could take tens of seconds to complete. Not because anything was failing, but because of how control loops work.</p><p>Each part of the system waits for the next reconciliation cycle, and those delays stack up across components.</p><p>As workloads become more short-lived and automated, lifecycle speed becomes an increasingly important part of the developer experience. Spinning up a container, scaling it, or tearing it down should feel immediate.</p><p>This is the problem we set out to solve.</p><h2 id="the-problem-control-loops-introduce-latency">The problem: control loops introduce latency</h2><p>Magic Containers is built around a&#xA0;<strong>control loop architecture</strong>.</p><p>Each component continuously reconciles&#xA0;<em>desired state &#x2192; actual state</em>:</p><ul><li><strong>Application Provisioner</strong>&#xA0;&#x2192; selects regions for deployment</li><li><strong>Controller Manager</strong>&#xA0;&#x2192; ensures the desired number of replicas</li><li><strong>Scheduler</strong>&#xA0;&#x2192; assigns pods to nodes</li><li><strong>Local Container Manager (LCM)</strong>&#xA0;&#x2192; ensures containers run on the assigned node</li></ul><p>Each of these components runs independently in a loop:</p><pre><code class="language-python">while&#xA0;True:
	observe_state()
  diff&#xA0;=&#xA0;desired_state&#xA0;-&#xA0;actual_state
  if&#xA0;diff:
	  reconcile(diff)
  sleep(interval)
</code></pre><p>This model is extremely reliable and forms the backbone of many distributed systems, including Kubernetes.</p><p>But it comes with a tradeoff.</p><p><strong>Latency compounds across loops.</strong></p><p>Each loop runs on an interval of&#xA0;<strong>~5 to 10 seconds</strong>.</p><p>That means a single operation doesn&#x2019;t execute immediately. Instead, it waits for the next loop iteration.</p><p>Now chain multiple components together:</p><pre><code class="language-python">User action
	&#x2192; waits for Provisioner loop (up to 10s)
	&#x2192; waits for Controller loop (up to 10s)
	&#x2192; waits for Scheduler loop (up to 10s)
  &#x2192; waits for LCM loop (up to 10s)
</code></pre><p>In the worst case, this stacks up to&#xA0;<strong>tens of seconds before a container is fully running</strong>.</p><p>Even under typical conditions, deploys and updates felt noticeably delayed.</p><p>Nothing was technically incorrect. The system always converged to the correct state, but the&#xA0;<em>experience</em>&#xA0;lagged behind what modern workflows expect.</p><h3 id="what-we-considered-and-rejected">What we considered (and rejected)</h3><p>We explored several approaches before settling on the final solution.</p><p><strong>1. Decreasing loop intervals</strong></p><p>Reducing intervals from ~10 seconds to sub-second.</p><p><strong>Why we rejected it:</strong></p><ul><li>Significant increase in CPU usage across all control plane components</li><li>Higher pressure on state storage and coordination systems</li><li>Still fundamentally polling-based, meaning latency never reaches zero</li></ul><p><strong>2. Removing loops entirely</strong></p><p>Moving to a purely event-driven system.</p><p><strong>Why we rejected it:</strong></p><ul><li>Loops are critical as a&#xA0;<strong>safety mechanism</strong></li><li>They continuously verify and correct drift between desired and actual state</li><li>Without them, missed events could lead to permanent inconsistencies</li></ul><p><strong>3. Hybrid model (chosen)</strong></p><p>Keep loops for correctness and safety, but introduce&#xA0;<strong>events for immediacy</strong>.</p><h2 id="the-solution-event-driven-acceleration">The solution: event-driven acceleration</h2><p>We introduced a&#xA0;<strong>message broker with event queues</strong>&#xA0;between components.</p><p>The key idea: <em>Loops ensure consistency. Events provide speed.</em></p><p>Instead of waiting for the next loop iteration, components now react immediately when something changes.</p><p><strong>Before: loop-driven propagation</strong></p><pre><code class="language-python">[User Action]
&#x2193;
(wait for Provisioner loop)
&#x2193;
(wait for Controller loop)
&#x2193;
(wait for Scheduler loop)
&#x2193;
(wait for LCM loop)
&#x2193;
[Container Running]
</code></pre><p><strong>After: event-accelerated flow</strong></p><pre><code class="language-python">[User Action]
&#x2193;
[Event: Application Created] &#x2192; Queue
&#x2193;
[Provisioner triggered immediately]
&#x2193;
[Event: Provisioning Complete]
&#x2193;
[Controller Manager triggered]
&#x2193;
[Event: Pods Created]
&#x2193;
[Scheduler triggered]
&#x2193;
[Event: Pod Scheduled]
&#x2193;
[LCM triggered]
&#x2193;
[Container Running]
</code></pre><h3 id="how-it-works">How It Works</h3><p>Each component now listens for specific events and reacts instantly.</p><p><strong>Example event</strong></p><pre><code class="language-json">{
    &quot;type&quot;: &quot;application.created&quot;,
    &quot;app_id&quot;: &quot;app_123&quot;,
    &quot;regions&quot;: [&quot;eu-central&quot;, &quot;us-east&quot;],
    &quot;timestamp&quot;: 1713949200
}
</code></pre><p><strong>Provisioner</strong></p><pre><code class="language-go">func handleApplicationCreated(event Event) {
    regions := selectRegions(event)
    publish(&quot;provisioning.completed&quot;, regions)
}
</code></pre><p><strong>Controller Manager</strong></p><pre><code class="language-go">func handleProvisioningComplete(event Event) {
    createReplicas(event.app_id, desiredReplicas)
    publish(&quot;replicas.created&quot;, event.app_id)
}
</code></pre><p><strong>Scheduler</strong></p><pre><code class="language-go">func handleReplicasCreated(event Event) {
    node := selectNode(event)
    publish(&quot;pod.scheduled&quot;, node)
}
</code></pre><p><strong>Local Container Manager (LCM)</strong></p><pre><code class="language-go">func&#xA0;handlePodScheduled(event&#xA0;Event) {  
		startContainer(event.node,&#xA0;event.pod)
}
</code></pre><h3 id="important-loops-still-exist">Important: loops still exist</h3><p>The control loops were&#xA0;<strong>not removed</strong>.</p><p>They still run continuously to:</p><ul><li>Detect drift (e.g., crashed containers or missing replicas)</li><li>Reconcile inconsistencies</li><li>Act as a fallback if events are delayed or lost</li></ul><pre><code class="language-go">// simplified reconciliation loop
for {
    diff := computeDiff(desiredState, actualState)
    if diff != nil {
        reconcile(diff)
    }
    sleep(5 * time.Second)
}
</code></pre><p>This hybrid design gives us:</p><ul><li><strong>Fast reaction time (events)</strong></li><li><strong>Strong consistency guarantees (loops)</strong></li></ul><p>These events don&#x2019;t just drive internal components, they also power real-time updates across the platform, including the Dashboard.</p><h2 id="from-control-plane-to-user-experience">From control plane to user experience</h2><p>Reducing backend latency is only part of the story.</p><p>Before this change, even when operations completed, the <strong>Dashboard still relied on polling</strong>&#xA0;to fetch updates. This introduced an additional delay between something happening in the system and the user actually seeing it.</p><p>In practice, this meant:</p><ul><li>Deploy finishes &#x2192; UI updates a few seconds later</li><li>Scaling event happens &#x2192; user sees it after the next refresh cycle</li></ul><p>To solve this, we extended the same event-driven model all the way to the frontend.</p><h3 id="real-time-updates-via-websockets">Real-time updates via WebSockets</h3><p>We introduced&#xA0;<strong>WebSocket-based event streaming</strong>&#xA0;between the control plane and the Dashboard.</p><p>Instead of polling for state changes, the UI now subscribes to live updates:</p><pre><code class="language-python">Client &#x2192; opens WebSocket connection
&#x2192; subscribes to application events
</code></pre><p>Whenever something changes:</p><pre><code class="language-python">[Control Plane Event]
&#x2193;
[Message Broker]
&#x2193;
[WebSocket Gateway]
&#x2193;
[Dashboard UI updates instantly]
</code></pre><h3 id="what-this-changes">What this changes</h3><p>This removes the final layer of perceived latency.</p><h4 id="before">Before</h4><ul><li>Backend finishes &#x2192; UI polls &#x2192; user sees update later</li></ul><h4 id="after">After</h4><ul><li>Backend finishes &#x2192; event emitted &#x2192; UI updates instantly</li></ul><h4 id="result">Result</h4><ul><li>Deploy progress updates feel&#xA0;<strong>real time</strong></li><li>Scaling actions are visible&#xA0;<strong>immediately</strong></li><li>State transitions (creating &#x2192; running &#x2192; scaling) feel&#xA0;<strong>continuous</strong></li></ul><h3 id="why-this-matters">Why this matters</h3><p>Without this step, the platform would be technically fast but still&#xA0;<em>feel slow</em>.</p><p>By pushing events all the way to the UI, we aligned:</p><ul><li><strong>System speed</strong></li><li><strong>User perception</strong></li></ul><h2 id="the-impact">The impact</h2><p>By eliminating waiting between steps, we removed the largest source of latency.</p><p><strong>Before vs. after</strong></p>
<!--kg-card-begin: html-->
<table style="border:1px solid rgba(24, 61, 109, 0.18); border-collapse:collapse; width:100%;"> <thead> <tr style="font-weight:700; text-align:left; background:#223c6a; color:white;"> <th style="padding:12px 14px; background:rgba(255,255,255,0.03); border-bottom:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); color:white;"> Operation </th> <th style="padding:12px 14px; border-bottom:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); color:white;"> Before (loop-driven) </th> <th style="padding:12px 14px; border-bottom:1px solid rgba(24, 61, 109, 0.18); color:white;"> After (event-driven) </th> </tr> </thead> <tbody> <tr> <td style="padding:12px 14px; border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">Deploy</td> <td style="padding:12px 14px; border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">10&#x2013;40s</td> <td style="padding:12px 14px; vertical-align:middle; color:#183d6d;"><strong>&lt; 5s</strong></td> </tr> <tr> <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">Update</td> <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">10&#x2013;40s</td> <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;"><strong>&lt; 4s</strong></td> </tr> <tr> <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">Undeploy</td> <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">~60s+</td> <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;"><strong>~60s (grace period)</strong></td> </tr> </tbody> </table> <br>
<!--kg-card-end: html-->
<h3 id="what-changed-technically">What changed technically</h3><ul><li>Removed dependency on loop timing for forward progress</li><li>Reduced end-to-end latency by&#xA0;<strong>an order of magnitude</strong></li><li>Maintained correctness via continuous reconciliation</li></ul><h3 id="why-this-matters-1">Why this matters</h3><p>This fundamentally changes how Magic Containers behaves.</p><ul><li><strong>CI/CD pipelines speed up.</strong> Infrastructure is no longer the slowest step</li><li><strong>Ephemeral workloads become practical.</strong> Create &#x2192; run &#x2192; destroy flows now complete in seconds</li><li><strong>Event-driven systems feel natural.</strong> Infrastructure now reacts at the same speed as application logic</li></ul><h3 id="tradeoffs-and-challenges">Tradeoffs and challenges</h3><p><strong>1. Event ordering</strong></p><p>Ensuring correct sequencing across distributed components.</p><p><strong>Solution:</strong></p><ul><li>Idempotent handlers</li></ul><p><strong>2. Reliability</strong></p><p>Events can fail or be delayed.</p><p><strong>Solution:</strong></p><ul><li>Retry mechanisms</li><li>Dead-letter queues</li><li>Control loops as fallback</li></ul><p><strong>3. Observability</strong></p><p>Async systems are harder to debug.</p><p><strong>Solution:</strong></p><ul><li>Correlation IDs</li><li>Event tracing across components</li></ul><h2 id="what%E2%80%99s-next">What&#x2019;s next</h2><p>We&#x2019;re already exploring:</p><ul><li>Optimizing image download times to reduce startup time</li><li>Automated build and updates directly from your GitHub repository</li><li>Access to recent log history alongside live logs for easier troubleshooting</li></ul><h2 id="final-thoughts">Final thoughts</h2><p>Magic Containers started as a loop-driven control plane designed for correctness.</p><p>With the introduction of event-driven acceleration, it now reacts immediately to changes, without relying on the next reconciliation cycle.</p><p>The result is a system that converges just as reliably, but gets there much faster.</p>]]></content:encoded></item><item><title><![CDATA[STACKIT and bunny.net partner to offer fast, private, regulation-ready content delivery]]></title><description><![CDATA[bunny.net teamed up with STACKIT, the cloud provider of Schwarz Digits, to deliver a high-performance, regulation-ready, EU-sovereign content delivery network (CDN) and edge security ecosystem built specifically for European businesses.]]></description><link>https://bunny.net/blog/stackit-and-bunny-net-partner-to-offer-fast-private-regulation-ready-content-delivery/</link><guid isPermaLink="false">6a168a5e160dc403fbfcf374</guid><category><![CDATA[News]]></category><category><![CDATA[Privacy]]></category><dc:creator><![CDATA[Graeme Inglis]]></dc:creator><pubDate>Wed, 27 May 2026 06:17:07 GMT</pubDate><media:content url="https://bunny.net/blog/content/images/2026/05/Stackit-and-bunny.net-Partnership-blog.png" medium="image"/><content:encoded><![CDATA[<img src="https://bunny.net/blog/content/images/2026/05/Stackit-and-bunny.net-Partnership-blog.png" alt="STACKIT and bunny.net partner to offer fast, private, regulation-ready content delivery"><p>European infrastructure teams are facing a massive challenge.</p><p>With regulations like GDPR, NIS2, and the EU Data Act tightening across the continent, compliance is no longer a checkbox. It dictates your entire architecture. Historically, the industry narrative held that guaranteeing data sovereignty meant sacrificing raw edge performance.</p><p>We weren&#x2019;t willing to trade performance for sovereignty. Now, you don&#x2019;t have to either.</p><p>bunny.net teamed up with STACKIT, the cloud provider of Schwarz Digits, to deliver a high-performance, regulation-ready, EU-sovereign content delivery network (CDN) and edge security ecosystem built specifically for European businesses.</p><h2 id="uncompromising-performance-under-european-law">Uncompromising performance under European law</h2><p>This partnership brings together two teams obsessed with digital independence. If you aren&apos;t familiar with STACKIT, it is the cloud backbone of Schwarz Digits, the IT organization behind Schwarz Group. Operating out of its German headquarters, it has built an enterprise-grade cloud ecosystem focused entirely on data privacy and sovereign control.</p><p>By combining STACKIT&#x2019;s cloud infrastructure with bunny.net&#x2019;s global edge network, we are launching an integrated ecosystem that meets the needs of both your legal team and your DevOps leads.</p><p>When you route your traffic through STACKIT CDN, your data remains fully within EU jurisdiction. No hidden tracking cookies, no data monetization, and no third-party analytics processing your users&apos; information.</p><h2 id="engineering-edge-security-and-scale">Engineering edge security and scale</h2><p>We built this ecosystem to handle demanding, enterprise-scale workloads optimized for compliance.</p><p>Here is what this sovereign architecture delivers out of the box:</p><ul><li><strong>Global edge performance:</strong> Connect your STACKIT-hosted applications directly to a global edge network backed by 250 Tbps+ of backbone capacity, ensuring consistent sub-30 ms latency across Europe and beyond.</li><li><strong>Integrated edge protection:</strong> Mitigate risks before traffic hits your infrastructure. The platform injects robust security tools right at the edge, including a web application firewall (WAF), Distributed Denial-of-Service (DDoS) mitigation, and global rate limiting.</li><li><strong>Transparent cost infrastructure:</strong> Scale your application without worrying about complex pricing tiers or vendor lock-in. Keep track of your carrots with a predictable, transparent, pay-as-you-go cost model.</li></ul><h2 id="building-an-independent-digital-future">Building an independent digital future</h2><p>Your company&#x2019;s growth shouldn&#x2019;t be punished for protecting user privacy. By anchoring your traffic in a trust-first ecosystem like Stackit CDN, you can optimize your delivery pipeline, satisfy strict regulatory requirements, and maintain absolute control over your data.</p><p>Ready to see how the new sovereign edge can transform your stack? Hop on over to the <a href="https://stackit.com/en/products/network/stackit-cdn?_gl=1*1dqzp6s*_up*MQ">STACKIT platform</a> to learn more, or connect with our team to find out how to hop ahead securely.</p>]]></content:encoded></item><item><title><![CDATA[HopStart cohort #2: the three startups we're backing next]]></title><description><![CDATA[Picking just three was genuinely hard. There were plenty of products we'd have loved to back. Ultimately, we look for teams solving real problems for their users, especially when bunny.net is a natural fit for the infrastructure behind their product.]]></description><link>https://bunny.net/blog/hopstart-cohort-2-the-three-startups-were-backing-next/</link><guid isPermaLink="false">6a0ed12c160dc403fbfcf343</guid><category><![CDATA[News]]></category><dc:creator><![CDATA[Marek Nalikowski]]></dc:creator><pubDate>Thu, 21 May 2026 12:22:10 GMT</pubDate><media:content url="https://bunny.net/blog/content/images/2026/05/bunny.net-Hopstart-2nd-cohort-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://bunny.net/blog/content/images/2026/05/bunny.net-Hopstart-2nd-cohort-1.png" alt="HopStart cohort #2: the three startups we&apos;re backing next"><p>Back in January, we welcomed <a href="https://bunny.net/blog/announcing-hopstart-cohort-1-meet-the-3-startups-building-with-bunny-net/">the first group of founders</a> into HopStart, our program designed to take infrastructure off the plate of early-stage teams so they can pour their energy into the products they&apos;re building.</p><p>Since then, applications for the second round have rolled in, and the bar has only gone up. As a reminder, each cohort runs quarterly and supports three startups with up to <strong>$50,000 USD in credits</strong> for bunny.net, regular touchpoints with our team, and early access to features and products before they launch more broadly.</p><p>Picking just three was genuinely hard. There were plenty of products we&apos;d have loved to back. Ultimately, we look for teams solving real problems for their users, especially when bunny.net is a natural fit for the infrastructure behind their product.</p><p>Here are the three startups joining <strong>HopStart cohort #2</strong>.</p><h2 id="1st-place-sublimly">1st place: Sublimly</h2><p><strong>Founder:</strong> Patrick Faust</p><p><strong>Year founded:</strong> 2025</p><p><strong>URL:</strong> <a href="https://www.sublimly.com/">https://www.sublimly.com/</a></p><p><strong>Credits received:</strong> $50,000 for one year</p><p><strong>In the founder&apos;s words:</strong> &quot;Sublimly is a SaaS platform that empowers real estate agents, luxury brands, travel companies, and UGC creators to produce professional-quality videos and photos in minutes &#x2014; without any design or video editing skills. Users upload their raw photos, and our AI-powered pipeline handles the heavy lifting: intelligent photo enhancement, automated background removal, smart cropping, and AI-generated copy tailored to each industry context. Users then select a template adapted to their vertical, and Sublimly programmatically generates polished, branded video content ready for social media.&quot;</p><figure class="kg-card kg-image-card"><img src="https://bunny.net/blog/content/images/2026/05/Sublimly-home-page.png" class="kg-image" alt="HopStart cohort #2: the three startups we&apos;re backing next" loading="lazy" width="2000" height="1008" srcset="https://bunny.net/blog/content/images/size/w600/2026/05/Sublimly-home-page.png 600w, https://bunny.net/blog/content/images/size/w1000/2026/05/Sublimly-home-page.png 1000w, https://bunny.net/blog/content/images/size/w1600/2026/05/Sublimly-home-page.png 1600w, https://bunny.net/blog/content/images/size/w2400/2026/05/Sublimly-home-page.png 2400w" sizes="(min-width: 720px) 720px"></figure><h2 id="2nd-place-writizzy">2nd place: Writizzy</h2><p><strong>Founder:</strong> Hugo Lassi&#xE8;ge and Thomas Sanlis</p><p><strong>Year founded:</strong> 2025</p><p><strong>URL:</strong> <a href="https://writizzy.com/">https://writizzy.com/</a></p><p><strong>Credits received:</strong> $25,000 for one year</p><p><strong>In the founders&apos; words:</strong> &quot;Writizzy is a European publishing platform designed as an open alternative to Substack or Medium. The goal isn&apos;t just to provide another CMS, but to give creators back control over their digital real estate. In an era of platform lock-in and centralized moderation, we provide a space where authors truly own their content and audience.&quot;</p><figure class="kg-card kg-image-card"><img src="https://bunny.net/blog/content/images/2026/05/Writizzy-home-page-1.png" class="kg-image" alt="HopStart cohort #2: the three startups we&apos;re backing next" loading="lazy" width="2000" height="1070" srcset="https://bunny.net/blog/content/images/size/w600/2026/05/Writizzy-home-page-1.png 600w, https://bunny.net/blog/content/images/size/w1000/2026/05/Writizzy-home-page-1.png 1000w, https://bunny.net/blog/content/images/size/w1600/2026/05/Writizzy-home-page-1.png 1600w, https://bunny.net/blog/content/images/size/w2400/2026/05/Writizzy-home-page-1.png 2400w" sizes="(min-width: 720px) 720px"></figure><h2 id="3rd-place-scratchupload">3rd place: ScratchUpload</h2><p><strong>Founder:</strong> Nathaniel Sturtz</p><p><strong>Year founded:</strong> 2025</p><p><strong>URL:</strong> <a href="https://scratchupload.org/">https://scratchupload.org/</a></p><p><strong>Credits received:</strong> $10,000 for one year</p><p><strong>In the founder&apos;s words:</strong> &quot;ScratchUpload is leading the way in privacy- and accessibility-focused image hosting for an underserved community: users of SimplyPlural and Pluralkit.&quot;</p><figure class="kg-card kg-image-card"><img src="https://bunny.net/blog/content/images/2026/05/ScratchUpload-home-page.png" class="kg-image" alt="HopStart cohort #2: the three startups we&apos;re backing next" loading="lazy" width="2000" height="847" srcset="https://bunny.net/blog/content/images/size/w600/2026/05/ScratchUpload-home-page.png 600w, https://bunny.net/blog/content/images/size/w1000/2026/05/ScratchUpload-home-page.png 1000w, https://bunny.net/blog/content/images/size/w1600/2026/05/ScratchUpload-home-page.png 1600w, https://bunny.net/blog/content/images/size/w2400/2026/05/ScratchUpload-home-page.png 2400w" sizes="(min-width: 720px) 720px"></figure><h2 id="apply-for-hopstart-cohort-3">Apply for HopStart cohort #3</h2><p>The window for the next round is open now. If you&apos;re building something with real ambition behind it, and you can see bunny.net playing a role in helping you get there, we&apos;d love to hear from you. Submit your application via <a href="https://bunny.net/HopStart/#HopStart-Form">the short form</a> before <strong>May 31</strong>.</p><p>If you applied previously and didn&apos;t make it through, please don&apos;t take that as a &#x2018;no&#x2019; for good. Fit shifts from one round to the next, and what didn&apos;t land before could be exactly the right fit this time.</p><p>We&apos;ll share the cohort #3 lineup in <strong>July</strong>.</p><p>We can&apos;t wait to see what you&apos;ve been working on!</p>]]></content:encoded></item><item><title><![CDATA[How to migrate from Vimeo to Bunny Stream]]></title><description><![CDATA[Bunny Stream is a feature-rich video hosting solution that is part of the bunny.net web infrastructure platform. Instead of subscription tiers, Bunny Stream uses a usage-based pricing model, where you’re charged only for the storage you use and the traffic your videos receive.]]></description><link>https://bunny.net/blog/how-to-migrate-from-vimeo-to-bunny-stream/</link><guid isPermaLink="false">6a018323160dc403fbfcf2a8</guid><category><![CDATA[News]]></category><dc:creator><![CDATA[Marek Nalikowski]]></dc:creator><pubDate>Wed, 13 May 2026 11:48:47 GMT</pubDate><media:content url="https://bunny.net/blog/content/images/2026/05/How-to-migrate-from-vimeo-to-bunny-stream.png" medium="image"/><content:encoded><![CDATA[<img src="https://bunny.net/blog/content/images/2026/05/How-to-migrate-from-vimeo-to-bunny-stream.png" alt="How to migrate from Vimeo to Bunny Stream"><p>Whether you&apos;re a creator sharing your work through Vimeo or a developer integrating video into an app or platform, this guide walks you through how to migrate your video content to Bunny Stream.</p>
<!--kg-card-begin: html-->
<p>What we&#x2019;ll cover:</p>

<ul>
  <li>Choosing the right migration path for your situation</li>
  <li>
    How to move your video content manually with
    <a href="#manual-upload">direct upload</a>
    or
    <a href="#url-fetch">URL import</a>
  </li>
  <li>
    How to automate the process with
    <a href="#bulk-migration">CLI-based bulk migration</a>
  </li>
</ul>
<!--kg-card-end: html-->
<h2 id="why-migrate-from-vimeo-to-bunny-stream">Why migrate from Vimeo to Bunny Stream</h2><p><strong>Vimeo</strong> is an all-in-one SaaS platform for hosting, managing, and sharing video content. As of May 2026, it offers four paid tiers (Starter, Standard, Advanced, and Enterprise) billed either monthly or annually. Each tier comes with fixed storage and bandwidth limits, and more advanced features are only available in the higher subscription plans.</p><p><strong>Bunny Stream</strong> is a feature-rich video hosting solution that is part of the bunny.net web infrastructure platform. Instead of subscription tiers, Bunny Stream uses a usage-based pricing model, where <a href="https://bunny.net/pricing/stream/#calculator">you&#x2019;re charged only for the storage you use and the traffic your videos receive</a>.</p><p>With the exception of some <a href="https://bunny.net/stream/media-cage-enterprise-multi-drm-digital-rights-management/">enterprise-grade add-on features</a>, Bunny Stream offers most of its functionality to all users, regardless of how much they pay. By default, Bunny Stream is prepaid, meaning you purchase credits to use it, which ensures you&#x2019;re never hit with runaway costs (though postpaid is available on request).</p><p>Despite being part of an API-first web infrastructure platform, Bunny Stream has an intuitive user interface, that makes it accessible to non-technical users. With its attractive, usage-based pricing, it&#x2019;s very cost-effective for small creators and growing businesses alike.</p><h2 id="planning-your-migration-from-vimeo-to-bunny-stream">Planning your migration from Vimeo to Bunny Stream</h2><p>The right migration path depends mostly on the size of your video library and how you want to handle the process. In general, you have three options:</p><ul><li><strong>Manual upload</strong> &#x2192; best for small libraries; requires access to your original video files</li><li><strong>URL fetch</strong> &#x2192; also practical for smaller libraries; lets you import videos using a direct file URL from Vimeo without downloading them locally</li><li><strong>Bulk migration using the </strong><a href="https://docs.bunny.net/stream/vimeo2bunny"><strong>Vimeo2Bunny CLI tool</strong></a> &#x2192; best for larger libraries; automates the migration process</li></ul><p>Pick the option that works best for you, then follow the instructions below.</p>
<!--kg-card-begin: html-->
<h2 id="manual-upload">Migrate via manual upload</h2>
<!--kg-card-end: html-->
<p>Before you begin, <a href="https://dash.bunny.net/auth/register">sign up for a bunny.net account</a> and make sure you have all the videos you need saved on your computer.</p><ul><li><strong>Step 1:</strong> In your bunny.net dashboard, go to <strong>Stream.</strong></li><li><strong>Step 2:</strong> Click <strong>Add Video Library</strong>, then enter a name.</li><li><strong>Step 3:</strong> Once the library is created, select <strong>Upload a Video</strong>. You can upload multiple videos at once.</li><li><strong>Step 4:</strong> After the upload is complete, your video(s) will appear in the library. Selecting a video will take you to a screen like the one below, where you can edit its title, description, etc., as well as grab its URL or embed code.</li></ul><figure class="kg-card kg-image-card"><img src="https://bunny.net/blog/content/images/2026/05/bunny.net-stream-migration.png" class="kg-image" alt="How to migrate from Vimeo to Bunny Stream" loading="lazy" width="2000" height="1026" srcset="https://bunny.net/blog/content/images/size/w600/2026/05/bunny.net-stream-migration.png 600w, https://bunny.net/blog/content/images/size/w1000/2026/05/bunny.net-stream-migration.png 1000w, https://bunny.net/blog/content/images/size/w1600/2026/05/bunny.net-stream-migration.png 1600w, https://bunny.net/blog/content/images/size/w2400/2026/05/bunny.net-stream-migration.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>And that&#x2019;s it: your videos are now ready to be shared.</p><ul><li>If you&#x2019;re sharing videos privately (e.g., sending a link to a client), just copy the <strong>Direct Play URL</strong> from the video&#x2019;s page in the dashboard.</li><li>If you&#x2019;re hosting videos on your website, replace your existing Vimeo embeds with Bunny Stream embed codes. You can adjust embed settings and copy the embed code from the video&#x2019;s page under <strong>Embed</strong>.</li></ul>
<!--kg-card-begin: html-->
<h2 id="url-fetch">Migrate via URL fetch</h2>
<!--kg-card-end: html-->
<p>Instead of uploading files from your computer, you can import videos using a direct file URL from Vimeo.</p><ul><li><strong>Step 1:</strong> In your bunny.net dashboard, go to <strong>Stream.</strong></li><li><strong>Step 2:</strong> Click <strong>Add Video Library</strong>, then enter a name.</li><li><strong>Step 3:</strong> Now go to your Vimeo account and navigate to your video library.</li><li><strong>Step 4:</strong> Choose a video you&#x2019;d like to move to Bunny Stream and click the <strong>Share</strong> dropdown (&quot;More sharing options&quot;) in the top-right corner.</li><li><strong>Step 5:</strong> Select <strong>Download</strong> &#x2014; you&#x2019;ll get a pop-up menu with available resolutions to download.</li><li><strong>Step 6:</strong> Right-click the download icon for the resolution you&#x2019;d like to upload and select <strong>Copy link address.</strong></li><li><strong>Step 7:</strong> Now go back to your bunny.net video library, click the dropdown next to <strong>Upload a Video,</strong> and select <strong>Upload Video from URL.</strong></li></ul><p></p><figure class="kg-card kg-image-card"><img src="https://bunny.net/blog/content/images/2026/05/bunny.net-stream-library.png" class="kg-image" alt="How to migrate from Vimeo to Bunny Stream" loading="lazy" width="1650" height="613" srcset="https://bunny.net/blog/content/images/size/w600/2026/05/bunny.net-stream-library.png 600w, https://bunny.net/blog/content/images/size/w1000/2026/05/bunny.net-stream-library.png 1000w, https://bunny.net/blog/content/images/size/w1600/2026/05/bunny.net-stream-library.png 1600w, https://bunny.net/blog/content/images/2026/05/bunny.net-stream-library.png 1650w" sizes="(min-width: 720px) 720px"></figure><ul><li><strong>Step 8</strong>: Paste the Vimeo URL from your clipboard into the URL field of the pop-up menu and click <strong>Upload Video</strong>.</li></ul><figure class="kg-card kg-image-card"><img src="https://bunny.net/blog/content/images/2026/05/bunny.net-stream-upload.png" class="kg-image" alt="How to migrate from Vimeo to Bunny Stream" loading="lazy" width="1396" height="1060" srcset="https://bunny.net/blog/content/images/size/w600/2026/05/bunny.net-stream-upload.png 600w, https://bunny.net/blog/content/images/size/w1000/2026/05/bunny.net-stream-upload.png 1000w, https://bunny.net/blog/content/images/2026/05/bunny.net-stream-upload.png 1396w" sizes="(min-width: 720px) 720px"></figure><p>Note that, depending on your Vimeo video settings, you may need to add request headers and key values under <strong>Advanced</strong> if they are needed to authenticate the HTTP/HTTPS request to download your video.</p><p>As with manual uploads, if you&apos;re hosting videos on a website, you&apos;ll need to replace your existing Vimeo embeds with Bunny Stream embed codes. You can adjust embed settings and copy the embed code from the selected video&#x2019;s page under <strong>Embed</strong>.</p>
<!--kg-card-begin: html-->
<h2 id="bulk-migration">Bulk migration using Vimeo2Bunny CLI</h2>
<!--kg-card-end: html-->
<p>Both manual upload and URL fetch methods are impractical for larger libraries, so we built a CLI tool that automates the video migration using URLs.</p><p><a href="https://www.npmjs.com/package/vimeo2bunny">Vimeo2Bunny</a> is able to fetch all (or selected) videos and folders from your Vimeo account and create bunny.net collections matching your Vimeo folder structure.</p>
<!--kg-card-begin: html-->
<p>
  For each video, the tool gets a download URL from Vimeo and sends it to bunny.net&#x2019;s fetch endpoint. Bunny Stream downloads directly from Vimeo and copies title, description, and tags. The tool also saves progress to
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">~/.vimeo2bunny/migration-state.json</code>
  so migrations can be resumed if needed.
</p>
<!--kg-card-end: html-->
<p>You can run Vimeo2Bunny without installation using npx. If you don&#x2019;t have npx installed, follow this <a href="https://docs.npmjs.com/downloading-and-installing-node-js-and-npm">official installation guide</a>.</p><p>To use the tool, you&#x2019;ll need to first get the API credentials from both Vimeo and bunny.net.</p><h3 id="how-to-get-the-vimeo-access-token">How to get the Vimeo Access Token</h3><ul><li><strong>Step 1:</strong> Log in to the&#xA0;<a href="https://developer.vimeo.com/">Vimeo Developer Portal</a>&#xA0;and click <strong>Create an app.</strong></li><li><strong>Step 2:</strong> Enter a name for the app and add a brief description. Avoid including &#x201C;Vimeo&#x201D; in the name as it will be rejected.</li><li><strong>Step 3:</strong> Select&#xA0;<strong>No</strong>&#xA0;for &#x201C;Will people besides you be able to access your app?&#x201D;, then accept the terms and click&#xA0;<strong>Create App</strong>.</li><li><strong>Step 4:</strong> On your app&#x2019;s page, go to the&#xA0;<strong>Authentication</strong>&#xA0;section in the left sidebar.</li><li><strong>Step 5:</strong> Under&#xA0;<strong>Generate an access token</strong>, select&#xA0;<strong>Authenticated (you)</strong>&#xA0;and check the following scopes:</li></ul>
<!--kg-card-begin: html-->
<table style="border:1px solid rgba(24, 61, 109, 0.18); border-collapse:collapse; width:100%;">
  <thead>
    <tr style="font-weight:700; text-align:left; background:#223c6a; color:white;">
      <th style="padding:12px 14px; background:rgba(255,255,255,0.03); border-bottom:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); color:white;">
        Scope
      </th>
      <th style="padding:12px 14px; border-bottom:1px solid rgba(24, 61, 109, 0.18); color:white;">
        Required
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="padding:12px 14px; border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">Public</td>
      <td style="padding:12px 14px; vertical-align:middle; color:#183d6d;">&#x2714;&#xFE0F; Required (pre-selected)</td>
    </tr>
    <tr>
      <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">Private</td>
      <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">&#x2714;&#xFE0F; Required</td>
    </tr>
    <tr>
      <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">Video Files</td>
      <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">&#x2714;&#xFE0F; Required</td>
    </tr>
  </tbody>
</table>
<br>
<!--kg-card-end: html-->
<ul><li><strong>Step 6:</strong> Click&#xA0;<strong>Generate.</strong>&#xA0;Your personal access token will appear under&#xA0;<strong>Personal Access Tokens</strong>. Copy and store the token immediately, as it won&#x2019;t be shown again.</li></ul><h3 id="how-to-get-your-bunny-stream-credentials">How to get your Bunny Stream credentials</h3><ul><li>Step 1: Log in to&#xA0;bunny.net.</li><li>Step 2: Go to <strong>Stream</strong> and select a video library.</li><li>Step 3: Go to <strong>API</strong> and copy the <strong>Video</strong> <strong>Library ID</strong>&#xA0;and&#xA0;the <strong>API key</strong>.</li></ul><h3 id="how-to-use-the-vimeo2bunny-cli">How to use the Vimeo2Bunny CLI</h3>
<!--kg-card-begin: html-->
<p>
  If you&#x2019;re using npx
  (recommended), remember to prefix all commands with
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">npx</code>.
  Alternatively, you can install from npm or clone the repo, as described
  <a href="https://docs.bunny.net/stream/vimeo2bunny#installation">here</a>.
</p>

<!--kg-card-end: html-->
<p>To get started, run:</p>
<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

npx vimeo2bunny config

</div>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  You&#x2019;ll be prompted to enter your Vimeo Access Token, Bunny Stream Library ID, and Bunny Stream API key. Alternatively, you can set environment variables (or use an 
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">.env</code>
  file):
</p>

<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

<code style="color:#ECEC93;">export</code> <code style="color:#F5B83D;">VIMEO_ACCESS_TOKEN=</code>your_token
<br>
<code style="color:#ECEC93;">export</code> <code style="color:#F5B83D;">BUNNY_LIBRARY_ID=</code>your_library_id
<br>
<code style="color:#ECEC93;">export</code> <code style="color:#F5B83D;">BUNNY_LIBRARY_API_KEY=</code>your_api_key

</div>
<br>
<!--kg-card-end: html-->
<p>Once configured, the tool lets you list your Vimeo content and migrate all or selected videos:</p>
<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

vimeo2bunny list&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; <code style="color:#998066;"># everything</code>
<br>
vimeo2bunny list --folders&#xA0;&#xA0; <code style="color:#998066;"># folders only</code>
<br>
vimeo2bunny list --videos&#xA0;&#xA0;&#xA0; <code style="color:#998066;"># videos only</code>
<br>
<br>
<code style="color:#998066;"># Preview what will happen (no changes made)</code>
<br>
vimeo2bunny migrate --dry-run
<br>
<br>
<code style="color:#998066;"># Run the migration</code>
<br>
vimeo2bunny migrate
<br>
<br>
<code style="color:#998066;"># Migrate a specific folder</code>
<br>
vimeo2bunny migrate --folder &lt;vimeo-folder-id&gt;
<br>
<br>
<code style="color:#998066;"># Adjust parallel transfers (default: 3, max: 20)</code>
<br>
vimeo2bunny migrate --concurrency 5
<br>
<br>
<code style="color:#998066;"># Resume an interrupted migration</code>
<br>
vimeo2bunny migrate --resume
<br>
<br>
<code style="color:#998066;"># Check migration status</code>
<br>
vimeo2bunny status

</div>
<br>
<!--kg-card-end: html-->
<p>Here&#x2019;s what gets mapped during bulk migration and how:</p>
<!--kg-card-begin: html-->
<table style="border:1px solid rgba(24, 61, 109, 0.18); border-collapse:collapse; width:100%;">
  <thead>
    <tr style="font-weight:700; text-align:left; background:#223c6a; color:white;">
      <th style="padding:12px 14px; background:rgba(255,255,255,0.03); border-bottom:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); color:white;">
        Vimeo
      </th>
      <th style="padding:12px 14px; border-bottom:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); color:white;">
        bunny.net
      </th>
      <th style="padding:12px 14px; border-bottom:1px solid rgba(24, 61, 109, 0.18); color:white;">
        Notes
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="padding:12px 14px; border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">
        <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">name</code>
      </td>
      <td style="padding:12px 14px; border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">
        <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">title</code>
      </td>
      <td style="padding:12px 14px; vertical-align:middle; color:#183d6d;">Direct mapping</td>
    </tr>
    <tr>
      <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">
        <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">description</code>
      </td>
      <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">
        <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">metaTags[description]</code>
      </td>
      <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">Stored as meta tag</td>
    </tr>
    <tr>
      <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">
        <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">tags</code>
      </td>
      <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">
        <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">metaTags[keywords]</code>
      </td>
      <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">Comma-separated</td>
    </tr>
    <tr>
      <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">
        <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">Folder</code>
      </td>
      <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); border-right:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">
        <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">Collection</code>
      </td>
      <td style="padding:12px 14px; border-top:1px solid rgba(24, 61, 109, 0.18); vertical-align:middle; color:#183d6d;">Folder &#x2192; Collection</td>
    </tr>
  </tbody>
</table>
<br>
<!--kg-card-end: html-->
<p>After migrating your videos to Bunny Stream, don&#x2019;t forget to replace your existing Vimeo embeds with Bunny Stream embed codes. You can find more details about embedding videos <a href="https://docs.bunny.net/stream/embedding">in the docs</a>.</p><h2 id="wrapping-up">Wrapping up</h2><p>Migrating from Vimeo to Bunny Stream is straightforward once you&#x2019;ve chosen the approach that fits your setup.</p><ul><li>For smaller libraries, manual upload or URL fetch will get you up and running quickly.</li><li>For larger libraries, the Vimeo2Bunny CLI tool handles the migration efficiently and lets you resume progress if needed.</li></ul><p>Once your videos are in Bunny Stream, the final step is updating your embeds so your content continues to play on your website or app.</p><p><strong>A quick note:</strong> this guide and the Vimeo2Bunny tool are intended for migrating content you own or have permission to use. You&apos;re responsible for making sure your usage complies with Vimeo&apos;s terms.</p><h2 id="additional-resources">Additional resources</h2><ul><li><a href="https://docs.bunny.net/stream/quickstart">Bunny Stream quickstart</a></li><li><a href="https://docs.bunny.net/stream/dashboard">Managing videos in the dashboard</a></li><li><a href="https://docs.bunny.net/stream/url-fetch">Uploading videos from a remote URL</a></li><li><a href="https://docs.bunny.net/stream/vimeo2bunny">Vimeo2Bunny CLI tool documentation</a></li><li><a href="https://docs.bunny.net/stream/embedding">Embedding videos</a></li></ul>]]></content:encoded></item><item><title><![CDATA[Introducing the bunny.net CLI]]></title><description><![CDATA[Today we're releasing the bunny.net CLI, a single command-line tool for building and managing your entire bunny.net stack. This first release ships with full Database support, including the interactive SQL shell we introduced a few weeks ago. ]]></description><link>https://bunny.net/blog/introducing-the-bunny-net-cli/</link><guid isPermaLink="false">69f1afb3160dc403fbfcf1fd</guid><category><![CDATA[News]]></category><dc:creator><![CDATA[Jamie Barton]]></dc:creator><pubDate>Mon, 11 May 2026 11:35:00 GMT</pubDate><media:content url="https://bunny.net/blog/content/images/2026/05/bunny.net-CLI-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://bunny.net/blog/content/images/2026/05/bunny.net-CLI-1.png" alt="Introducing the bunny.net CLI"><p>The best developer tools don&apos;t ask you to context-switch. They meet you where you already are.</p><p>For most developers, that&apos;s the terminal. Not because it&apos;s retro or minimal, but because it&apos;s the one surface that works everywhere&#x2014;your local machine, your CI pipeline, your AI coding assistant. The commands you type are the same commands your automation runs. A good CLI isn&apos;t just a power-user shortcut. It&apos;s the most composable interface a platform can offer.</p><p>Today we&apos;re releasing the bunny.net<strong> CLI,</strong> a single command-line tool for building and managing your entire bunny.net stack. This first release ships with full Database support, including the <a href="https://bunny.net/blog/introducing-the-interactive-bunny-database-shell">interactive SQL shell</a> we introduced a few weeks ago. Support for Edge Scripting, Storage, Magic Containers, and more is coming soon.</p><p>The CLI is currently in <strong>public preview</strong>, and we&#x2019;re building it in the open. We&#x2019;d love your feedback, bug reports, and pull requests as we shape what comes next.</p><h2 id="up-and-running-in-three-commands">Up and running in three commands</h2><p>Install with npm:</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; color:#998066; overflow:auto;">
<span style="color:#ECEC93;">npm install</span> <span style="color:#998066;">-</span><span style="color:#FFFFFF;">g</span> <span style="color:#FFFFFF;">@bunny.net/cli</span>

<span style="color:#998066;"># or</span>
<span style="color:#998066;"># curl -fsSL https://cli.bunny.net/install.sh | sh</span>
</pre>
<br>
<!--kg-card-end: html-->
<p>Log in:</p>
<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny login

</div>
<br>
<!--kg-card-end: html-->
<p>This opens your browser, authenticates with your bunny.net account, and saves a profile locally. Note that it currently fetches your API key from your account once signed in.</p><p>Confirm it worked:</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto;"><span style="color:#FFFFFF;">bunny </span><span style="color:#ECEC93;">whoami</span>
<span style="color:#FFFFFF;">Logged </span><span style="color:#779FC9;">in</span><span style="color:#FFFFFF;"> as Jamie Barton (jamie@bunny.net) &#x1F407;</span></pre>
<br>
<!--kg-card-end: html-->
<figure class="kg-card kg-image-card"><img src="https://bunny.net/blog/content/images/2026/05/bunny.net-CLI.png" class="kg-image" alt="Introducing the bunny.net CLI" loading="lazy" width="893" height="717" srcset="https://bunny.net/blog/content/images/size/w600/2026/05/bunny.net-CLI.png 600w, https://bunny.net/blog/content/images/2026/05/bunny.net-CLI.png 893w" sizes="(min-width: 720px) 720px"></figure><h2 id="database-management-without-the-dashboard-detour">Database management without the dashboard detour</h2><p>The usual flow after creating a database is to open a dashboard, hunt for the connection string, copy it, paste it somewhere, hope you got the right one.</p><p>The CLI replaces all of that with a few commands.</p><h3 id="create-a-database">Create a database</h3>
<!--kg-card-begin: html-->
<p>
  Run <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny db create</code> with no arguments and the CLI walks you through it interactively &#x2014; name, region, options:
</p>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny db create

</div>
<br>
<!--kg-card-end: html-->
<p>Already know what you want? Pass the flags directly:</p>
<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny db create --name my-app-db --primary UK

</div>
<br>
<!--kg-card-end: html-->
<p>Both paths produce the same result. Interactive mode is great for exploring. Flags-first is what you&apos;ll reach for in scripts, pipelines, or when your AI assistant is driving.</p>
<!--kg-card-begin: html-->
<p>
  After creation, the CLI offers to link the database to your current directory, generate an auth token, and write credentials to
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">.env</code>
  so you can go straight from
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">create</code>
  to querying in the same shell session.
</p>
<br>
<!--kg-card-end: html-->
<p>Inspect what you just created:</p>
<!--kg-card-begin: html-->
<pre style="background-color:#202020; padding:1em; border-radius:6px; font-family:monospace; overflow:auto; color:#FFFFFF;">bunny db show

 Key               Value
 ID                db_01KP8D0MKHB7HWEYYE6VX8CTG7
 Name              my-app-db
 URL               libsql://01KP8D0MGVG0PEWR9SCTND02PH-my-app-db.lite.bunnydb.net/
 Status            Active
 Size              <span style="color:#d1949e;">4.1 KB / 1024.0 MB</span>  &#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;&#x2591;  <span style="color:#d1949e;">0%</span>
 Storage Region    eu-west-1
 Primary Region    London (UK)
 Replica Regions   None</pre>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<br>
<h3>Linking a directory to a database</h3>

<p>
  Most <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">db</code> commands need to know which database you&apos;re targeting. Passing an ID every time gets old fast, so the CLI resolves the target database automatically in this order:
</p>

<ul>
  <li>An explicit <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">&lt;database-id&gt;</code> argument</li>
  <li>A <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">.bunny/database.json</code> manifest created by <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny db link</code></li>
  <li><code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">BUNNY_DATABASE_URL</code> from a <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">.env</code> file (walked up from the current directory)</li>
  <li>An interactive picker if none of the above match</li>
</ul>

<p>
  The recommended flow is <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny db link</code>:
</p>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny db <code style="color:#ECEC93;">link</code>

</div>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  This writes a small
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">.bunny/database.json</code>
  manifest to your project. Every
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">db</code>
  command run from that directory (
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">show</code>,
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">shell</code>,
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">studio</code>,
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">usage</code>,
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">tokens create</code>)
  now knows which database to target. No flags, no env var lookups, no connection strings in your shell history.
</p>

<p>
  Add
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">.bunny/</code>
  to your
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">.gitignore</code>.
  The manifest is a per-developer convenience, not something to commit. Each contributor links their own environment.
</p>

<p>
  If you&apos;d rather drive things from
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">.env</code>
  (handy when the same
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">BUNNY_DATABASE_URL</code>
  is already wired into your app), the CLI picks that up automatically. And when you need to run a one-off command against a different database without changing context, pass the ID as an argument:
</p>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny db show db_01KP8D0MKHB7HWEYYE6VX8CTG7

</div>
<br>
<!--kg-card-end: html-->
<h3 id="list-monitor-and-clean-up">List, monitor, and clean up</h3>
<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny db list
<br>
bunny db delete
<br>
bunny db usage

</div>
<br>
<!--kg-card-end: html-->
<h3 id="multi-region-replication">Multi-region replication</h3><p>Bring data closer to your users with read replicas:</p>
<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny db regions list
<br>
bunny db regions <code style="color:#ECEC93;">add</code> --replicas UK,NY
<br>
bunny db regions remove --replicas NY

</div>
<br>
<!--kg-card-end: html-->
<h3 id="token-management">Token management</h3><p>Generate and revoke database tokens without touching the dashboard:</p>
<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny db tokens create
<br>
bunny db tokens create --read-only --expiry 30d
<br>
bunny db tokens invalidate

</div>
<br>
<!--kg-card-end: html-->
<h3 id="drop-into-the-interactive-shell">Drop into the interactive shell</h3><p>The CLI embeds the same <a href="https://bunny.net/blog/introducing-the-interactive-bunny-database-shell">@bunny.net/database-shell</a> we released as a standalone package, so you get the full interactive experience (dot-commands, saved views, output modes, sensitive column masking) with zero extra setup:</p>
<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny db shell

</div>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

&#x2713; Connected to database
<br>
&#xA0;&#xA0;Type .help for commands, .quit to exit.
<br>
<br>
&#x2192;&#xA0;&#xA0;SELECT id, name, email FROM users;
<br>
&#x250C;&#x2500;&#x2500;&#x2500;&#x2500;&#x252C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x252C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2510;
<br>
&#x2502; id &#x2502; name&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#x2502; email&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#x2502;
<br>
&#x251C;&#x2500;&#x2500;&#x2500;&#x2500;&#x253C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x253C;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2524;
<br>
&#x2502; 1&#xA0;&#xA0;&#x2502; Alice&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#x2502; a****e@example.com&#xA0;&#xA0;&#x2502;
<br>
&#x2502; 2&#xA0;&#xA0;&#x2502; Bob&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#x2502; b****b@example.com&#xA0;&#xA0;&#x2502;
<br>
&#x2514;&#x2500;&#x2500;&#x2500;&#x2500;&#x2534;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2534;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2518;
<br>
2 rows (4ms)

</div>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  No connection strings. No token flags. Your authenticated profile handles it. Just
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny db shell</code>
  and you&apos;re querying.
</p>
<!--kg-card-end: html-->
<p>Run a one-off query without entering the REPL:</p>
<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny db shell <code style="color:#ECEC93;">&lt;</code>database-id<code style="color:#ECEC93;">&gt;</code> <code style="color:#BDE052;">&quot;SELECT count(*) FROM orders&quot;</code>

</div>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<h3>A read-only view with <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny db studio</code></h3>

<p>
  Sometimes you don&apos;t want a REPL. You want to click through tables and see what&apos;s actually in there.
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny db studio</code>
  spins up a local server and opens a read-only table viewer in your browser:
</p>

<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny db studio

</div>
<br>
<p>
  It uses the same resolution order as every other
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">db</code>
  command, so if you&apos;ve run
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny db link</code>
  it picks up the right database automatically. A short-lived auth token is generated for the session and discarded when you close it.
</p>
<br>
<!--kg-card-end: html-->
<figure class="kg-card kg-image-card"><img src="https://bunny.net/blog/content/images/2026/05/bunny-cli-read-only-view.png" class="kg-image" alt="Introducing the bunny.net CLI" loading="lazy" width="1066" height="557" srcset="https://bunny.net/blog/content/images/size/w600/2026/05/bunny-cli-read-only-view.png 600w, https://bunny.net/blog/content/images/size/w1000/2026/05/bunny-cli-read-only-view.png 1000w, https://bunny.net/blog/content/images/2026/05/bunny-cli-read-only-view.png 1066w" sizes="(min-width: 720px) 720px"></figure>
<!--kg-card-begin: html-->
<p>
  Studio is currently experimental and read-only by design, <strong>inspect, don&apos;t mutate</strong>. For schema changes and data edits, reach for
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny db shell</code>.
</p>

<h2>Raw API access with <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny api</code></h2>

<p>
  Purpose-built commands cover the most common workflows, but sometimes you need to reach an endpoint that doesn&#x2019;t have a dedicated command yet, or you just want to poke at the API directly. That&#x2019;s what
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny api</code>
  is for.
</p>

<p>
  It&#x2019;s a raw, authenticated HTTP client for the entire bunny.net
  API. Pick a method, pass a path, and go:
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

# List your pull zones
<br>
bunny api <code style="color:#D1949E;">GET</code> <code style="color:#ECEC93;">/</code>pullzone
<br>
<br>
# Grab a specific DNS zone
<br>
bunny api <code style="color:#D1949E;">GET</code> <code style="color:#ECEC93;">/</code>dnszone<code style="color:#ECEC93;">/</code><code style="color:#D1949E;">12345</code>
<br>
<br>
# Create a resource with a JSON body
<br>
bunny api <code style="color:#D1949E;">POST</code> <code style="color:#ECEC93;">/</code>videolibrary <code style="color:#ECEC93;">--</code>body <code style="color:#BDE052;">&apos;{&quot;Name&quot;:&quot;uploads&quot;,&quot;ReplicationRegions&quot;:[&quot;DE&quot;,&quot;NY&quot;]}&apos;</code>

</div>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<p>
  Every request is automatically authenticated with your current profile, and every response is pretty-printed JSON by default. Pair it with
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">--output json</code>
  and
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">jq</code>
  to build quick one-liners:
</p>
<br>
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny api <code style="color:#D1949E;">GET</code> <code style="color:#ECEC93;">/</code>pullzone <code style="color:#ECEC93;">--</code>output json <code style="color:#ECEC93;">|</code> jq <code style="color:#BDE052;">&apos;.[].Name&apos;</code>

</div>
<br>

<p>
  You can also pipe request bodies from stdin, which makes it easy to compose with other tools or feed in generated payloads:
</p>
<br>
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

cat payload.json <code style="color:#ECEC93;">|</code> bunny api <code style="color:#D1949E;">POST</code> <code style="color:#ECEC93;">/</code>dnszone

</div>
<br>

<p>
  Think of
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny api</code>
  as the escape hatch. If the
 bunny.net
  API supports it, you can call it from your terminal right now &#x2014; no wrapper command needed, no dashboard required. It&#x2019;s especially useful for AI agents that need to access endpoints beyond the built-in commands.
</p>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<h2>Built like the CLIs you already trust</h2>

<p>
  We followed the
  <a href="https://clig.dev/">Command Line Interface Guidelines</a>
  closely. The conventions that make
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">git</code>,
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">docker</code>,
  and
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">kubectl</code>
  feel predictable - we wanted
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">bunny</code>
  to feel that way from the start.
</p>

<p>
  <strong>Interactive or scriptable - your call.</strong>
  Prompts and spinners are TTY-aware. They show up in your terminal but stay out of the way in CI or when piping output.
</p>

<p>
  <strong>Structured output everywhere.</strong>
  Every command supports
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">--output json</code>.
  Primary output goes to stdout, messages go to stderr, so piping always works cleanly:
</p>

<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny db list <code style="color:#ECEC93;">--</code>output json <code style="color:#ECEC93;">|</code> jq <code style="color:#BDE052;">&apos;.[].name&apos;</code>

</div>
<br>
<p>
  <strong>Clear, helpful errors.</strong>
  Errors are written in plain language with hints on how to fix them. Exit codes are meaningful:
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">1</code>
  for fixable problems,
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">2</code>
  for internal errors. With
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">--output json</code>,
  errors come back as structured JSON too.
</p>

<p>
  <strong>Respects your environment.</strong>
  The CLI honors
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">NO_COLOR</code>,
  supports named profiles for switching between accounts, and reads
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">BUNNYNET_API_KEY</code>
  from your environment when you&apos;d rather skip interactive login:
</p>
<br>
<!--kg-card-end: html-->

<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

bunny config profile create --name work
<br>
bunny login --profile work
<br>
bunny db list --profile work

</div>
<!--kg-card-end: html-->
<h2 id="your-ai-agent-already-knows-how-to-use-it">Your AI agent already knows how to use it</h2><p>There&apos;s a growing question in the developer tools space: do AI agents need custom protocols, or are the CLI tools developers already use enough?</p><p>Increasingly, the answer is that CLIs win.</p><p>AI models have been trained on billions of lines of terminal interactions. When an agent reaches for a CLI, it&apos;s drawing on deeply learned patterns, not adapting to a bespoke integration. Every bunny CLI command that returns structured JSON is a command an AI agent can already use, with zero extra setup.</p>
<!--kg-card-begin: html-->
<p>
  If you&apos;re working with Claude Code, Cursor, or Windsurf, your assistant can already create databases, manage tokens, and query data through the bunny CLI, the same way it uses
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">git</code>
  or
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">npm</code>
  today. No MCP server to configure. No schema tokens eating your context window. The CLI you use is the same interface your agent uses.
</p>
<!--kg-card-end: html-->
<p>The CLI comes with agent skills for databases and scripts so your agent knows how to get started. Below we can see Claude Code discovered the tool, skills, and created a database and schema for a new project.</p><figure class="kg-card kg-image-card"><img src="https://bunny.net/blog/content/images/2026/05/bunny.net-client-demo-1.png" class="kg-image" alt="Introducing the bunny.net CLI" loading="lazy" width="870" height="961" srcset="https://bunny.net/blog/content/images/size/w600/2026/05/bunny.net-client-demo-1.png 600w, https://bunny.net/blog/content/images/2026/05/bunny.net-client-demo-1.png 870w" sizes="(min-width: 720px) 720px"></figure><h2 id="whats-coming-next">What&apos;s coming next</h2><p>Database support is the starting point. Here&apos;s where we&apos;re headed.</p><h3 id="edge-scripting">Edge Scripting</h3><p>Scaffold a project, deploy serverless functions to the edge, and manage secrets, all from the terminal. Deployment history and rollbacks are on the roadmap too.</p><h3 id="storage">Storage</h3><p>Create buckets, list and sync files, and manage access to S3-compatible storage. The same operations you&apos;d do in the dashboard or with a third-party client, built directly into the CLI with your existing auth.</p><h3 id="magic-containers-apps">Magic Containers Apps</h3>
<!--kg-card-begin: html-->
<p>
  If you&apos;ve ever wanted to go from a
  <code style="background-color:#1e1e1e; color:#EB5757; padding:2px 6px; border-radius:4px;">Dockerfile</code>
  to a running, globally distributed application in a single command, that&apos;s what we&#x2019;re adding next.
</p>
<!--kg-card-end: html-->
<h3 id="the-bigger-picture">The bigger picture</h3><p>Our goal is a workflow where you go from an empty directory to a fully deployed application without leaving the terminal. Database provisioned, edge functions live, storage configured, containers scaled. Whether you&apos;re typing the commands or your AI assistant is running them, it&apos;s the same tool, the same output, the same interface.</p><p>Every bunny.net product, one CLI.</p><h2 id="built-in-the-open-built-in-typescript">Built in the open, built in TypeScript</h2><p>The bunny CLI is open source, and we&#x2019;re developing it in public. This isn&#x2019;t a finished product we&#x2019;re handing to developers. It&#x2019;s an active project we want to build with you.</p><p>The bunny.net developer platform is JavaScript-native. Edge Scripts run with Deno and Node.js compatibility. The developers building on bunny.net write JavaScript and TypeScript. By keeping the CLI in the same language, we can share code between the CLI and the scripts developers write. Use exports, generated clients, and utilities written for the CLI inside Edge Scripts.</p><p>One language for the CLI, the API client, the config schemas, and the edge runtime means fewer switches and a codebase that any JS developer can read, debug, or contribute to.</p><p>If something is broken, <a href="https://github.com/BunnyWay/cli/issues">open an issue</a>. If you want a feature, tell us. If you want to contribute, pull requests are welcome. The CLI is built with TypeScript, Yargs, and a lot of packages that should feel very familiar.</p><h2 id="get-started">Get started</h2>
<!--kg-card-begin: html-->
<div style="background-color:#1e1e1e; padding:1em; border-radius:6px; font-family:monospace; color:#ffffff;">

<code style="color:#ECEC93;">npm install</code> -g @bunny.net/cli
<br>
bunny login

</div>
<br>
<!--kg-card-end: html-->
<p><a href="https://github.com/bunnyWay/cli" rel="noopener noreferrer">Star the repo on GitHub</a>, join us on <a href="https://discord.com/invite/bunnynet" rel="noopener noreferrer">Discord</a>, and let&#x2019;s build this together.</p>]]></content:encoded></item></channel></rss>