> ## Documentation Index
> Fetch the complete documentation index at: https://tbd-6fc993ce-hypeship-browser-pools-docs-refresh.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Browser Pools Quickstart

> Create a pool of pre-warmed browsers and acquire your first one

A browser pool keeps a set of identically-configured browsers warm and ready to use. Acquiring a browser from a pool is faster than creating one on demand because the browser has already booted with your stealth, proxy, extension, viewport, and profile settings applied.

**Idle browsers in a pool aren't billed.** You pay the standard per-second rate only while a browser is acquired and running, so a warm pool costs nothing between tasks. Pool capacity does count against your [concurrency limit](/info/pricing#concurrency-limits) — a pool of 20 uses 20 of your limit whether or not those browsers are acquired.

<Info>
  Browser pools are available on the Start-Up and Enterprise plans. Install the Kernel SDK first:

  * Typescript/Javascript: `npm install @onkernel/sdk`
  * Python: `pip install kernel`
  * Go: `go get github.com/kernel/kernel-go-sdk`
</Info>

## Create the pool

Create the pool once — at deploy time, on service startup, or by hand from the CLI or [dashboard](https://dashboard.onkernel.com). Keep it out of the code path that serves your workload: pools fill at 25% of their size per minute by default, so a pool of 20 takes about four minutes to warm up completely.

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  import Kernel from '@onkernel/sdk';

  const kernel = new Kernel();

  const pool = await kernel.browserPools.create({
    name: 'checkout-pool',
    size: 20,
    stealth: true,
    timeout_seconds: 600,
  });

  console.log(pool.id);
  ```

  ```python Python theme={null}
  from kernel import Kernel

  kernel = Kernel()

  pool = kernel.browser_pools.create(
      name="checkout-pool",
      size=20,
      stealth=True,
      timeout_seconds=600,
  )

  print(pool.id)
  ```

  ```go Go theme={null}
  package main

  import (
  	"context"
  	"fmt"

  	"github.com/kernel/kernel-go-sdk"
  )

  func main() {
  	ctx := context.Background()
  	client := kernel.NewClient()

  	pool, err := client.BrowserPools.New(ctx, kernel.BrowserPoolNewParams{
  		Name:           kernel.String("checkout-pool"),
  		Size:           20,
  		Stealth:        kernel.Bool(true),
  		TimeoutSeconds: kernel.Int(600),
  	})
  	if err != nil {
  		panic(err)
  	}

  	fmt.Println(pool.ID)
  }
  ```

  ```bash CLI theme={null}
  kernel browser-pools create checkout-pool --size 20 --stealth --timeout 600
  ```
</CodeGroup>

Every browser in the pool gets the same configuration, so put whatever your workload needs here — proxies, extensions, a viewport, a profile, custom [Chrome policies](/browsers/pools/policy-json). See [Pool configuration options](/browsers/pools/overview#pool-configuration-options) for the full list.

## Acquire, drive, and release a browser

Now do the work. `acquire` returns a browser immediately if one is available, or waits until one is. The response carries the same fields as an on-demand browser — `session_id`, `cdp_ws_url`, `browser_live_view_url` — so your automation code doesn't change.

Release the browser when you're done. Release in a `finally` block: a browser you don't release stays acquired until its idle timeout expires, which shrinks the pool everyone else is acquiring from.

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  import Kernel from '@onkernel/sdk';

  const kernel = new Kernel();

  const kernelBrowser = await kernel.browserPools.acquire('checkout-pool', {
    acquire_timeout_seconds: 30,
  });

  try {
    const response = await kernel.browsers.playwright.execute(
      kernelBrowser.session_id,
      {
        code: `
          await page.goto('https://www.onkernel.com');
          return await page.title();
        `,
      },
    );
    console.log(response.result);
  } finally {
    await kernel.browserPools.release('checkout-pool', {
      session_id: kernelBrowser.session_id,
    });
  }
  ```

  ```python Python theme={null}
  from kernel import Kernel

  kernel = Kernel()

  kernel_browser = kernel.browser_pools.acquire(
      "checkout-pool",
      acquire_timeout_seconds=30,
  )

  try:
      response = kernel.browsers.playwright.execute(
          id=kernel_browser.session_id,
          code="""
            await page.goto('https://www.onkernel.com')
            return await page.title()
          """,
      )
      print(response.result)
  finally:
      kernel.browser_pools.release(
          "checkout-pool",
          session_id=kernel_browser.session_id,
      )
  ```

  ```go Go theme={null}
  package main

  import (
  	"context"
  	"fmt"

  	"github.com/kernel/kernel-go-sdk"
  )

  func main() {
  	ctx := context.Background()
  	client := kernel.NewClient()

  	kernelBrowser, err := client.BrowserPools.Acquire(ctx, "checkout-pool", kernel.BrowserPoolAcquireParams{
  		AcquireTimeoutSeconds: kernel.Int(30),
  	})
  	if err != nil {
  		panic(err)
  	}
  	defer func() {
  		if err := client.BrowserPools.Release(ctx, "checkout-pool", kernel.BrowserPoolReleaseParams{
  			SessionID: kernelBrowser.SessionID,
  		}); err != nil {
  			panic(err)
  		}
  	}()

  	response, err := client.Browsers.Playwright.Execute(ctx, kernelBrowser.SessionID, kernel.BrowserPlaywrightExecuteParams{
  		Code: `
  			await page.goto('https://www.onkernel.com');
  			return await page.title();
  		`,
  	})
  	if err != nil {
  		panic(err)
  	}

  	fmt.Println(response.Result)
  }
  ```

  ```bash CLI theme={null}
  kernel browser-pools acquire checkout-pool --timeout 30

  # then, when you're done with the session it returned
  kernel browser-pools release checkout-pool --session-id <session-id>
  ```
</CodeGroup>

By default, released browsers are reused: the browser goes back into the pool as-is and the next `acquire` gets it. Pass `reuse: false` to destroy it instead and have the pool warm a replacement — use that when the browser holds state you don't want the next task to see. See [Release a browser](/browsers/pools/overview#release-a-browser).

## Check on the pool

`available_count` tells you how many browsers are ready to be acquired right now, and `acquired_count` how many are in use.

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  const pool = await kernel.browserPools.retrieve('checkout-pool');

  console.log(pool.available_count, pool.acquired_count);
  ```

  ```python Python theme={null}
  pool = kernel.browser_pools.retrieve("checkout-pool")

  print(pool.available_count, pool.acquired_count)
  ```

  ```go Go theme={null}
  pool, err := client.BrowserPools.Get(ctx, "checkout-pool")
  if err != nil {
  	panic(err)
  }

  fmt.Println(pool.AvailableCount, pool.AcquiredCount)
  ```

  ```bash CLI theme={null}
  kernel browser-pools get checkout-pool
  ```
</CodeGroup>

Watch this number to size the pool. If `available_count` regularly hits 0, your tasks are queueing behind `acquire` and the pool needs to be bigger. If it stays above 30–40% of the pool size under normal load, you can shrink the pool and free up concurrency for other work. Target 10–20% available during typical load.

Resize at any time with `update`; there's no need to tear the pool down and rebuild it. Added capacity warms up at the pool's fill rate, so raise the size before a traffic peak rather than during it:

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  await kernel.browserPools.update('checkout-pool', { size: 40 });
  ```

  ```python Python theme={null}
  kernel.browser_pools.update("checkout-pool", size=40)
  ```

  ```go Go theme={null}
  if _, err := client.BrowserPools.Update(ctx, "checkout-pool", kernel.BrowserPoolUpdateParams{
  	Size: 40,
  }); err != nil {
  	panic(err)
  }
  ```

  ```bash CLI theme={null}
  kernel browser-pools update checkout-pool --size 40
  ```
</CodeGroup>

## Before you go to production

Three things behave differently from on-demand browsers:

* **A pool's `timeout_seconds` only runs while a browser is acquired.** Browsers wait in the pool indefinitely; the clock starts when you acquire one and only counts idle time — no CDP connection, no live view, no in-flight computer controls request. A task that runs for 20 minutes with an active CDP connection won't be killed by a 10-minute timeout. See [Timeout behavior](/browsers/pools/overview#timeout-behavior).
* **Updating a pool doesn't reconfigure browsers that already exist.** `update` changes the configuration used for browsers created after it. Pass `discard_all_idle: true` to rebuild the idle ones too; acquired browsers keep their old configuration even then. See [Update a pool](/browsers/pools/overview#update-a-pool).
* **A profile set on the pool is read-only.** Every browser in the pool shares it, so `save_changes` doesn't apply at the pool level. To give each of your users their own persisted login state, attach the profile after you acquire the browser — see [Per-user profiles with pools](/browsers/pools/overview#per-user-profiles-with-pools).

## What's next

<Columns cols={2}>
  <Card title="Overview" href="/browsers/pools/overview">
    The full pool API — configuration options, profile refresh, flushing, and deletion.
  </Card>

  <Card title="Scale" href="/introduction/scale">
    Architecture patterns for high concurrency, including pools behind a task queue.
  </Card>

  <Card title="Custom Chrome Policies" href="/browsers/pools/policy-json">
    Apply Chrome enterprise policies — pop-up handling, download behavior, homepages — to every browser in a pool.
  </Card>

  <Card title="FAQ" href="/browsers/pools/faq">
    Sizing, fill rate, reuse semantics, and debugging pooled browsers in production.
  </Card>
</Columns>
