← Blog

BLOG

Browser Use Got: What You Actually Get in Production

Browser use got you a working demo. Here is what production adds: hosted Chromium, CDP access, persistent profiles, and metered browser-hour billing.

October 15, 20268 min readRemote Browser

# Browser Use Got: What You Actually Get in Production

"Browser use got" is the search you type after the demo works and the deployment does not. The local script drove Chrome, clicked the right buttons, and returned the data. Then you moved it to a server, ran it twice, and watched it fail on a login wall, a missing profile, or a headless flag that behaved differently from your laptop. What you got was a proof of concept. What you need is a runtime.

This post is about the gap between those two things. It covers what browser-use actually gives you out of the box, what breaks when you leave localhost, and which production criteria matter when you pick a hosted runtime. If you want the broader architecture first, read Remote Browser for AI agents before continuing here.

What browser-use gives you, and what it does not

Browser-use is a Python library that lets an LLM drive a browser. You give it a task in natural language, it observes the DOM, decides on actions, and executes them through Playwright or a similar driver. It is genuinely good at the agent loop: perception, planning, action selection, retry.

What it does not give you is infrastructure. Specifically:

  • A browser that lives somewhere other than your machine. Browser-use launches a local Chromium by default. On a laptop that is fine. On a container with 512 MB of RAM and no display server, it is a problem.
  • Session persistence across runs. Each launch starts clean unless you wire up a user data directory yourself, and that directory has to survive redeploys.
  • A stable network identity. Your agent's IP is your server's IP. Sites that rate-limit or block datacenter ranges will block you.
  • Observability. When a run fails at step 14 of 30, you need to see what the agent saw. Screenshots in a log file are not the same as a live session you can attach to.
  • Concurrency control. Ten parallel agents on one box means ten Chromium processes competing for CPU and memory. Nothing in the library manages that for you.

None of this is a criticism of browser-use. It is a library, not a platform. The mistake is assuming the library is the whole stack.

The three things that break first

In practice, teams hit the same three walls in the same order.

Wall one: the browser will not start. Headless Chromium needs shared libraries that minimal container images do not ship. You add libnss3, libatk, libgbm, and a dozen others, rebuild, and it works — until the base image updates and something shifts. This is a maintenance tax, not a one-time fix.

Wall two: the session does not persist. Your agent logs into a dashboard on Monday. On Tuesday it is logged out, because the container was recycled and the user data directory went with it. Persistent profiles are the fix, but implementing them correctly means separating browser state from compute state, which is an architectural decision, not a config flag.

Wall three: the site fights back. Bot detection looks at TLS fingerprints, IP reputation, navigator properties, and behavioral signals. A default headless Chromium on a cloud IP fails most of these checks. You can mitigate some of it — configurable proxy settings, realistic viewports, adjusted browser surfaces — but doing it well is its own project.

Each wall is solvable. The question is whether solving them is your product or a distraction from it.

What a hosted runtime changes

A hosted browser runtime moves the browser off your infrastructure and exposes it over a connection protocol. Remote Browser runs hosted Chromium sessions that you drive over CDP, with Playwright, Puppeteer, or Selenium on the client side. Your agent code stays where it is. The browser moves.

Concretely, that means:

  • Sessions are created via API, not launched as subprocesses. You get a connection URL and a session ID.
  • Profiles persist across sessions when you ask for them, so cookies and local storage survive.
  • The live viewer lets you watch a session in real time and attach to a stuck run instead of guessing from logs.
  • Proxy and browser settings are configurable at the session level, so you can route through a specific region or adjust the browser surface without rebuilding an image.
  • Usage is metered per browser-hour, which maps cost to actual work rather than to provisioned capacity.

The trade-off is real: you are adding a network hop and a vendor dependency. For a script that runs once a day on your laptop, that is overkill. For anything running in production, the hop is cheaper than the alternative.

Comparison: local browser-use vs. hosted runtime

DimensionLocal browser-useHosted runtime (Remote Browser)
Browser locationYour machine or containerManaged cloud Chromium
Setup costInstall Playwright + system depsAPI key and a connection URL
Session persistenceManual user data dirPersistent profiles via API
Network identityYour server's IPConfigurable proxy settings
DebuggingLogs and screenshotsLive viewer, attach mid-session
ScalingOne process per agent, manualSession-level isolation, API-driven
Cost modelYour compute, always onPer browser-hour, pay for use
Failure modeContainer OOM, missing libsNetwork hop, vendor availability

