---
title: "Run browser checks on a browser pool | Grafana Cloud documentation"
description: "Run Synthetic Monitoring private probe browser checks on a separate fleet of browser instances, on Kubernetes, Helm, or other environments."
---

> For a curated documentation index, see [llms.txt](/llms.txt). For the complete documentation index, see [llms-full.txt](/llms-full.txt).

# Run browser checks on a browser pool

> Caution
> 
> The browser pool is an [experimental](https://grafana.com/docs/release-life-cycle/) feature. Configuration flags and behavior might change in future releases.

By default, the agent starts a Chromium process for each browser check, on the same host and with the same resources as every other check it runs. With a browser pool, the agent instead borrows a browser session from a separate fleet of browser instances. Each instance runs [`crocochrome`](https://github.com/grafana/crocochrome), a Chromium supervisor: for each session, it starts a Chromium browser, and it stops the browser and removes its data when the session ends. Each instance runs one session at a time.

## Before you begin

The browser pool requires:

- `crocochrome` version `v0.13.0` or later, or `v0.14.0` or later for the unprivileged image.
- k6 checks enabled on the agent. They’re enabled by default, so don’t set `-disable-k6`.
- The agent’s built-in k6 runner. You can’t use the browser pool with a remote k6 runner set with `-k6-uri`.

## When to use a browser pool

Consider a browser pool when a probe runs many browser checks:

- **Resource isolation:** Each browser session gets the dedicated CPU and memory of its instance, instead of competing inside the agent with protocol and scripted checks. If Chromium runs out of memory, it affects only that instance, not the agent.
- **Independent scaling:** You size and scale the browser fleet separately from the agents.
- **Smaller agents:** The agents no longer run Chromium, so they don’t need the `-browser` image.

If a probe runs only a few browser checks, use the `-browser` image instead. It’s simpler to operate and needs fewer privileges than the hardened browser pool image. Refer to [Browser pool security considerations](https://grafana.com/docs/grafana-cloud/observe-and-act/testing/synthetic-monitoring/set-up/set-up-private-probes/browser-pool/#browser-pool-security-considerations).

## How the browser pool works

For each browser check run, the agent uses a free instance and releases it when the run ends. Several agents, for example the agents in a [cluster](https://grafana.com/docs/grafana-cloud/observe-and-act/testing/synthetic-monitoring/set-up/set-up-private-probes/clustering/), can share the same fleet.

If no instance becomes free within a short wait, the check run fails. The agent doesn’t fall back to a local Chromium.

## Size the browser pool and the agents

- **Browser pool instances:** Run at least as many instances as the peak number of browser checks running at the same time. Browser checks with long frequencies, such as hourly checks, tend to run in bursts. Size the fleet for those bursts.
- **Agents:** With Chromium running on the browser pool, a few agents can run many checks of every type, so keep the number of agents small. If you need the probe to keep running when an agent fails, run a [cluster](https://grafana.com/docs/grafana-cloud/observe-and-act/testing/synthetic-monitoring/set-up/set-up-private-probes/clustering/) of at least two agents.

To size each instance, use the browser check figures in [Calculate resource requirements](https://grafana.com/docs/grafana-cloud/observe-and-act/testing/synthetic-monitoring/set-up/set-up-private-probes/network-and-resource-requirements/#calculate-resource-requirements). Each instance runs one session at a time.

## Browser pool security considerations

`crocochrome` is available as two container images:

- **Hardened (default):** The supervisor runs Chromium as a separate user, so a compromised browser can’t interfere with the supervisor. To do this, the container needs privilege escalation and a few added Linux capabilities. It meets the Pod Security `baseline` profile, but not `restricted`.
- **Unprivileged:** Images tagged `<version>-unprivileged`. The container needs no added capabilities or privilege escalation, and can run with an arbitrary user ID. It meets the `restricted` profile. Chromium runs as the same user as the supervisor, so a compromised browser can interfere with the supervisor and with later browser sessions on the same instance.

Use the hardened image unless your platform doesn’t allow it, for example because it enforces the `restricted` profile, a policy engine such as Kyverno or OPA Gatekeeper blocks added capabilities, or it runs on OpenShift. Only use the unprivileged image when you trust both the check scripts and the content of the pages they load, including third-party scripts.

For the security context that each image requires and the trade-offs in detail, refer to [Choosing an image](https://github.com/grafana/crocochrome/blob/main/doc/capabilities.md#choosing-an-image) in the `crocochrome` repository.

> Warning
> 
> The browser pool instances don’t authenticate requests. Anyone who can reach an instance can start browser sessions in your network. Only allow traffic to the instances from the agents, for example with a Kubernetes NetworkPolicy.

## Configure the browser pool on Kubernetes

These steps extend [Deployment with Kubernetes](https://grafana.com/docs/grafana-cloud/observe-and-act/testing/synthetic-monitoring/set-up/set-up-private-probes/set-up/#deployment-with-kubernetes) and work for both the single agent and the [cluster](https://grafana.com/docs/grafana-cloud/observe-and-act/testing/synthetic-monitoring/set-up/set-up-private-probes/clustering/#configure-clustering-on-kubernetes) examples. They use the browser pool files from the [Kubernetes example](https://github.com/grafana/synthetic-monitoring-agent/tree/main/examples/kubernetes) in the agent GitHub repository.

1. Get the browser pool deployment files.
   
   Bash ![Copy code to clipboard](https://grafana.com/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```bash
   wget https://raw.githubusercontent.com/grafana/synthetic-monitoring-agent/main/examples/kubernetes/crocochrome-service.yaml
   wget https://raw.githubusercontent.com/grafana/synthetic-monitoring-agent/main/examples/kubernetes/crocochrome-deployment.yaml
   ```
   
   In these files:
   
   - The `crocochrome` Service is headless, so its name resolves to the IP address of every instance. The agent must reach each instance individually. Don’t use a regular `ClusterIP` Service.
   - `terminationGracePeriodSeconds: 320` gives an instance time to finish its active session before it stops. A session can last up to 5 minutes.
   - The `securityContext` allows privilege escalation and adds the capabilities that the hardened image needs to run Chromium as a separate user. Don’t change these settings unless you switch to the unprivileged image. Refer to [Browser pool security considerations](https://grafana.com/docs/grafana-cloud/observe-and-act/testing/synthetic-monitoring/set-up/set-up-private-probes/browser-pool/#browser-pool-security-considerations).
   - Chromium writes temporary files to an in-memory volume mounted at `/chromium-tmp`, which counts toward the memory limit.
2. Deploy the browser pool.
   
   Bash ![Copy code to clipboard](https://grafana.com/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```bash
   kubectl apply -f crocochrome-service.yaml
   kubectl apply -f crocochrome-deployment.yaml
   ```
3. In `deployment.yaml` or `cluster-deployment.yaml`, uncomment the browser pool arguments:
   
   YAML ![Copy code to clipboard](https://grafana.com/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```yaml
   - --browser-pool-enabled
   - --browser-pool-addresses=crocochrome.synthetic-monitoring.svc.cluster.local:8080
   ```
   
   Then apply the agent manifest again, for example:
   
   Bash ![Copy code to clipboard](https://grafana.com/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```bash
   kubectl apply -f deployment.yaml
   ```
4. Verify that the agent uses the pool. The agent logs a warning that the browser pool is experimental when it starts, and the `sm_agent_browser_pool_instances` metric reports the instances it found. Refer to [Monitor the browser pool](https://grafana.com/docs/grafana-cloud/observe-and-act/testing/synthetic-monitoring/set-up/set-up-private-probes/browser-pool/#monitor-the-browser-pool).

To change the size of the fleet, scale the `crocochrome` Deployment. The agents pick up the change within 15 seconds.

## Configure the browser pool with Helm

If you deploy the agent with the [Helm chart](https://grafana.com/docs/grafana-cloud/observe-and-act/testing/synthetic-monitoring/set-up/set-up-private-probes/set-up/#deploy-the-agent-with-helm), the chart can deploy the browser pool and point the agents to it. Enable the browser pool in your values file:

YAML ![Copy code to clipboard](https://grafana.com/media/images/icons/icon-copy-small-2.svg) Copy

```yaml
browserPool:
  enabled: true
  crocochrome:
    # Each instance runs one browser session at a time.
    replicas: 3
    # Set to true to use the unprivileged image.
    unprivileged: false
```

Then upgrade the release:

Bash ![Copy code to clipboard](https://grafana.com/media/images/icons/icon-copy-small-2.svg) Copy

```bash
helm upgrade sm-agent oci://ghcr.io/grafana/helm-charts/synthetic-monitoring-agent \
  -n synthetic-monitoring -f values.yaml
```

The chart sets the image and the security context that the selected image requires. Refer to [Browser pool security considerations](https://grafana.com/docs/grafana-cloud/observe-and-act/testing/synthetic-monitoring/set-up/set-up-private-probes/browser-pool/#browser-pool-security-considerations) before you set `browserPool.crocochrome.unprivileged`. With the browser pool, the agents can use the standard image.

Optionally, the chart can also deploy a NetworkPolicy that only allows the agents to reach the instances. If you enable it and want to scrape the instances’ metrics, which use the same port, add your Prometheus Pods to `browserPool.crocochrome.networkPolicy.extraIngressFrom`. Refer to the chart [README](https://github.com/grafana/synthetic-monitoring-agent/tree/main/charts/synthetic-monitoring-agent).

## Configure the browser pool in other environments

You can run the browser pool anywhere the agents can reach it over HTTP, for example as `crocochrome` containers with Docker. Set `-browser-pool-addresses` to the address of each instance, or to a DNS name that resolves to all of them.

## Browser pool flags reference

Expand table

| Flag                      | Default | Description                                                                                                                                                                                                                                                                                                         |
|---------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `-browser-pool-enabled`   | `false` | Runs browser checks on the browser pool instead of a local Chromium. Requires `-browser-pool-addresses`.                                                                                                                                                                                                            |
| `-browser-pool-addresses` | `""`    | Comma-separated list of browser pool instances. Uses the same format as [cluster join addresses](https://grafana.com/docs/grafana-cloud/observe-and-act/testing/synthetic-monitoring/set-up/set-up-private-probes/clustering/#join-address-formats). The port defaults to `8080`. Requires `-browser-pool-enabled`. |

The agent doesn’t start if you set only one of these flags. If you disable k6 checks with `-disable-k6`, the agent logs a warning and ignores the browser pool.

## Monitor the browser pool

When the browser pool is enabled, each agent exposes the following metrics. Like the rest of the browser pool, these metrics might change while the feature is experimental.

Expand table

| Metric                                           | Type      | Description                                                                                                 |
|--------------------------------------------------|-----------|-------------------------------------------------------------------------------------------------------------|
| `sm_agent_browser_pool_instances`                | Gauge     | Number of instances the agent knows about, by `state`: `free`, or `busy` with a session held by this agent. |
| `sm_agent_browser_pool_acquires_total`           | Counter   | Number of session acquisitions, by `result`: `success`, `exhausted`, or `error`.                            |
| `sm_agent_browser_pool_acquire_duration_seconds` | Histogram | Time spent acquiring a session.                                                                             |
| `sm_agent_browser_pool_probes_total`             | Counter   | Number of attempts on an instance, by `result`: `acquired`, `busy`, `draining`, or `error`.                 |
| `sm_agent_browser_pool_releases_total`           | Counter   | Number of session releases, by `result`: `ok` or `error`.                                                   |

Each instance also exposes metrics on port `8080` at `/metrics`, including:

Expand table

| Metric                                     | Type      | Description                                                                                                                                                                                               |
|--------------------------------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `sm_crocochrome_session_active`            | Gauge     | `1` while the instance runs a browser session, `0` otherwise.                                                                                                                                             |
| `sm_crocochrome_sessions_created_total`    | Counter   | Number of browser sessions the instance started.                                                                                                                                                          |
| `sm_crocochrome_sessions_terminated_total` | Counter   | Number of browser sessions that ended, by `reason`: `deleted` when the agent released the session, `timeout` when the session reached the 5-minute limit, or `replaced` when another session replaced it. |
| `sm_crocochrome_session_duration_seconds`  | Histogram | How long each browser session lasted.                                                                                                                                                                     |
| `sm_crocochrome_chromium_executions_total` | Counter   | Number of Chromium processes that ended, by `state`: `finished` when the session ended normally, or `failed` when Chromium exited with an unexpected error.                                               |
| `sm_crocochrome_chromium_resource_usage`   | Histogram | Maximum memory used by each Chromium process, by `resource`: `rss`.                                                                                                                                       |
| `sm_crocochrome_chromium_oom_kills_total`  | Counter   | Number of times the kernel stopped a process in the container because it ran out of memory during a session.                                                                                              |
| `sm_crocochrome_info`                      | Gauge     | Build information, with the `crocochrome` version in the `version` label. Always `1`.                                                                                                                     |

Because each instance runs one session at a time, `avg(sm_crocochrome_session_active)` across instances is the share of the fleet in use, from `0` to `1`. Use it to decide when to add or remove instances, or as the input of an autoscaling policy for the `crocochrome` Deployment, for example with a HorizontalPodAutoscaler on an external metric or with KEDA.

With the Helm chart, set `browserPool.crocochrome.replicas: null`, so that `helm upgrade` doesn’t reset the number of instances, and add the autoscaler with `extraObjects`.

Keep some headroom: when every instance is busy, new browser check runs wait and can fail.

To estimate how many instances you need, multiply the rate of new sessions across the fleet, `sum(rate(sm_crocochrome_sessions_created_total[5m]))`, by the average session duration from `sm_crocochrome_session_duration_seconds`. The result is the average number of busy instances. Add headroom for bursts on top of it.

In a healthy pool:

- `sm_agent_browser_pool_acquires_total{result="exhausted"}` doesn’t increase. If it does, browser checks are failing because no instance was free. Add instances.
- `sm_agent_browser_pool_probes_total` doesn’t grow much faster than `sm_agent_browser_pool_acquires_total`. A high ratio means agents often try busy instances. Add instances or reduce the number of agents.
- `sm_agent_browser_pool_releases_total{result="error"}` doesn’t increase. Sessions that aren’t released stay busy until the instance ends them after 5 minutes.
- `sm_crocochrome_sessions_terminated_total{reason="timeout"}` doesn’t increase. If it does, sessions aren’t released, and their instances stay busy until the 5-minute limit.
- `sm_crocochrome_sessions_terminated_total{reason="replaced"}` doesn’t increase. Agents never replace sessions, so if it increases, something other than the agents is using the instances.
- `sm_crocochrome_chromium_executions_total{state="failed"}` and `sm_crocochrome_chromium_oom_kills_total` don’t increase. Out-of-memory kills mean the memory limit of the instances is too low. Use `sm_crocochrome_chromium_resource_usage` to see how much memory Chromium uses.
