This guide explains how to architect production-scale browser automation systems using Kernel, how to handle high-concurrency workloads, and best practices for building resilient systems.After understanding the basics of our browsers, you should understand how to create and connect to individual browsers on-demand. This guide builds on that foundation to help you design systems using browser pools that can handle hundreds or thousands of concurrent browser tasks reliably.
For proof-of-concept work and early production systems with modest concurrency needs, creating browsers on-demand is the simplest approach.When to use:
Processing fewer than 50 concurrent tasks
Infrequent, unpredictable workloads
Early development and testing
Example
import Kernel from '@onkernel/sdk';import { chromium } from 'playwright';const kernel = new Kernel();async function processTask(taskData: any) { // Create browser with extended timeout for long tasks const session = await kernel.browsers.create({ stealth: true, timeout_seconds: 3600, // Destroy browser after 1 hour of inactivity }); try { const browser = await chromium.connectOverCDP(session.cdp_ws_url); const context = browser.contexts()[0]; const page = context.pages()[0]; // Your automation logic here await page.goto(taskData.url); // ... perform work ... return { success: true, data: /* results */ }; } catch (error) { console.error('Task failed:', error); return { success: false, error: error.message }; } finally { // Always clean up await kernel.browsers.deleteByID(session.session_id); }}
For production systems with consistent, high-frequency workloads, a browser pool allows you to access higher concurrency plus predictable performance.When to use:
50-100+ concurrent tasks with consistent configuration
Steady request patterns
Example
import Kernel from '@onkernel/sdk';import { chromium } from 'playwright';const kernel = new Kernel();const POOL_NAME = 'production-pool';// Initialize pool once (typically in deployment/startup)async function initializePool() { await kernel.browserPools.create({ name: POOL_NAME, size: 25, // Balance cost and availability timeout_seconds: 300, // Destroy browsers after 5 minutes of inactivity stealth: true, headless: false, // headless: true for cost savings if no live view needed });}async function processTask(taskData: any) { let session; try { // Acquire browser (returns immediately if available) session = await kernel.browserPools.acquire(POOL_NAME, { acquire_timeout_seconds: 30, // Wait up to 30s for availability }); const browser = await chromium.connectOverCDP(session.cdp_ws_url); const context = browser.contexts()[0]; const page = context.pages()[0]; // Perform work await page.goto(taskData.url); // ... automation logic ... return { success: true, data: /* results */ }; } catch (error) { console.error('Task failed:', error); return { success: false, error: error.message }; } finally { // Critical: Always release back to pool if (session) { await kernel.browserPools.release(POOL_NAME, { session_id: session.session_id, reuse: true, // Reuse for efficiency }); } }}
Key considerations:
Pool size should match your typical concurrency
Always release browsers in a finally block to prevent pool exhaustion
Set acquire_timeout_seconds based on your SLA requirements