The last row matters. Neither option is failure-free. Local failures tend to be environmental and repetitive; hosted failures tend to be transient and visible. Most teams find the second category easier to operate.

Wiring it up: Playwright over CDP

The connection path is the part people get wrong. You do not launch a browser. You connect to one that already exists.

import { chromium, Browser, Page } from 'playwright';

interface SessionResponse {
  sessionId: string;
  cdpUrl: string;
}

async function createSession(): Promise<SessionResponse> {
  const res = await fetch('https://api.remote-browser.dev/v1/sessions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.REMOTE_BROWSER_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      profile: 'checkout-agent',
      persistProfile: true,
      proxy: { country: 'us' },
    }),
  });

  if (!res.ok) {
    throw new Error(`Session create failed: ${res.status} ${await res.text()}`);
  }
  return res.json() as Promise<SessionResponse>;
}

async function runTask(): Promise<void> {
  const { sessionId, cdpUrl } = await createSession();
  let browser: Browser | undefined;

  try {
    browser = await chromium.connectOverCDP(cdpUrl);
    const context = browser.contexts()[0] ?? await browser.newContext();
    const page: Page = context.pages()[0] ?? await context.newPage();

    await page.goto('https://example.com/dashboard', {
      waitUntil: 'domcontentloaded',
      timeout: 30_000,
    });

    // Your agent loop goes here. The browser is remote; the logic is local.
    await page.getByRole('button', { name: 'Export' }).click();
    await page.waitForEvent('download');
  } finally {
    await browser?.close();
    await fetch(`https://api.remote-browser.dev/v1/sessions/${sessionId}`, {
      method: 'DELETE',
      headers: { 'Authorization': `Bearer ${process.env.REMOTE_BROWSER_KEY}` },
    });
  }
}

Three details worth noting. First, connectOverCDP returns a browser whose contexts already exist — you reuse them rather than creating new ones, or you lose the profile state. Second, the finally block closes the session explicitly; leaked sessions are the most common source of surprise cost. Third, the profile name is the identity. Same name, same cookies. Different name, clean slate.

The CDP layer underneath is documented in the Chrome DevTools Protocol reference, and Playwright's own guidance on connecting to existing browsers is worth reading before you design your session lifecycle.

Production criteria that actually matter

When you evaluate a hosted runtime, these are the questions that separate usable from unusable.

Session isolation. Can two agents run concurrently without sharing cookies, storage, or network identity? If the answer is "usually," it is no.

Profile semantics. Are profiles named, persistent, and independent of session lifetime? A profile that dies with the session is not a profile.

Connection protocol. CDP is the baseline. If a runtime only offers a proprietary SDK, you are locked into its client library and its upgrade cadence.

Observability. Can you watch a live session? Can you retrieve a session recording after the fact? Debugging an agent without seeing the page is guesswork.

Failure semantics. What happens when a session dies mid-task? Does the API tell you, or do you discover it via timeout?

Cost shape. Per-browser-hour billing means idle sessions cost money. Check whether idle time is metered and how sessions are reaped. Current rates and limits are on the pricing page.

Data handling. Where does session data live, how long is it retained, and can you disable retention? For anything touching user accounts, this is a procurement question, not a technical one.

Where browser-use still fits

A hosted runtime does not replace browser-use. It gives it somewhere to run.

The pattern that works: keep browser-use as the agent loop, point its browser at a remote CDP endpoint instead of a local launch, and let the runtime handle profiles, proxies, and lifecycle. Your prompt engineering and tool definitions stay portable. Your infrastructure stops being a second job.

If you are still deciding between running Chromium yourself and connecting to a managed one, Remote Browser online walks through the operational differences. And if your agents need to drive a browser that a human is also watching, remote control browser covers the shared-session case.

The short version

Browser use got you an agent that works on your machine. Production needs a browser that works on someone else's, survives restarts, presents a consistent identity, and can be watched when it fails. That is a runtime problem, and it is separable from the agent problem.

The setup is not complicated: create a session, connect over CDP, reuse the existing context, close the session in a finally block. The hard part is choosing a runtime whose session semantics, observability, and cost model match how you actually operate. Start with the documentation to see the session API surface, then run one real task end to end before you commit to anything.