Mastering Browser Run on Cloudflare Containers: A Step-by-Step Guide to Faster, Scalable Headless Browsers
Overview
Browser Run has undergone a transformative upgrade by migrating to Cloudflare’s Containers, resulting in significantly faster performance and greater scalability. Previously built on shared infrastructure with Browser Isolation (BISO), the platform now leverages Durable Object (DO)-enabled Containers, enabling developers to spin up 60 browser instances per minute via Workers binding and run up to 120 concurrently—four times the previous limit. Quick Action response times have dropped by over 50%, and new features and fixes ship faster than ever. This guide walks you through the essentials of using Browser Run on Containers, from setup to optimization, with practical code examples and tips to avoid common pitfalls.

Prerequisites
Before diving in, ensure you have the following:
- A Cloudflare account with Workers access (Free, Pay-as-you-go, or Enterprise plan).
- Basic familiarity with JavaScript or TypeScript for writing Cloudflare Workers.
- Understanding of headless browser concepts (e.g., Puppeteer, Playwright).
- Wrangler CLI installed and configured (
npm install -g wrangler). - A Cloudflare API token with Workers and Browser Run permissions (optional but recommended for debugging).
Step-by-Step Instructions
1. Setting Up Your Environment
Start by creating a new Workers project or using an existing one. In your wrangler.toml, add the browser binding under [browser]:
name = "my-browser-worker"
main = "src/index.js"
compatibility_date = "2024-01-01"
[browser]
binding = "BROWSER"
This tells Workers to expose a BROWSER object you can use to launch headless browser instances. Deploy with wrangler deploy.
2. Creating Your First Browser Instance via Workers Binding
Use the BROWSER binding to launch a new browser session. Here’s a minimal example in JavaScript that fetches a webpage and returns its title:
export default {
async fetch(request, env) {
const browser = await env.BROWSER.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com');
const title = await page.title();
await browser.close();
return new Response(`Title: ${title}`);
} catch (err) {
await browser.close();
return new Response('Error: ' + err.message, { status: 500 });
}
}
}
Key details: launch() returns a browser instance; always close it to free resources. The new Containers backend ensures startup is lightning-fast.
3. Performing Quick Actions (Screenshot, PDF, etc.)
Browser Run supports “Quick Actions” for common tasks without managing full browser lifecycles. These are now 50% faster. Example: capture a screenshot:
export default {
async fetch(request, env) {
const screenshot = await env.BROWSER.screenshot('https://example.com');
return new Response(screenshot, {
headers: { 'Content-Type': 'image/png' }
});
}
}
Other actions include pdf(url), content(url) to extract HTML, and html(url) for rendering. Use these for high-volume tasks to reduce overhead.
4. Scaling and Concurrency
You’re now capped at 60 browsers per minute and 120 concurrent sessions. To maximize throughput, use Promise.all for parallel tasks:

const urls = ['https://a.com', 'https://b.com', ...];
const results = await Promise.all(urls.map(url =>
env.BROWSER.screenshot(url)
));
But watch out—exceeding limits returns 429 errors. Buffer calls or use a queue. The new Container architecture handles spiky workloads better than the old BISO setup.
5. Monitoring and Performance Optimization
Use Cloudflare’s dashboard to track Browser Run metrics: request count, latency, success rate. For custom monitoring, add logging in your Worker. Optimization tips:
- Reuse browser instances for multiple page interactions within a single session (avoid launch/close per page).
- Set timeouts to prevent hanging connections:
page.setDefaultTimeout(10000); - Use Quick Actions for simple tasks—they bypass full browser setup.
Common Mistakes and Pitfalls
- Forgetting to close the browser: Each unclosed session counts toward your 120 limit. Always use
try/finallyto ensurebrowser.close()runs. - Misunderstanding rate limits: 60 launches/min, 120 concurrent. Confusing
launch()with Quick Actions? Quick Actions don’t count toward the launch limit but still count toward concurrency if they use a browser. - Not handling async errors: Promises can reject. Wrap in try/catch to avoid unhandled rejections crashing your Worker.
- Ignoring global distribution: Browser Run runs on Cloudflare’s edge. If your Worker is in one region, the browser starts nearby—but don’t assume latency is zero. Use
request.cf.colofor debugging. - Hardcoding URLs without validation: Malicious inputs can cause unexpected behavior. Sanitize URIs before passing to Browser Run.
Summary
Browser Run on Cloudflare Containers delivers 4x higher concurrency (up to 120), 50% faster Quick Actions, and 60 launches per minute—all with no code changes required. This guide provided the steps to set up your environment, create browser instances, use Quick Actions, scale efficiently, and avoid common mistakes. By building on Cloudflare’s own platform, the team ensures continuous improvement and reliability. Start leveraging this upgrade today for end-to-end testing, AI web agents, or secure URL investigation.
Related Articles
- Dynamic Workflows: Custom Durable Execution for Every Tenant
- Kubernetes v1.36 Beta Upgrade: Mixed Version Proxy Now Default, Eliminates Upgrade 404 Errors
- Dynamic Workflows: Enabling Per-Tenant Durable Execution on Cloudflare
- Serverless Spam Classifier Launched: Real-Time ML on AWS Lambda
- AWS MCP Server Reaches General Availability: Secure AI Agent Integration
- AI Agent Security Crisis: Sandboxing Solutions Emerge as Critical Defense Against Catastrophic Failures
- Mastering Controller Resilience: A Guide to Staleness Mitigation and Observability in Kubernetes v1.36
- Harnessing AI with Azure Cosmos DB: Insights from Cosmos Conf 2026