Repository files navigation

BaseLayer

BaseLayer is an open-source browser hosting control plane and host runtime for running Chromium sessions with Docker and Firecracker-backed isolation.

It was built to test how fast a self-hosted browser provider can get on the BrowserArena lifecycle methodology:

session create + CDP connect + page.goto + session release

Launch thread: BaseLayer on X

Results

These are self-hosted BrowserArena-methodology runs.

Latest Methodology Snapshot

BrowserArena changed its default fairness URL to example.com on 2026-05-05. Using that current target, BaseLayer's latest sequential self-hosted snapshot was measured on 2026-06-01. The competitor positioning referenced in docs/browserarena-results.md uses BrowserArena c1 leaderboard numbers checked on 2026-06-01.

In the BrowserArena c1 leaderboard snapshot checked on 2026-06-01, this places BaseLayer above the top listed provider by sequential p50 lifecycle latency. For leaderboard-style comparisons, BaseLayer pools all successful iterations, computes the p50 of each lifecycle stage, then sums those stage p50s. The raw per-iteration total_ms.p50 is retained in artifacts for auditing, but it is not the headline comparison number.

Runtime / profileRun dateTopologyRunsSuccessCreate p50Connect p50Goto p50Release p50Lifecycle p50
baselayer-firecracker-headless-shell2026-06-01AWS t3.micro runner -> AWS m5zn.metal host, us-east-2, BrowserArena stages, example.com target, c1 x100, slowest clean run from 5 repeats100100/10074.8 ms54.4 ms82.6 ms11.8 ms223.6 ms

These are self-hosted, sequential benchmark results from five c1 x100 runs on 2026-06-01. The public headline uses the slowest clean 100/100 run from that batch, rounded to 224 ms, rather than the fastest or pooled value. The five-run batch ranged from 190.8 ms to 223.6 ms; pooled successful iterations summed to 216.3 ms across 498/500 successes. These are not official BrowserArena leaderboard submissions. The runs are reproducible, and this repo includes the benchmark harness used, run artifacts, and replication notes.

The latest concurrent c10 x10 run remains under review while the scheduler and demo harness are being hardened. The current public headline is the conservative sequential run above.

The one-shot self-hosted BrowserArena runner now provisions the AWS t3.micro runner and m5zn.metal provider host, bootstraps BaseLayer, waits for warm-pool readiness, runs BrowserArena, pulls artifacts, and tears down resources. See docs/BENCHMARKS.md for the exact command and benchmark knobs.

The current self-host runner path was revalidated on 2026-06-06 with https://example.com, c1 x100, and c10 x100. Three fresh-metal repeats were clean for both c1 and c10. Immediate back-to-back same-host repeats kept c1 stable and kept c10 p50 in range, but the third c10 repeat reached 98/100, so same-host density reruns should still be treated as reliability stress tests.

See docs/browserarena-results.md for the replication notes and caveats.

Google-Target Snapshot

The headline BaseLayer rows below used the BrowserArena hello-browser methodology as it existed at the time of the runs: page.goto against https://google.com/ with waitUntil: "domcontentloaded".

BrowserArena changed its default fairness URL to example.com on 2026-05-05. Current BrowserArena leaderboard numbers are therefore much lower and are not directly comparable with these Google-target rows.

Runtime / profileRun dateTopologyRunsSuccessCreate p50Connect p50Goto p50Release p50Lifecycle p50
baselayer-firecracker-headless-shell-cdp-warm-density2026-04-23AWS t3.micro runner -> AWS m5zn.metal provider, us-east-2, BrowserArena hello-browser, Google target10099/10093 ms56 ms618 ms5 ms769 ms
baselayer-firecracker-headless-shell-cdp-warm-density2026-04-24AWS t3.micro runner -> AWS m5zn.metal provider, us-east-1, BrowserArena hello-browser, Google target10099/10087 ms102 ms555 ms5 ms749 ms
baselayer-firecracker-full-chromium2026-05-07AWS t3.micro runner -> AWS m5zn.metal provider, public /v1, BrowserArena hello-browser, Google target100100/10096 ms61 ms619 ms10 ms784 ms
baselayer-firecracker-headless-shell2026-04-12AWS m5zn.metal, concurrent BrowserArena-style waves, Google targetc2424/24448 ms104 ms3788 ms9 ms4349 ms

See docs/browserarena-results.md and docs/current-best-profiles.md for replication notes and historical context.

What This Repo Contains

  • control-plane HTTP API for browser session lifecycle management
  • node-agent runtime for launching browser sessions
  • Docker-backed per-session runtime support
  • Firecracker snapshot/restore tooling for chromium-headless-shell
  • benchmark harnesses aligned to BrowserArena stage names
  • public result notes and profile documentation

BaseLayer is lower-level infrastructure for browser hosting experiments. It is not a managed browser automation product.

Replicate The Self-Hosted Methodology

The most direct in-repo replication path is the provider /v1 benchmark harness. It uses the same lifecycle stages as BrowserArena:

  • session_creation_ms
  • session_connect_ms
  • page_goto_ms
  • session_release_ms
  • total_ms

1. Prepare A Linux/KVM Provider Host

Use a Linux host with KVM enabled. The published AWS runs used m5zn.metal.

git clone https://github.com/Lasdw6/baselayer.git baselayer
cd baselayer
npm ci
npm run build
./scripts/bench/bootstrap-firecracker-linux.sh
sudo ./scripts/firecracker/build-headless-shell-rootfs.sh

Start the control plane and Firecracker node agent:

export CONTROL_PLANE_PORT=3000
export CONTROL_PLANE_ASYNC_SESSION_DELETE=1
export CONTROL_PLANE_ASYNC_SESSION_DELETE_DELAY_MS=120000
export CONTROL_PLANE_URL="http://127.0.0.1:3000"export NODE_AGENT_PORT=4000
export NODE_AGENT_PUBLIC_HOST="127.0.0.1"export NODE_AGENT_MODE="firecracker"export BASELAYER_SUPPORTED_RUNTIME_PROFILES="baselayer-firecracker-headless-shell"export MAX_SESSIONS=128
export FIRECRACKER_KERNEL_PATH="$PWD/artifacts/firecracker/vmlinux"export FIRECRACKER_ROOTFS_PATH="$PWD/artifacts/firecracker/rootfs.ext4"export FIRECRACKER_SNAPSHOT_DIR="$PWD/data/firecracker/snapshots"export FIRECRACKER_ALLOW_AUTO_SNAPSHOT=1
export FIRECRACKER_MAX_MICROVM_COUNT=128
export FIRECRACKER_NETWORK_POOL_SIZE=128
export FIRECRACKER_READY_SETTLE_MS=50
export FIRECRACKER_RESTORE_RETRIES=2
npm run dev:api
# in another shell:
npm run dev:agent

For public network tests, put the API behind your own firewall/reverse proxy and enable API-key auth. If you run a split public/internal deployment, set CONTROL_PLANE_PUBLIC_V1_ONLY=1 only on the public listener; the node agent still needs internal registration routes. See Public API Safety.

2. Run The Built-In BrowserArena-Stage Harness

From the provider host or a same-region runner:

export BASELAYER_API_URL="http://<provider-host>:3000/v1"export BASELAYER_RUNTIME_PROFILE="baselayer-firecracker-headless-shell"export BENCH_RUNS=100
export BENCH_CONCURRENCY=1
export BENCH_BROWSERARENA_PAGE_URL="https://example.com/"export BENCH_PAGE_GOTO_WAIT_UNTIL="domcontentloaded"export BENCH_CONNECT_RETRY_BUDGET_MS=15000
export BENCH_OUT="$PWD/data/benchmarks/provider-api-100.json"
npm run bench:provider-api

The output JSON includes p50/p95/p99 for each BrowserArena-style stage plus the raw per-iteration rows.

The current c1 replication rows used that shape with BENCH_RUNS=100 and BENCH_CONCURRENCY=1, repeated five times.

export BENCH_RUNS=10
export BENCH_CONCURRENCY=10
export BENCH_OUT="$PWD/data/benchmarks/provider-api-example-c10x10.json"
npm run bench:provider-api

In this harness, BENCH_RUNS=10 at BENCH_CONCURRENCY=10 means 10 waves of 10 sessions, or 100 measured sessions total.

To reproduce the historical Google-target rows, use:

export BENCH_BROWSERARENA_PAGE_URL="https://google.com/"

3. Run With The BrowserArena Harness

The headline rows above were produced with the BrowserArena hello-browser lifecycle against a self-hosted BaseLayer provider endpoint. Use the same topology when comparing numbers:

  • runner and provider in the same AWS region
  • target: https://example.com for current BrowserArena methodology, or Google only when reproducing the historical snapshot above
  • wait condition: domcontentloaded
  • runs: 100
  • concurrency: 1
  • bounded provider-side admission backpressure is allowed only when it is counted inside session_creation_ms
  • async delete enabled for latency-style runs

Shape:

BASELAYER_BASE_URL="http://<provider-host>:3000/v1" \
npm run bench -- --provider=baselayer --benchmark=hello-browser --runs=100 --concurrency=1

Use a short smoke first:

BENCH_RUNS=1 npm run bench:provider-api
BENCH_RUNS=5 npm run bench:provider-api
BENCH_RUNS=100 npm run bench:provider-api

Only compare a 100-run if the smoke runs are clean.

Requirements

  • Node.js 22+
  • npm
  • Docker for the container runtime path
  • Linux/KVM for Firecracker proof and benchmark paths

Windows and macOS are fine for editing, building TypeScript, and running unit tests that do not require Docker/KVM.

Local Quick Start

Install dependencies:

npm install

Build and test:

npm run build
npm test

Build the runtime image:

docker build -f Dockerfile.runtime -t baselayer-runtime:local .

Start the control plane:

npm run dev:api

Start a local node agent in managed mode:

export CONTROL_PLANE_URL="http://127.0.0.1:3000"export NODE_AGENT_PORT="4000"export NODE_AGENT_PUBLIC_HOST="127.0.0.1"export NODE_AGENT_MODE="managed"export RUNTIME_IMAGE="baselayer-runtime:local"
npm run dev:agent

Create a session:

curl -X POST http://127.0.0.1:3000/v1/sessions \
-H "content-type: application/json" \
-d '{"browser":"chromium","keepAlive":false,"timeoutSec":900,"idleTimeoutSec":120}'

Public API Safety

The control plane is designed for local and lab use by default. Before exposing /v1 outside a trusted network:

  • set CONTROL_PLANE_PUBLIC_V1_ONLY=1
  • set CONTROL_PLANE_ENFORCE_PROVIDER_API_KEY_AUTH=1
  • provide API keys through CONTROL_PLANE_PROVIDER_API_KEY_CONFIG_PATH
  • bind services behind a firewall or reverse proxy

Example config files live in config.

Repository Layout

  • src/api - control plane, scheduler, API routes, store
  • src/node-agent - host runtime, Docker launcher, Firecracker integration
  • src/runtime - browser runtime container entrypoint
  • src/bench - benchmark harnesses and runtime experiments
  • scripts/firecracker - rootfs and Firecracker image helpers
  • scripts/bench - Linux bootstrap and benchmark helpers
  • docs - public architecture, benchmark, and experiment notes
  • test - unit and contract tests

Documentation

License

MIT. See LICENSE.

About

Browser hosting control plane and host runtime for running Chromium sessions

Resources

Contributing

Security policy

Stars

36 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

BaseLayer

BaseLayer is an open-source browser hosting control plane and host runtime for running Chromium sessions with Docker and Firecracker-backed isolation.

It was built to test how fast a self-hosted browser provider can get on the BrowserArena lifecycle methodology:

session create + CDP connect + page.goto + session release

Launch thread: BaseLayer on X

Results

These are self-hosted BrowserArena-methodology runs.

Latest Methodology Snapshot

BrowserArena changed its default fairness URL to example.com on 2026-05-05. Using that current target, BaseLayer's latest sequential self-hosted snapshot was measured on 2026-06-01. The competitor positioning referenced in docs/browserarena-results.md uses BrowserArena c1 leaderboard numbers checked on 2026-06-01.

In the BrowserArena c1 leaderboard snapshot checked on 2026-06-01, this places BaseLayer above the top listed provider by sequential p50 lifecycle latency. For leaderboard-style comparisons, BaseLayer pools all successful iterations, computes the p50 of each lifecycle stage, then sums those stage p50s. The raw per-iteration total_ms.p50 is retained in artifacts for auditing, but it is not the headline comparison number.

Runtime / profileRun dateTopologyRunsSuccessCreate p50Connect p50Goto p50Release p50Lifecycle p50
baselayer-firecracker-headless-shell2026-06-01AWS t3.micro runner -> AWS m5zn.metal host, us-east-2, BrowserArena stages, example.com target, c1 x100, slowest clean run from 5 repeats100100/10074.8 ms54.4 ms82.6 ms11.8 ms223.6 ms

These are self-hosted, sequential benchmark results from five c1 x100 runs on 2026-06-01. The public headline uses the slowest clean 100/100 run from that batch, rounded to 224 ms, rather than the fastest or pooled value. The five-run batch ranged from 190.8 ms to 223.6 ms; pooled successful iterations summed to 216.3 ms across 498/500 successes. These are not official BrowserArena leaderboard submissions. The runs are reproducible, and this repo includes the benchmark harness used, run artifacts, and replication notes.

The latest concurrent c10 x10 run remains under review while the scheduler and demo harness are being hardened. The current public headline is the conservative sequential run above.

The one-shot self-hosted BrowserArena runner now provisions the AWS t3.micro runner and m5zn.metal provider host, bootstraps BaseLayer, waits for warm-pool readiness, runs BrowserArena, pulls artifacts, and tears down resources. See docs/BENCHMARKS.md for the exact command and benchmark knobs.

The current self-host runner path was revalidated on 2026-06-06 with https://example.com, c1 x100, and c10 x100. Three fresh-metal repeats were clean for both c1 and c10. Immediate back-to-back same-host repeats kept c1 stable and kept c10 p50 in range, but the third c10 repeat reached 98/100, so same-host density reruns should still be treated as reliability stress tests.

See docs/browserarena-results.md for the replication notes and caveats.

Google-Target Snapshot

The headline BaseLayer rows below used the BrowserArena hello-browser methodology as it existed at the time of the runs: page.goto against https://google.com/ with waitUntil: "domcontentloaded".

BrowserArena changed its default fairness URL to example.com on 2026-05-05. Current BrowserArena leaderboard numbers are therefore much lower and are not directly comparable with these Google-target rows.

Runtime / profileRun dateTopologyRunsSuccessCreate p50Connect p50Goto p50Release p50Lifecycle p50
baselayer-firecracker-headless-shell-cdp-warm-density2026-04-23AWS t3.micro runner -> AWS m5zn.metal provider, us-east-2, BrowserArena hello-browser, Google target10099/10093 ms56 ms618 ms5 ms769 ms
baselayer-firecracker-headless-shell-cdp-warm-density2026-04-24AWS t3.micro runner -> AWS m5zn.metal provider, us-east-1, BrowserArena hello-browser, Google target10099/10087 ms102 ms555 ms5 ms749 ms
baselayer-firecracker-full-chromium2026-05-07AWS t3.micro runner -> AWS m5zn.metal provider, public /v1, BrowserArena hello-browser, Google target100100/10096 ms61 ms619 ms10 ms784 ms
baselayer-firecracker-headless-shell2026-04-12AWS m5zn.metal, concurrent BrowserArena-style waves, Google targetc2424/24448 ms104 ms3788 ms9 ms4349 ms

See docs/browserarena-results.md and docs/current-best-profiles.md for replication notes and historical context.

What This Repo Contains

  • control-plane HTTP API for browser session lifecycle management
  • node-agent runtime for launching browser sessions
  • Docker-backed per-session runtime support
  • Firecracker snapshot/restore tooling for chromium-headless-shell
  • benchmark harnesses aligned to BrowserArena stage names
  • public result notes and profile documentation

BaseLayer is lower-level infrastructure for browser hosting experiments. It is not a managed browser automation product.

Replicate The Self-Hosted Methodology

The most direct in-repo replication path is the provider /v1 benchmark harness. It uses the same lifecycle stages as BrowserArena:

  • session_creation_ms
  • session_connect_ms
  • page_goto_ms
  • session_release_ms
  • total_ms

1. Prepare A Linux/KVM Provider Host

Use a Linux host with KVM enabled. The published AWS runs used m5zn.metal.

git clone https://github.com/Lasdw6/baselayer.git baselayer
cd baselayer
npm ci
npm run build
./scripts/bench/bootstrap-firecracker-linux.sh
sudo ./scripts/firecracker/build-headless-shell-rootfs.sh

Start the control plane and Firecracker node agent:

export CONTROL_PLANE_PORT=3000
export CONTROL_PLANE_ASYNC_SESSION_DELETE=1
export CONTROL_PLANE_ASYNC_SESSION_DELETE_DELAY_MS=120000
export CONTROL_PLANE_URL="http://127.0.0.1:3000"export NODE_AGENT_PORT=4000
export NODE_AGENT_PUBLIC_HOST="127.0.0.1"export NODE_AGENT_MODE="firecracker"export BASELAYER_SUPPORTED_RUNTIME_PROFILES="baselayer-firecracker-headless-shell"export MAX_SESSIONS=128
export FIRECRACKER_KERNEL_PATH="$PWD/artifacts/firecracker/vmlinux"export FIRECRACKER_ROOTFS_PATH="$PWD/artifacts/firecracker/rootfs.ext4"export FIRECRACKER_SNAPSHOT_DIR="$PWD/data/firecracker/snapshots"export FIRECRACKER_ALLOW_AUTO_SNAPSHOT=1
export FIRECRACKER_MAX_MICROVM_COUNT=128
export FIRECRACKER_NETWORK_POOL_SIZE=128
export FIRECRACKER_READY_SETTLE_MS=50
export FIRECRACKER_RESTORE_RETRIES=2
npm run dev:api
# in another shell:
npm run dev:agent

For public network tests, put the API behind your own firewall/reverse proxy and enable API-key auth. If you run a split public/internal deployment, set CONTROL_PLANE_PUBLIC_V1_ONLY=1 only on the public listener; the node agent still needs internal registration routes. See Public API Safety.

2. Run The Built-In BrowserArena-Stage Harness

From the provider host or a same-region runner:

export BASELAYER_API_URL="http://<provider-host>:3000/v1"export BASELAYER_RUNTIME_PROFILE="baselayer-firecracker-headless-shell"export BENCH_RUNS=100
export BENCH_CONCURRENCY=1
export BENCH_BROWSERARENA_PAGE_URL="https://example.com/"export BENCH_PAGE_GOTO_WAIT_UNTIL="domcontentloaded"export BENCH_CONNECT_RETRY_BUDGET_MS=15000
export BENCH_OUT="$PWD/data/benchmarks/provider-api-100.json"
npm run bench:provider-api

The output JSON includes p50/p95/p99 for each BrowserArena-style stage plus the raw per-iteration rows.

The current c1 replication rows used that shape with BENCH_RUNS=100 and BENCH_CONCURRENCY=1, repeated five times.

export BENCH_RUNS=10
export BENCH_CONCURRENCY=10
export BENCH_OUT="$PWD/data/benchmarks/provider-api-example-c10x10.json"
npm run bench:provider-api

In this harness, BENCH_RUNS=10 at BENCH_CONCURRENCY=10 means 10 waves of 10 sessions, or 100 measured sessions total.

To reproduce the historical Google-target rows, use:

export BENCH_BROWSERARENA_PAGE_URL="https://google.com/"

3. Run With The BrowserArena Harness

The headline rows above were produced with the BrowserArena hello-browser lifecycle against a self-hosted BaseLayer provider endpoint. Use the same topology when comparing numbers:

  • runner and provider in the same AWS region
  • target: https://example.com for current BrowserArena methodology, or Google only when reproducing the historical snapshot above
  • wait condition: domcontentloaded
  • runs: 100
  • concurrency: 1
  • bounded provider-side admission backpressure is allowed only when it is counted inside session_creation_ms
  • async delete enabled for latency-style runs

Shape:

BASELAYER_BASE_URL="http://<provider-host>:3000/v1" \
npm run bench -- --provider=baselayer --benchmark=hello-browser --runs=100 --concurrency=1

Use a short smoke first:

BENCH_RUNS=1 npm run bench:provider-api
BENCH_RUNS=5 npm run bench:provider-api
BENCH_RUNS=100 npm run bench:provider-api

Only compare a 100-run if the smoke runs are clean.

Requirements

  • Node.js 22+
  • npm
  • Docker for the container runtime path
  • Linux/KVM for Firecracker proof and benchmark paths

Windows and macOS are fine for editing, building TypeScript, and running unit tests that do not require Docker/KVM.

Local Quick Start

Install dependencies:

npm install

Build and test:

npm run build
npm test

Build the runtime image:

docker build -f Dockerfile.runtime -t baselayer-runtime:local .

Start the control plane:

npm run dev:api

Start a local node agent in managed mode:

export CONTROL_PLANE_URL="http://127.0.0.1:3000"export NODE_AGENT_PORT="4000"export NODE_AGENT_PUBLIC_HOST="127.0.0.1"export NODE_AGENT_MODE="managed"export RUNTIME_IMAGE="baselayer-runtime:local"
npm run dev:agent

Create a session:

curl -X POST http://127.0.0.1:3000/v1/sessions \
-H "content-type: application/json" \
-d '{"browser":"chromium","keepAlive":false,"timeoutSec":900,"idleTimeoutSec":120}'

Public API Safety

The control plane is designed for local and lab use by default. Before exposing /v1 outside a trusted network:

  • set CONTROL_PLANE_PUBLIC_V1_ONLY=1
  • set CONTROL_PLANE_ENFORCE_PROVIDER_API_KEY_AUTH=1
  • provide API keys through CONTROL_PLANE_PROVIDER_API_KEY_CONFIG_PATH
  • bind services behind a firewall or reverse proxy

Example config files live in config.

Repository Layout

  • src/api - control plane, scheduler, API routes, store
  • src/node-agent - host runtime, Docker launcher, Firecracker integration
  • src/runtime - browser runtime container entrypoint
  • src/bench - benchmark harnesses and runtime experiments
  • scripts/firecracker - rootfs and Firecracker image helpers
  • scripts/bench - Linux bootstrap and benchmark helpers
  • docs - public architecture, benchmark, and experiment notes
  • test - unit and contract tests

Documentation

License

MIT. See LICENSE.

About

Browser hosting control plane and host runtime for running Chromium sessions

Resources

Contributing

Security policy

Stars

36 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

BaseLayer

BaseLayer is an open-source browser hosting control plane and host runtime for running Chromium sessions with Docker and Firecracker-backed isolation.

It was built to test how fast a self-hosted browser provider can get on the BrowserArena lifecycle methodology:

session create + CDP connect + page.goto + session release

Launch thread: BaseLayer on X

Results

These are self-hosted BrowserArena-methodology runs.

Latest Methodology Snapshot

BrowserArena changed its default fairness URL to example.com on 2026-05-05. Using that current target, BaseLayer's latest sequential self-hosted snapshot was measured on 2026-06-01. The competitor positioning referenced in docs/browserarena-results.md uses BrowserArena c1 leaderboard numbers checked on 2026-06-01.

In the BrowserArena c1 leaderboard snapshot checked on 2026-06-01, this places BaseLayer above the top listed provider by sequential p50 lifecycle latency. For leaderboard-style comparisons, BaseLayer pools all successful iterations, computes the p50 of each lifecycle stage, then sums those stage p50s. The raw per-iteration total_ms.p50 is retained in artifacts for auditing, but it is not the headline comparison number.

Runtime / profileRun dateTopologyRunsSuccessCreate p50Connect p50Goto p50Release p50Lifecycle p50
baselayer-firecracker-headless-shell2026-06-01AWS t3.micro runner -> AWS m5zn.metal host, us-east-2, BrowserArena stages, example.com target, c1 x100, slowest clean run from 5 repeats100100/10074.8 ms54.4 ms82.6 ms11.8 ms223.6 ms

These are self-hosted, sequential benchmark results from five c1 x100 runs on 2026-06-01. The public headline uses the slowest clean 100/100 run from that batch, rounded to 224 ms, rather than the fastest or pooled value. The five-run batch ranged from 190.8 ms to 223.6 ms; pooled successful iterations summed to 216.3 ms across 498/500 successes. These are not official BrowserArena leaderboard submissions. The runs are reproducible, and this repo includes the benchmark harness used, run artifacts, and replication notes.

The latest concurrent c10 x10 run remains under review while the scheduler and demo harness are being hardened. The current public headline is the conservative sequential run above.

The one-shot self-hosted BrowserArena runner now provisions the AWS t3.micro runner and m5zn.metal provider host, bootstraps BaseLayer, waits for warm-pool readiness, runs BrowserArena, pulls artifacts, and tears down resources. See docs/BENCHMARKS.md for the exact command and benchmark knobs.

The current self-host runner path was revalidated on 2026-06-06 with https://example.com, c1 x100, and c10 x100. Three fresh-metal repeats were clean for both c1 and c10. Immediate back-to-back same-host repeats kept c1 stable and kept c10 p50 in range, but the third c10 repeat reached 98/100, so same-host density reruns should still be treated as reliability stress tests.

See docs/browserarena-results.md for the replication notes and caveats.

Google-Target Snapshot

The headline BaseLayer rows below used the BrowserArena hello-browser methodology as it existed at the time of the runs: page.goto against https://google.com/ with waitUntil: "domcontentloaded".

BrowserArena changed its default fairness URL to example.com on 2026-05-05. Current BrowserArena leaderboard numbers are therefore much lower and are not directly comparable with these Google-target rows.

Runtime / profileRun dateTopologyRunsSuccessCreate p50Connect p50Goto p50Release p50Lifecycle p50
baselayer-firecracker-headless-shell-cdp-warm-density2026-04-23AWS t3.micro runner -> AWS m5zn.metal provider, us-east-2, BrowserArena hello-browser, Google target10099/10093 ms56 ms618 ms5 ms769 ms
baselayer-firecracker-headless-shell-cdp-warm-density2026-04-24AWS t3.micro runner -> AWS m5zn.metal provider, us-east-1, BrowserArena hello-browser, Google target10099/10087 ms102 ms555 ms5 ms749 ms
baselayer-firecracker-full-chromium2026-05-07AWS t3.micro runner -> AWS m5zn.metal provider, public /v1, BrowserArena hello-browser, Google target100100/10096 ms61 ms619 ms10 ms784 ms
baselayer-firecracker-headless-shell2026-04-12AWS m5zn.metal, concurrent BrowserArena-style waves, Google targetc2424/24448 ms104 ms3788 ms9 ms4349 ms

See docs/browserarena-results.md and docs/current-best-profiles.md for replication notes and historical context.

What This Repo Contains

  • control-plane HTTP API for browser session lifecycle management
  • node-agent runtime for launching browser sessions
  • Docker-backed per-session runtime support
  • Firecracker snapshot/restore tooling for chromium-headless-shell
  • benchmark harnesses aligned to BrowserArena stage names
  • public result notes and profile documentation

BaseLayer is lower-level infrastructure for browser hosting experiments. It is not a managed browser automation product.

Replicate The Self-Hosted Methodology

The most direct in-repo replication path is the provider /v1 benchmark harness. It uses the same lifecycle stages as BrowserArena:

  • session_creation_ms
  • session_connect_ms
  • page_goto_ms
  • session_release_ms
  • total_ms

1. Prepare A Linux/KVM Provider Host

Use a Linux host with KVM enabled. The published AWS runs used m5zn.metal.

git clone https://github.com/Lasdw6/baselayer.git baselayer
cd baselayer
npm ci
npm run build
./scripts/bench/bootstrap-firecracker-linux.sh
sudo ./scripts/firecracker/build-headless-shell-rootfs.sh

Start the control plane and Firecracker node agent:

export CONTROL_PLANE_PORT=3000
export CONTROL_PLANE_ASYNC_SESSION_DELETE=1
export CONTROL_PLANE_ASYNC_SESSION_DELETE_DELAY_MS=120000
export CONTROL_PLANE_URL="http://127.0.0.1:3000"export NODE_AGENT_PORT=4000
export NODE_AGENT_PUBLIC_HOST="127.0.0.1"export NODE_AGENT_MODE="firecracker"export BASELAYER_SUPPORTED_RUNTIME_PROFILES="baselayer-firecracker-headless-shell"export MAX_SESSIONS=128
export FIRECRACKER_KERNEL_PATH="$PWD/artifacts/firecracker/vmlinux"export FIRECRACKER_ROOTFS_PATH="$PWD/artifacts/firecracker/rootfs.ext4"export FIRECRACKER_SNAPSHOT_DIR="$PWD/data/firecracker/snapshots"export FIRECRACKER_ALLOW_AUTO_SNAPSHOT=1
export FIRECRACKER_MAX_MICROVM_COUNT=128
export FIRECRACKER_NETWORK_POOL_SIZE=128
export FIRECRACKER_READY_SETTLE_MS=50
export FIRECRACKER_RESTORE_RETRIES=2
npm run dev:api
# in another shell:
npm run dev:agent

For public network tests, put the API behind your own firewall/reverse proxy and enable API-key auth. If you run a split public/internal deployment, set CONTROL_PLANE_PUBLIC_V1_ONLY=1 only on the public listener; the node agent still needs internal registration routes. See Public API Safety.

2. Run The Built-In BrowserArena-Stage Harness

From the provider host or a same-region runner:

export BASELAYER_API_URL="http://<provider-host>:3000/v1"export BASELAYER_RUNTIME_PROFILE="baselayer-firecracker-headless-shell"export BENCH_RUNS=100
export BENCH_CONCURRENCY=1
export BENCH_BROWSERARENA_PAGE_URL="https://example.com/"export BENCH_PAGE_GOTO_WAIT_UNTIL="domcontentloaded"export BENCH_CONNECT_RETRY_BUDGET_MS=15000
export BENCH_OUT="$PWD/data/benchmarks/provider-api-100.json"
npm run bench:provider-api

The output JSON includes p50/p95/p99 for each BrowserArena-style stage plus the raw per-iteration rows.

The current c1 replication rows used that shape with BENCH_RUNS=100 and BENCH_CONCURRENCY=1, repeated five times.

export BENCH_RUNS=10
export BENCH_CONCURRENCY=10
export BENCH_OUT="$PWD/data/benchmarks/provider-api-example-c10x10.json"
npm run bench:provider-api

In this harness, BENCH_RUNS=10 at BENCH_CONCURRENCY=10 means 10 waves of 10 sessions, or 100 measured sessions total.

To reproduce the historical Google-target rows, use:

export BENCH_BROWSERARENA_PAGE_URL="https://google.com/"

3. Run With The BrowserArena Harness

The headline rows above were produced with the BrowserArena hello-browser lifecycle against a self-hosted BaseLayer provider endpoint. Use the same topology when comparing numbers:

  • runner and provider in the same AWS region
  • target: https://example.com for current BrowserArena methodology, or Google only when reproducing the historical snapshot above
  • wait condition: domcontentloaded
  • runs: 100
  • concurrency: 1
  • bounded provider-side admission backpressure is allowed only when it is counted inside session_creation_ms
  • async delete enabled for latency-style runs

Shape:

BASELAYER_BASE_URL="http://<provider-host>:3000/v1" \
npm run bench -- --provider=baselayer --benchmark=hello-browser --runs=100 --concurrency=1

Use a short smoke first:

BENCH_RUNS=1 npm run bench:provider-api
BENCH_RUNS=5 npm run bench:provider-api
BENCH_RUNS=100 npm run bench:provider-api

Only compare a 100-run if the smoke runs are clean.

Requirements

  • Node.js 22+
  • npm
  • Docker for the container runtime path
  • Linux/KVM for Firecracker proof and benchmark paths

Windows and macOS are fine for editing, building TypeScript, and running unit tests that do not require Docker/KVM.

Local Quick Start

Install dependencies:

npm install

Build and test:

npm run build
npm test

Build the runtime image:

docker build -f Dockerfile.runtime -t baselayer-runtime:local .

Start the control plane:

npm run dev:api

Start a local node agent in managed mode:

export CONTROL_PLANE_URL="http://127.0.0.1:3000"export NODE_AGENT_PORT="4000"export NODE_AGENT_PUBLIC_HOST="127.0.0.1"export NODE_AGENT_MODE="managed"export RUNTIME_IMAGE="baselayer-runtime:local"
npm run dev:agent

Create a session:

curl -X POST http://127.0.0.1:3000/v1/sessions \
-H "content-type: application/json" \
-d '{"browser":"chromium","keepAlive":false,"timeoutSec":900,"idleTimeoutSec":120}'

Public API Safety

The control plane is designed for local and lab use by default. Before exposing /v1 outside a trusted network:

  • set CONTROL_PLANE_PUBLIC_V1_ONLY=1
  • set CONTROL_PLANE_ENFORCE_PROVIDER_API_KEY_AUTH=1
  • provide API keys through CONTROL_PLANE_PROVIDER_API_KEY_CONFIG_PATH
  • bind services behind a firewall or reverse proxy

Example config files live in config.

Repository Layout

  • src/api - control plane, scheduler, API routes, store
  • src/node-agent - host runtime, Docker launcher, Firecracker integration
  • src/runtime - browser runtime container entrypoint
  • src/bench - benchmark harnesses and runtime experiments
  • scripts/firecracker - rootfs and Firecracker image helpers
  • scripts/bench - Linux bootstrap and benchmark helpers
  • docs - public architecture, benchmark, and experiment notes
  • test - unit and contract tests

Documentation

License

MIT. See LICENSE.

About

Browser hosting control plane and host runtime for running Chromium sessions

Resources

Contributing

Security policy

Stars

36 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

BaseLayer

BaseLayer is an open-source browser hosting control plane and host runtime for running Chromium sessions with Docker and Firecracker-backed isolation.

It was built to test how fast a self-hosted browser provider can get on the BrowserArena lifecycle methodology:

session create + CDP connect + page.goto + session release

Launch thread: BaseLayer on X

Results

These are self-hosted BrowserArena-methodology runs.

Latest Methodology Snapshot

BrowserArena changed its default fairness URL to example.com on 2026-05-05. Using that current target, BaseLayer's latest sequential self-hosted snapshot was measured on 2026-06-01. The competitor positioning referenced in docs/browserarena-results.md uses BrowserArena c1 leaderboard numbers checked on 2026-06-01.

In the BrowserArena c1 leaderboard snapshot checked on 2026-06-01, this places BaseLayer above the top listed provider by sequential p50 lifecycle latency. For leaderboard-style comparisons, BaseLayer pools all successful iterations, computes the p50 of each lifecycle stage, then sums those stage p50s. The raw per-iteration total_ms.p50 is retained in artifacts for auditing, but it is not the headline comparison number.

Runtime / profileRun dateTopologyRunsSuccessCreate p50Connect p50Goto p50Release p50Lifecycle p50
baselayer-firecracker-headless-shell2026-06-01AWS t3.micro runner -> AWS m5zn.metal host, us-east-2, BrowserArena stages, example.com target, c1 x100, slowest clean run from 5 repeats100100/10074.8 ms54.4 ms82.6 ms11.8 ms223.6 ms

These are self-hosted, sequential benchmark results from five c1 x100 runs on 2026-06-01. The public headline uses the slowest clean 100/100 run from that batch, rounded to 224 ms, rather than the fastest or pooled value. The five-run batch ranged from 190.8 ms to 223.6 ms; pooled successful iterations summed to 216.3 ms across 498/500 successes. These are not official BrowserArena leaderboard submissions. The runs are reproducible, and this repo includes the benchmark harness used, run artifacts, and replication notes.

The latest concurrent c10 x10 run remains under review while the scheduler and demo harness are being hardened. The current public headline is the conservative sequential run above.

The one-shot self-hosted BrowserArena runner now provisions the AWS t3.micro runner and m5zn.metal provider host, bootstraps BaseLayer, waits for warm-pool readiness, runs BrowserArena, pulls artifacts, and tears down resources. See docs/BENCHMARKS.md for the exact command and benchmark knobs.

The current self-host runner path was revalidated on 2026-06-06 with https://example.com, c1 x100, and c10 x100. Three fresh-metal repeats were clean for both c1 and c10. Immediate back-to-back same-host repeats kept c1 stable and kept c10 p50 in range, but the third c10 repeat reached 98/100, so same-host density reruns should still be treated as reliability stress tests.

See docs/browserarena-results.md for the replication notes and caveats.

Google-Target Snapshot

The headline BaseLayer rows below used the BrowserArena hello-browser methodology as it existed at the time of the runs: page.goto against https://google.com/ with waitUntil: "domcontentloaded".

BrowserArena changed its default fairness URL to example.com on 2026-05-05. Current BrowserArena leaderboard numbers are therefore much lower and are not directly comparable with these Google-target rows.

Runtime / profileRun dateTopologyRunsSuccessCreate p50Connect p50Goto p50Release p50Lifecycle p50
baselayer-firecracker-headless-shell-cdp-warm-density2026-04-23AWS t3.micro runner -> AWS m5zn.metal provider, us-east-2, BrowserArena hello-browser, Google target10099/10093 ms56 ms618 ms5 ms769 ms
baselayer-firecracker-headless-shell-cdp-warm-density2026-04-24AWS t3.micro runner -> AWS m5zn.metal provider, us-east-1, BrowserArena hello-browser, Google target10099/10087 ms102 ms555 ms5 ms749 ms
baselayer-firecracker-full-chromium2026-05-07AWS t3.micro runner -> AWS m5zn.metal provider, public /v1, BrowserArena hello-browser, Google target100100/10096 ms61 ms619 ms10 ms784 ms
baselayer-firecracker-headless-shell2026-04-12AWS m5zn.metal, concurrent BrowserArena-style waves, Google targetc2424/24448 ms104 ms3788 ms9 ms4349 ms

See docs/browserarena-results.md and docs/current-best-profiles.md for replication notes and historical context.

What This Repo Contains

  • control-plane HTTP API for browser session lifecycle management
  • node-agent runtime for launching browser sessions
  • Docker-backed per-session runtime support
  • Firecracker snapshot/restore tooling for chromium-headless-shell
  • benchmark harnesses aligned to BrowserArena stage names
  • public result notes and profile documentation

BaseLayer is lower-level infrastructure for browser hosting experiments. It is not a managed browser automation product.

Replicate The Self-Hosted Methodology

The most direct in-repo replication path is the provider /v1 benchmark harness. It uses the same lifecycle stages as BrowserArena:

  • session_creation_ms
  • session_connect_ms
  • page_goto_ms
  • session_release_ms
  • total_ms

1. Prepare A Linux/KVM Provider Host

Use a Linux host with KVM enabled. The published AWS runs used m5zn.metal.

git clone https://github.com/Lasdw6/baselayer.git baselayer
cd baselayer
npm ci
npm run build
./scripts/bench/bootstrap-firecracker-linux.sh
sudo ./scripts/firecracker/build-headless-shell-rootfs.sh

Start the control plane and Firecracker node agent:

export CONTROL_PLANE_PORT=3000
export CONTROL_PLANE_ASYNC_SESSION_DELETE=1
export CONTROL_PLANE_ASYNC_SESSION_DELETE_DELAY_MS=120000
export CONTROL_PLANE_URL="http://127.0.0.1:3000"export NODE_AGENT_PORT=4000
export NODE_AGENT_PUBLIC_HOST="127.0.0.1"export NODE_AGENT_MODE="firecracker"export BASELAYER_SUPPORTED_RUNTIME_PROFILES="baselayer-firecracker-headless-shell"export MAX_SESSIONS=128
export FIRECRACKER_KERNEL_PATH="$PWD/artifacts/firecracker/vmlinux"export FIRECRACKER_ROOTFS_PATH="$PWD/artifacts/firecracker/rootfs.ext4"export FIRECRACKER_SNAPSHOT_DIR="$PWD/data/firecracker/snapshots"export FIRECRACKER_ALLOW_AUTO_SNAPSHOT=1
export FIRECRACKER_MAX_MICROVM_COUNT=128
export FIRECRACKER_NETWORK_POOL_SIZE=128
export FIRECRACKER_READY_SETTLE_MS=50
export FIRECRACKER_RESTORE_RETRIES=2
npm run dev:api
# in another shell:
npm run dev:agent

For public network tests, put the API behind your own firewall/reverse proxy and enable API-key auth. If you run a split public/internal deployment, set CONTROL_PLANE_PUBLIC_V1_ONLY=1 only on the public listener; the node agent still needs internal registration routes. See Public API Safety.

2. Run The Built-In BrowserArena-Stage Harness

From the provider host or a same-region runner:

export BASELAYER_API_URL="http://<provider-host>:3000/v1"export BASELAYER_RUNTIME_PROFILE="baselayer-firecracker-headless-shell"export BENCH_RUNS=100
export BENCH_CONCURRENCY=1
export BENCH_BROWSERARENA_PAGE_URL="https://example.com/"export BENCH_PAGE_GOTO_WAIT_UNTIL="domcontentloaded"export BENCH_CONNECT_RETRY_BUDGET_MS=15000
export BENCH_OUT="$PWD/data/benchmarks/provider-api-100.json"
npm run bench:provider-api

The output JSON includes p50/p95/p99 for each BrowserArena-style stage plus the raw per-iteration rows.

The current c1 replication rows used that shape with BENCH_RUNS=100 and BENCH_CONCURRENCY=1, repeated five times.

export BENCH_RUNS=10
export BENCH_CONCURRENCY=10
export BENCH_OUT="$PWD/data/benchmarks/provider-api-example-c10x10.json"
npm run bench:provider-api

In this harness, BENCH_RUNS=10 at BENCH_CONCURRENCY=10 means 10 waves of 10 sessions, or 100 measured sessions total.

To reproduce the historical Google-target rows, use:

export BENCH_BROWSERARENA_PAGE_URL="https://google.com/"

3. Run With The BrowserArena Harness

The headline rows above were produced with the BrowserArena hello-browser lifecycle against a self-hosted BaseLayer provider endpoint. Use the same topology when comparing numbers:

  • runner and provider in the same AWS region
  • target: https://example.com for current BrowserArena methodology, or Google only when reproducing the historical snapshot above
  • wait condition: domcontentloaded
  • runs: 100
  • concurrency: 1
  • bounded provider-side admission backpressure is allowed only when it is counted inside session_creation_ms
  • async delete enabled for latency-style runs

Shape:

BASELAYER_BASE_URL="http://<provider-host>:3000/v1" \
npm run bench -- --provider=baselayer --benchmark=hello-browser --runs=100 --concurrency=1

Use a short smoke first:

BENCH_RUNS=1 npm run bench:provider-api
BENCH_RUNS=5 npm run bench:provider-api
BENCH_RUNS=100 npm run bench:provider-api

Only compare a 100-run if the smoke runs are clean.

Requirements

  • Node.js 22+
  • npm
  • Docker for the container runtime path
  • Linux/KVM for Firecracker proof and benchmark paths

Windows and macOS are fine for editing, building TypeScript, and running unit tests that do not require Docker/KVM.

Local Quick Start

Install dependencies:

npm install

Build and test:

npm run build
npm test

Build the runtime image:

docker build -f Dockerfile.runtime -t baselayer-runtime:local .

Start the control plane:

npm run dev:api

Start a local node agent in managed mode:

export CONTROL_PLANE_URL="http://127.0.0.1:3000"export NODE_AGENT_PORT="4000"export NODE_AGENT_PUBLIC_HOST="127.0.0.1"export NODE_AGENT_MODE="managed"export RUNTIME_IMAGE="baselayer-runtime:local"
npm run dev:agent

Create a session:

curl -X POST http://127.0.0.1:3000/v1/sessions \
-H "content-type: application/json" \
-d '{"browser":"chromium","keepAlive":false,"timeoutSec":900,"idleTimeoutSec":120}'

Public API Safety

The control plane is designed for local and lab use by default. Before exposing /v1 outside a trusted network:

  • set CONTROL_PLANE_PUBLIC_V1_ONLY=1
  • set CONTROL_PLANE_ENFORCE_PROVIDER_API_KEY_AUTH=1
  • provide API keys through CONTROL_PLANE_PROVIDER_API_KEY_CONFIG_PATH
  • bind services behind a firewall or reverse proxy

Example config files live in config.

Repository Layout

  • src/api - control plane, scheduler, API routes, store
  • src/node-agent - host runtime, Docker launcher, Firecracker integration
  • src/runtime - browser runtime container entrypoint
  • src/bench - benchmark harnesses and runtime experiments
  • scripts/firecracker - rootfs and Firecracker image helpers
  • scripts/bench - Linux bootstrap and benchmark helpers
  • docs - public architecture, benchmark, and experiment notes
  • test - unit and contract tests

Documentation

License

MIT. See LICENSE.

About

Browser hosting control plane and host runtime for running Chromium sessions

Resources

Contributing

Security policy

Stars

36 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

BaseLayer

BaseLayer is an open-source browser hosting control plane and host runtime for running Chromium sessions with Docker and Firecracker-backed isolation.

It was built to test how fast a self-hosted browser provider can get on the BrowserArena lifecycle methodology:

session create + CDP connect + page.goto + session release

Launch thread: BaseLayer on X

Results

These are self-hosted BrowserArena-methodology runs.

Latest Methodology Snapshot

BrowserArena changed its default fairness URL to example.com on 2026-05-05. Using that current target, BaseLayer's latest sequential self-hosted snapshot was measured on 2026-06-01. The competitor positioning referenced in docs/browserarena-results.md uses BrowserArena c1 leaderboard numbers checked on 2026-06-01.

In the BrowserArena c1 leaderboard snapshot checked on 2026-06-01, this places BaseLayer above the top listed provider by sequential p50 lifecycle latency. For leaderboard-style comparisons, BaseLayer pools all successful iterations, computes the p50 of each lifecycle stage, then sums those stage p50s. The raw per-iteration total_ms.p50 is retained in artifacts for auditing, but it is not the headline comparison number.

Runtime / profileRun dateTopologyRunsSuccessCreate p50Connect p50Goto p50Release p50Lifecycle p50
baselayer-firecracker-headless-shell2026-06-01AWS t3.micro runner -> AWS m5zn.metal host, us-east-2, BrowserArena stages, example.com target, c1 x100, slowest clean run from 5 repeats100100/10074.8 ms54.4 ms82.6 ms11.8 ms223.6 ms

These are self-hosted, sequential benchmark results from five c1 x100 runs on 2026-06-01. The public headline uses the slowest clean 100/100 run from that batch, rounded to 224 ms, rather than the fastest or pooled value. The five-run batch ranged from 190.8 ms to 223.6 ms; pooled successful iterations summed to 216.3 ms across 498/500 successes. These are not official BrowserArena leaderboard submissions. The runs are reproducible, and this repo includes the benchmark harness used, run artifacts, and replication notes.

The latest concurrent c10 x10 run remains under review while the scheduler and demo harness are being hardened. The current public headline is the conservative sequential run above.

The one-shot self-hosted BrowserArena runner now provisions the AWS t3.micro runner and m5zn.metal provider host, bootstraps BaseLayer, waits for warm-pool readiness, runs BrowserArena, pulls artifacts, and tears down resources. See docs/BENCHMARKS.md for the exact command and benchmark knobs.

The current self-host runner path was revalidated on 2026-06-06 with https://example.com, c1 x100, and c10 x100. Three fresh-metal repeats were clean for both c1 and c10. Immediate back-to-back same-host repeats kept c1 stable and kept c10 p50 in range, but the third c10 repeat reached 98/100, so same-host density reruns should still be treated as reliability stress tests.

See docs/browserarena-results.md for the replication notes and caveats.

Google-Target Snapshot

The headline BaseLayer rows below used the BrowserArena hello-browser methodology as it existed at the time of the runs: page.goto against https://google.com/ with waitUntil: "domcontentloaded".

BrowserArena changed its default fairness URL to example.com on 2026-05-05. Current BrowserArena leaderboard numbers are therefore much lower and are not directly comparable with these Google-target rows.

Runtime / profileRun dateTopologyRunsSuccessCreate p50Connect p50Goto p50Release p50Lifecycle p50
baselayer-firecracker-headless-shell-cdp-warm-density2026-04-23AWS t3.micro runner -> AWS m5zn.metal provider, us-east-2, BrowserArena hello-browser, Google target10099/10093 ms56 ms618 ms5 ms769 ms
baselayer-firecracker-headless-shell-cdp-warm-density2026-04-24AWS t3.micro runner -> AWS m5zn.metal provider, us-east-1, BrowserArena hello-browser, Google target10099/10087 ms102 ms555 ms5 ms749 ms
baselayer-firecracker-full-chromium2026-05-07AWS t3.micro runner -> AWS m5zn.metal provider, public /v1, BrowserArena hello-browser, Google target100100/10096 ms61 ms619 ms10 ms784 ms
baselayer-firecracker-headless-shell2026-04-12AWS m5zn.metal, concurrent BrowserArena-style waves, Google targetc2424/24448 ms104 ms3788 ms9 ms4349 ms

See docs/browserarena-results.md and docs/current-best-profiles.md for replication notes and historical context.

What This Repo Contains

  • control-plane HTTP API for browser session lifecycle management
  • node-agent runtime for launching browser sessions
  • Docker-backed per-session runtime support
  • Firecracker snapshot/restore tooling for chromium-headless-shell
  • benchmark harnesses aligned to BrowserArena stage names
  • public result notes and profile documentation

BaseLayer is lower-level infrastructure for browser hosting experiments. It is not a managed browser automation product.

Replicate The Self-Hosted Methodology

The most direct in-repo replication path is the provider /v1 benchmark harness. It uses the same lifecycle stages as BrowserArena:

  • session_creation_ms
  • session_connect_ms
  • page_goto_ms
  • session_release_ms
  • total_ms

1. Prepare A Linux/KVM Provider Host

Use a Linux host with KVM enabled. The published AWS runs used m5zn.metal.

git clone https://github.com/Lasdw6/baselayer.git baselayer
cd baselayer
npm ci
npm run build
./scripts/bench/bootstrap-firecracker-linux.sh
sudo ./scripts/firecracker/build-headless-shell-rootfs.sh

Start the control plane and Firecracker node agent:

export CONTROL_PLANE_PORT=3000
export CONTROL_PLANE_ASYNC_SESSION_DELETE=1
export CONTROL_PLANE_ASYNC_SESSION_DELETE_DELAY_MS=120000
export CONTROL_PLANE_URL="http://127.0.0.1:3000"export NODE_AGENT_PORT=4000
export NODE_AGENT_PUBLIC_HOST="127.0.0.1"export NODE_AGENT_MODE="firecracker"export BASELAYER_SUPPORTED_RUNTIME_PROFILES="baselayer-firecracker-headless-shell"export MAX_SESSIONS=128
export FIRECRACKER_KERNEL_PATH="$PWD/artifacts/firecracker/vmlinux"export FIRECRACKER_ROOTFS_PATH="$PWD/artifacts/firecracker/rootfs.ext4"export FIRECRACKER_SNAPSHOT_DIR="$PWD/data/firecracker/snapshots"export FIRECRACKER_ALLOW_AUTO_SNAPSHOT=1
export FIRECRACKER_MAX_MICROVM_COUNT=128
export FIRECRACKER_NETWORK_POOL_SIZE=128
export FIRECRACKER_READY_SETTLE_MS=50
export FIRECRACKER_RESTORE_RETRIES=2
npm run dev:api
# in another shell:
npm run dev:agent

For public network tests, put the API behind your own firewall/reverse proxy and enable API-key auth. If you run a split public/internal deployment, set CONTROL_PLANE_PUBLIC_V1_ONLY=1 only on the public listener; the node agent still needs internal registration routes. See Public API Safety.

2. Run The Built-In BrowserArena-Stage Harness

From the provider host or a same-region runner:

export BASELAYER_API_URL="http://<provider-host>:3000/v1"export BASELAYER_RUNTIME_PROFILE="baselayer-firecracker-headless-shell"export BENCH_RUNS=100
export BENCH_CONCURRENCY=1
export BENCH_BROWSERARENA_PAGE_URL="https://example.com/"export BENCH_PAGE_GOTO_WAIT_UNTIL="domcontentloaded"export BENCH_CONNECT_RETRY_BUDGET_MS=15000
export BENCH_OUT="$PWD/data/benchmarks/provider-api-100.json"
npm run bench:provider-api

The output JSON includes p50/p95/p99 for each BrowserArena-style stage plus the raw per-iteration rows.

The current c1 replication rows used that shape with BENCH_RUNS=100 and BENCH_CONCURRENCY=1, repeated five times.

export BENCH_RUNS=10
export BENCH_CONCURRENCY=10
export BENCH_OUT="$PWD/data/benchmarks/provider-api-example-c10x10.json"
npm run bench:provider-api

In this harness, BENCH_RUNS=10 at BENCH_CONCURRENCY=10 means 10 waves of 10 sessions, or 100 measured sessions total.

To reproduce the historical Google-target rows, use:

export BENCH_BROWSERARENA_PAGE_URL="https://google.com/"

3. Run With The BrowserArena Harness

The headline rows above were produced with the BrowserArena hello-browser lifecycle against a self-hosted BaseLayer provider endpoint. Use the same topology when comparing numbers:

  • runner and provider in the same AWS region
  • target: https://example.com for current BrowserArena methodology, or Google only when reproducing the historical snapshot above
  • wait condition: domcontentloaded
  • runs: 100
  • concurrency: 1
  • bounded provider-side admission backpressure is allowed only when it is counted inside session_creation_ms
  • async delete enabled for latency-style runs

Shape:

BASELAYER_BASE_URL="http://<provider-host>:3000/v1" \
npm run bench -- --provider=baselayer --benchmark=hello-browser --runs=100 --concurrency=1

Use a short smoke first:

BENCH_RUNS=1 npm run bench:provider-api
BENCH_RUNS=5 npm run bench:provider-api
BENCH_RUNS=100 npm run bench:provider-api

Only compare a 100-run if the smoke runs are clean.

Requirements

  • Node.js 22+
  • npm
  • Docker for the container runtime path
  • Linux/KVM for Firecracker proof and benchmark paths

Windows and macOS are fine for editing, building TypeScript, and running unit tests that do not require Docker/KVM.

Local Quick Start

Install dependencies:

npm install

Build and test:

npm run build
npm test

Build the runtime image:

docker build -f Dockerfile.runtime -t baselayer-runtime:local .

Start the control plane:

npm run dev:api

Start a local node agent in managed mode:

export CONTROL_PLANE_URL="http://127.0.0.1:3000"export NODE_AGENT_PORT="4000"export NODE_AGENT_PUBLIC_HOST="127.0.0.1"export NODE_AGENT_MODE="managed"export RUNTIME_IMAGE="baselayer-runtime:local"
npm run dev:agent

Create a session:

curl -X POST http://127.0.0.1:3000/v1/sessions \
-H "content-type: application/json" \
-d '{"browser":"chromium","keepAlive":false,"timeoutSec":900,"idleTimeoutSec":120}'

Public API Safety

The control plane is designed for local and lab use by default. Before exposing /v1 outside a trusted network:

  • set CONTROL_PLANE_PUBLIC_V1_ONLY=1
  • set CONTROL_PLANE_ENFORCE_PROVIDER_API_KEY_AUTH=1
  • provide API keys through CONTROL_PLANE_PROVIDER_API_KEY_CONFIG_PATH
  • bind services behind a firewall or reverse proxy

Example config files live in config.

Repository Layout

  • src/api - control plane, scheduler, API routes, store
  • src/node-agent - host runtime, Docker launcher, Firecracker integration
  • src/runtime - browser runtime container entrypoint
  • src/bench - benchmark harnesses and runtime experiments
  • scripts/firecracker - rootfs and Firecracker image helpers
  • scripts/bench - Linux bootstrap and benchmark helpers
  • docs - public architecture, benchmark, and experiment notes
  • test - unit and contract tests

Documentation

License

MIT. See LICENSE.

About

Browser hosting control plane and host runtime for running Chromium sessions

Resources

Contributing

Security policy

Stars

36 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

BaseLayer

BaseLayer is an open-source browser hosting control plane and host runtime for running Chromium sessions with Docker and Firecracker-backed isolation.

It was built to test how fast a self-hosted browser provider can get on the BrowserArena lifecycle methodology:

session create + CDP connect + page.goto + session release

Launch thread: BaseLayer on X

Results

These are self-hosted BrowserArena-methodology runs.

Latest Methodology Snapshot

BrowserArena changed its default fairness URL to example.com on 2026-05-05. Using that current target, BaseLayer's latest sequential self-hosted snapshot was measured on 2026-06-01. The competitor positioning referenced in docs/browserarena-results.md uses BrowserArena c1 leaderboard numbers checked on 2026-06-01.

In the BrowserArena c1 leaderboard snapshot checked on 2026-06-01, this places BaseLayer above the top listed provider by sequential p50 lifecycle latency. For leaderboard-style comparisons, BaseLayer pools all successful iterations, computes the p50 of each lifecycle stage, then sums those stage p50s. The raw per-iteration total_ms.p50 is retained in artifacts for auditing, but it is not the headline comparison number.

Runtime / profileRun dateTopologyRunsSuccessCreate p50Connect p50Goto p50Release p50Lifecycle p50
baselayer-firecracker-headless-shell2026-06-01AWS t3.micro runner -> AWS m5zn.metal host, us-east-2, BrowserArena stages, example.com target, c1 x100, slowest clean run from 5 repeats100100/10074.8 ms54.4 ms82.6 ms11.8 ms223.6 ms

These are self-hosted, sequential benchmark results from five c1 x100 runs on 2026-06-01. The public headline uses the slowest clean 100/100 run from that batch, rounded to 224 ms, rather than the fastest or pooled value. The five-run batch ranged from 190.8 ms to 223.6 ms; pooled successful iterations summed to 216.3 ms across 498/500 successes. These are not official BrowserArena leaderboard submissions. The runs are reproducible, and this repo includes the benchmark harness used, run artifacts, and replication notes.

The latest concurrent c10 x10 run remains under review while the scheduler and demo harness are being hardened. The current public headline is the conservative sequential run above.

The one-shot self-hosted BrowserArena runner now provisions the AWS t3.micro runner and m5zn.metal provider host, bootstraps BaseLayer, waits for warm-pool readiness, runs BrowserArena, pulls artifacts, and tears down resources. See docs/BENCHMARKS.md for the exact command and benchmark knobs.

The current self-host runner path was revalidated on 2026-06-06 with https://example.com, c1 x100, and c10 x100. Three fresh-metal repeats were clean for both c1 and c10. Immediate back-to-back same-host repeats kept c1 stable and kept c10 p50 in range, but the third c10 repeat reached 98/100, so same-host density reruns should still be treated as reliability stress tests.

See docs/browserarena-results.md for the replication notes and caveats.

Google-Target Snapshot

The headline BaseLayer rows below used the BrowserArena hello-browser methodology as it existed at the time of the runs: page.goto against https://google.com/ with waitUntil: "domcontentloaded".

BrowserArena changed its default fairness URL to example.com on 2026-05-05. Current BrowserArena leaderboard numbers are therefore much lower and are not directly comparable with these Google-target rows.

Runtime / profileRun dateTopologyRunsSuccessCreate p50Connect p50Goto p50Release p50Lifecycle p50
baselayer-firecracker-headless-shell-cdp-warm-density2026-04-23AWS t3.micro runner -> AWS m5zn.metal provider, us-east-2, BrowserArena hello-browser, Google target10099/10093 ms56 ms618 ms5 ms769 ms
baselayer-firecracker-headless-shell-cdp-warm-density2026-04-24AWS t3.micro runner -> AWS m5zn.metal provider, us-east-1, BrowserArena hello-browser, Google target10099/10087 ms102 ms555 ms5 ms749 ms
baselayer-firecracker-full-chromium2026-05-07AWS t3.micro runner -> AWS m5zn.metal provider, public /v1, BrowserArena hello-browser, Google target100100/10096 ms61 ms619 ms10 ms784 ms
baselayer-firecracker-headless-shell2026-04-12AWS m5zn.metal, concurrent BrowserArena-style waves, Google targetc2424/24448 ms104 ms3788 ms9 ms4349 ms

See docs/browserarena-results.md and docs/current-best-profiles.md for replication notes and historical context.

What This Repo Contains

  • control-plane HTTP API for browser session lifecycle management
  • node-agent runtime for launching browser sessions
  • Docker-backed per-session runtime support
  • Firecracker snapshot/restore tooling for chromium-headless-shell
  • benchmark harnesses aligned to BrowserArena stage names
  • public result notes and profile documentation

BaseLayer is lower-level infrastructure for browser hosting experiments. It is not a managed browser automation product.

Replicate The Self-Hosted Methodology

The most direct in-repo replication path is the provider /v1 benchmark harness. It uses the same lifecycle stages as BrowserArena:

  • session_creation_ms
  • session_connect_ms
  • page_goto_ms
  • session_release_ms
  • total_ms

1. Prepare A Linux/KVM Provider Host

Use a Linux host with KVM enabled. The published AWS runs used m5zn.metal.

git clone https://github.com/Lasdw6/baselayer.git baselayer
cd baselayer
npm ci
npm run build
./scripts/bench/bootstrap-firecracker-linux.sh
sudo ./scripts/firecracker/build-headless-shell-rootfs.sh

Start the control plane and Firecracker node agent:

export CONTROL_PLANE_PORT=3000
export CONTROL_PLANE_ASYNC_SESSION_DELETE=1
export CONTROL_PLANE_ASYNC_SESSION_DELETE_DELAY_MS=120000
export CONTROL_PLANE_URL="http://127.0.0.1:3000"export NODE_AGENT_PORT=4000
export NODE_AGENT_PUBLIC_HOST="127.0.0.1"export NODE_AGENT_MODE="firecracker"export BASELAYER_SUPPORTED_RUNTIME_PROFILES="baselayer-firecracker-headless-shell"export MAX_SESSIONS=128
export FIRECRACKER_KERNEL_PATH="$PWD/artifacts/firecracker/vmlinux"export FIRECRACKER_ROOTFS_PATH="$PWD/artifacts/firecracker/rootfs.ext4"export FIRECRACKER_SNAPSHOT_DIR="$PWD/data/firecracker/snapshots"export FIRECRACKER_ALLOW_AUTO_SNAPSHOT=1
export FIRECRACKER_MAX_MICROVM_COUNT=128
export FIRECRACKER_NETWORK_POOL_SIZE=128
export FIRECRACKER_READY_SETTLE_MS=50
export FIRECRACKER_RESTORE_RETRIES=2
npm run dev:api
# in another shell:
npm run dev:agent

For public network tests, put the API behind your own firewall/reverse proxy and enable API-key auth. If you run a split public/internal deployment, set CONTROL_PLANE_PUBLIC_V1_ONLY=1 only on the public listener; the node agent still needs internal registration routes. See Public API Safety.

2. Run The Built-In BrowserArena-Stage Harness

From the provider host or a same-region runner:

export BASELAYER_API_URL="http://<provider-host>:3000/v1"export BASELAYER_RUNTIME_PROFILE="baselayer-firecracker-headless-shell"export BENCH_RUNS=100
export BENCH_CONCURRENCY=1
export BENCH_BROWSERARENA_PAGE_URL="https://example.com/"export BENCH_PAGE_GOTO_WAIT_UNTIL="domcontentloaded"export BENCH_CONNECT_RETRY_BUDGET_MS=15000
export BENCH_OUT="$PWD/data/benchmarks/provider-api-100.json"
npm run bench:provider-api

The output JSON includes p50/p95/p99 for each BrowserArena-style stage plus the raw per-iteration rows.

The current c1 replication rows used that shape with BENCH_RUNS=100 and BENCH_CONCURRENCY=1, repeated five times.

export BENCH_RUNS=10
export BENCH_CONCURRENCY=10
export BENCH_OUT="$PWD/data/benchmarks/provider-api-example-c10x10.json"
npm run bench:provider-api

In this harness, BENCH_RUNS=10 at BENCH_CONCURRENCY=10 means 10 waves of 10 sessions, or 100 measured sessions total.

To reproduce the historical Google-target rows, use:

export BENCH_BROWSERARENA_PAGE_URL="https://google.com/"

3. Run With The BrowserArena Harness

The headline rows above were produced with the BrowserArena hello-browser lifecycle against a self-hosted BaseLayer provider endpoint. Use the same topology when comparing numbers:

  • runner and provider in the same AWS region
  • target: https://example.com for current BrowserArena methodology, or Google only when reproducing the historical snapshot above
  • wait condition: domcontentloaded
  • runs: 100
  • concurrency: 1
  • bounded provider-side admission backpressure is allowed only when it is counted inside session_creation_ms
  • async delete enabled for latency-style runs

Shape:

BASELAYER_BASE_URL="http://<provider-host>:3000/v1" \
npm run bench -- --provider=baselayer --benchmark=hello-browser --runs=100 --concurrency=1

Use a short smoke first:

BENCH_RUNS=1 npm run bench:provider-api
BENCH_RUNS=5 npm run bench:provider-api
BENCH_RUNS=100 npm run bench:provider-api

Only compare a 100-run if the smoke runs are clean.

Requirements

  • Node.js 22+
  • npm
  • Docker for the container runtime path
  • Linux/KVM for Firecracker proof and benchmark paths

Windows and macOS are fine for editing, building TypeScript, and running unit tests that do not require Docker/KVM.

Local Quick Start

Install dependencies:

npm install

Build and test:

npm run build
npm test

Build the runtime image:

docker build -f Dockerfile.runtime -t baselayer-runtime:local .

Start the control plane:

npm run dev:api

Start a local node agent in managed mode:

export CONTROL_PLANE_URL="http://127.0.0.1:3000"export NODE_AGENT_PORT="4000"export NODE_AGENT_PUBLIC_HOST="127.0.0.1"export NODE_AGENT_MODE="managed"export RUNTIME_IMAGE="baselayer-runtime:local"
npm run dev:agent

Create a session:

curl -X POST http://127.0.0.1:3000/v1/sessions \
-H "content-type: application/json" \
-d '{"browser":"chromium","keepAlive":false,"timeoutSec":900,"idleTimeoutSec":120}'

Public API Safety

The control plane is designed for local and lab use by default. Before exposing /v1 outside a trusted network:

  • set CONTROL_PLANE_PUBLIC_V1_ONLY=1
  • set CONTROL_PLANE_ENFORCE_PROVIDER_API_KEY_AUTH=1
  • provide API keys through CONTROL_PLANE_PROVIDER_API_KEY_CONFIG_PATH
  • bind services behind a firewall or reverse proxy

Example config files live in config.

Repository Layout

  • src/api - control plane, scheduler, API routes, store
  • src/node-agent - host runtime, Docker launcher, Firecracker integration
  • src/runtime - browser runtime container entrypoint
  • src/bench - benchmark harnesses and runtime experiments
  • scripts/firecracker - rootfs and Firecracker image helpers
  • scripts/bench - Linux bootstrap and benchmark helpers
  • docs - public architecture, benchmark, and experiment notes
  • test - unit and contract tests

Documentation

License

MIT. See LICENSE.

About

Browser hosting control plane and host runtime for running Chromium sessions

Resources

Contributing

Security policy

Stars

36 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

BaseLayer

BaseLayer is an open-source browser hosting control plane and host runtime for running Chromium sessions with Docker and Firecracker-backed isolation.

It was built to test how fast a self-hosted browser provider can get on the BrowserArena lifecycle methodology:

session create + CDP connect + page.goto + session release

Launch thread: BaseLayer on X

Results

These are self-hosted BrowserArena-methodology runs.

Latest Methodology Snapshot

BrowserArena changed its default fairness URL to example.com on 2026-05-05. Using that current target, BaseLayer's latest sequential self-hosted snapshot was measured on 2026-06-01. The competitor positioning referenced in docs/browserarena-results.md uses BrowserArena c1 leaderboard numbers checked on 2026-06-01.

In the BrowserArena c1 leaderboard snapshot checked on 2026-06-01, this places BaseLayer above the top listed provider by sequential p50 lifecycle latency. For leaderboard-style comparisons, BaseLayer pools all successful iterations, computes the p50 of each lifecycle stage, then sums those stage p50s. The raw per-iteration total_ms.p50 is retained in artifacts for auditing, but it is not the headline comparison number.

Runtime / profileRun dateTopologyRunsSuccessCreate p50Connect p50Goto p50Release p50Lifecycle p50
baselayer-firecracker-headless-shell2026-06-01AWS t3.micro runner -> AWS m5zn.metal host, us-east-2, BrowserArena stages, example.com target, c1 x100, slowest clean run from 5 repeats100100/10074.8 ms54.4 ms82.6 ms11.8 ms223.6 ms

These are self-hosted, sequential benchmark results from five c1 x100 runs on 2026-06-01. The public headline uses the slowest clean 100/100 run from that batch, rounded to 224 ms, rather than the fastest or pooled value. The five-run batch ranged from 190.8 ms to 223.6 ms; pooled successful iterations summed to 216.3 ms across 498/500 successes. These are not official BrowserArena leaderboard submissions. The runs are reproducible, and this repo includes the benchmark harness used, run artifacts, and replication notes.

The latest concurrent c10 x10 run remains under review while the scheduler and demo harness are being hardened. The current public headline is the conservative sequential run above.

The one-shot self-hosted BrowserArena runner now provisions the AWS t3.micro runner and m5zn.metal provider host, bootstraps BaseLayer, waits for warm-pool readiness, runs BrowserArena, pulls artifacts, and tears down resources. See docs/BENCHMARKS.md for the exact command and benchmark knobs.

The current self-host runner path was revalidated on 2026-06-06 with https://example.com, c1 x100, and c10 x100. Three fresh-metal repeats were clean for both c1 and c10. Immediate back-to-back same-host repeats kept c1 stable and kept c10 p50 in range, but the third c10 repeat reached 98/100, so same-host density reruns should still be treated as reliability stress tests.

See docs/browserarena-results.md for the replication notes and caveats.

Google-Target Snapshot

The headline BaseLayer rows below used the BrowserArena hello-browser methodology as it existed at the time of the runs: page.goto against https://google.com/ with waitUntil: "domcontentloaded".

BrowserArena changed its default fairness URL to example.com on 2026-05-05. Current BrowserArena leaderboard numbers are therefore much lower and are not directly comparable with these Google-target rows.

Runtime / profileRun dateTopologyRunsSuccessCreate p50Connect p50Goto p50Release p50Lifecycle p50
baselayer-firecracker-headless-shell-cdp-warm-density2026-04-23AWS t3.micro runner -> AWS m5zn.metal provider, us-east-2, BrowserArena hello-browser, Google target10099/10093 ms56 ms618 ms5 ms769 ms
baselayer-firecracker-headless-shell-cdp-warm-density2026-04-24AWS t3.micro runner -> AWS m5zn.metal provider, us-east-1, BrowserArena hello-browser, Google target10099/10087 ms102 ms555 ms5 ms749 ms
baselayer-firecracker-full-chromium2026-05-07AWS t3.micro runner -> AWS m5zn.metal provider, public /v1, BrowserArena hello-browser, Google target100100/10096 ms61 ms619 ms10 ms784 ms
baselayer-firecracker-headless-shell2026-04-12AWS m5zn.metal, concurrent BrowserArena-style waves, Google targetc2424/24448 ms104 ms3788 ms9 ms4349 ms

See docs/browserarena-results.md and docs/current-best-profiles.md for replication notes and historical context.

What This Repo Contains

  • control-plane HTTP API for browser session lifecycle management
  • node-agent runtime for launching browser sessions
  • Docker-backed per-session runtime support
  • Firecracker snapshot/restore tooling for chromium-headless-shell
  • benchmark harnesses aligned to BrowserArena stage names
  • public result notes and profile documentation

BaseLayer is lower-level infrastructure for browser hosting experiments. It is not a managed browser automation product.

Replicate The Self-Hosted Methodology

The most direct in-repo replication path is the provider /v1 benchmark harness. It uses the same lifecycle stages as BrowserArena:

  • session_creation_ms
  • session_connect_ms
  • page_goto_ms
  • session_release_ms
  • total_ms

1. Prepare A Linux/KVM Provider Host

Use a Linux host with KVM enabled. The published AWS runs used m5zn.metal.

git clone https://github.com/Lasdw6/baselayer.git baselayer
cd baselayer
npm ci
npm run build
./scripts/bench/bootstrap-firecracker-linux.sh
sudo ./scripts/firecracker/build-headless-shell-rootfs.sh

Start the control plane and Firecracker node agent:

export CONTROL_PLANE_PORT=3000
export CONTROL_PLANE_ASYNC_SESSION_DELETE=1
export CONTROL_PLANE_ASYNC_SESSION_DELETE_DELAY_MS=120000
export CONTROL_PLANE_URL="http://127.0.0.1:3000"export NODE_AGENT_PORT=4000
export NODE_AGENT_PUBLIC_HOST="127.0.0.1"export NODE_AGENT_MODE="firecracker"export BASELAYER_SUPPORTED_RUNTIME_PROFILES="baselayer-firecracker-headless-shell"export MAX_SESSIONS=128
export FIRECRACKER_KERNEL_PATH="$PWD/artifacts/firecracker/vmlinux"export FIRECRACKER_ROOTFS_PATH="$PWD/artifacts/firecracker/rootfs.ext4"export FIRECRACKER_SNAPSHOT_DIR="$PWD/data/firecracker/snapshots"export FIRECRACKER_ALLOW_AUTO_SNAPSHOT=1
export FIRECRACKER_MAX_MICROVM_COUNT=128
export FIRECRACKER_NETWORK_POOL_SIZE=128
export FIRECRACKER_READY_SETTLE_MS=50
export FIRECRACKER_RESTORE_RETRIES=2
npm run dev:api
# in another shell:
npm run dev:agent

For public network tests, put the API behind your own firewall/reverse proxy and enable API-key auth. If you run a split public/internal deployment, set CONTROL_PLANE_PUBLIC_V1_ONLY=1 only on the public listener; the node agent still needs internal registration routes. See Public API Safety.

2. Run The Built-In BrowserArena-Stage Harness

From the provider host or a same-region runner:

export BASELAYER_API_URL="http://<provider-host>:3000/v1"export BASELAYER_RUNTIME_PROFILE="baselayer-firecracker-headless-shell"export BENCH_RUNS=100
export BENCH_CONCURRENCY=1
export BENCH_BROWSERARENA_PAGE_URL="https://example.com/"export BENCH_PAGE_GOTO_WAIT_UNTIL="domcontentloaded"export BENCH_CONNECT_RETRY_BUDGET_MS=15000
export BENCH_OUT="$PWD/data/benchmarks/provider-api-100.json"
npm run bench:provider-api

The output JSON includes p50/p95/p99 for each BrowserArena-style stage plus the raw per-iteration rows.

The current c1 replication rows used that shape with BENCH_RUNS=100 and BENCH_CONCURRENCY=1, repeated five times.

export BENCH_RUNS=10
export BENCH_CONCURRENCY=10
export BENCH_OUT="$PWD/data/benchmarks/provider-api-example-c10x10.json"
npm run bench:provider-api

In this harness, BENCH_RUNS=10 at BENCH_CONCURRENCY=10 means 10 waves of 10 sessions, or 100 measured sessions total.

To reproduce the historical Google-target rows, use:

export BENCH_BROWSERARENA_PAGE_URL="https://google.com/"

3. Run With The BrowserArena Harness

The headline rows above were produced with the BrowserArena hello-browser lifecycle against a self-hosted BaseLayer provider endpoint. Use the same topology when comparing numbers:

  • runner and provider in the same AWS region
  • target: https://example.com for current BrowserArena methodology, or Google only when reproducing the historical snapshot above
  • wait condition: domcontentloaded
  • runs: 100
  • concurrency: 1
  • bounded provider-side admission backpressure is allowed only when it is counted inside session_creation_ms
  • async delete enabled for latency-style runs

Shape:

BASELAYER_BASE_URL="http://<provider-host>:3000/v1" \
npm run bench -- --provider=baselayer --benchmark=hello-browser --runs=100 --concurrency=1

Use a short smoke first:

BENCH_RUNS=1 npm run bench:provider-api
BENCH_RUNS=5 npm run bench:provider-api
BENCH_RUNS=100 npm run bench:provider-api

Only compare a 100-run if the smoke runs are clean.

Requirements

  • Node.js 22+
  • npm
  • Docker for the container runtime path
  • Linux/KVM for Firecracker proof and benchmark paths

Windows and macOS are fine for editing, building TypeScript, and running unit tests that do not require Docker/KVM.

Local Quick Start

Install dependencies:

npm install

Build and test:

npm run build
npm test

Build the runtime image:

docker build -f Dockerfile.runtime -t baselayer-runtime:local .

Start the control plane:

npm run dev:api

Start a local node agent in managed mode:

export CONTROL_PLANE_URL="http://127.0.0.1:3000"export NODE_AGENT_PORT="4000"export NODE_AGENT_PUBLIC_HOST="127.0.0.1"export NODE_AGENT_MODE="managed"export RUNTIME_IMAGE="baselayer-runtime:local"
npm run dev:agent

Create a session:

curl -X POST http://127.0.0.1:3000/v1/sessions \
-H "content-type: application/json" \
-d '{"browser":"chromium","keepAlive":false,"timeoutSec":900,"idleTimeoutSec":120}'

Public API Safety

The control plane is designed for local and lab use by default. Before exposing /v1 outside a trusted network:

  • set CONTROL_PLANE_PUBLIC_V1_ONLY=1
  • set CONTROL_PLANE_ENFORCE_PROVIDER_API_KEY_AUTH=1
  • provide API keys through CONTROL_PLANE_PROVIDER_API_KEY_CONFIG_PATH
  • bind services behind a firewall or reverse proxy

Example config files live in config.

Repository Layout

  • src/api - control plane, scheduler, API routes, store
  • src/node-agent - host runtime, Docker launcher, Firecracker integration
  • src/runtime - browser runtime container entrypoint
  • src/bench - benchmark harnesses and runtime experiments
  • scripts/firecracker - rootfs and Firecracker image helpers
  • scripts/bench - Linux bootstrap and benchmark helpers
  • docs - public architecture, benchmark, and experiment notes
  • test - unit and contract tests

Documentation

License

MIT. See LICENSE.

About

Browser hosting control plane and host runtime for running Chromium sessions

Resources

Contributing

Security policy

Stars

36 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

BaseLayer

BaseLayer is an open-source browser hosting control plane and host runtime for running Chromium sessions with Docker and Firecracker-backed isolation.

It was built to test how fast a self-hosted browser provider can get on the BrowserArena lifecycle methodology:

session create + CDP connect + page.goto + session release

Launch thread: BaseLayer on X

Results

These are self-hosted BrowserArena-methodology runs.

Latest Methodology Snapshot

BrowserArena changed its default fairness URL to example.com on 2026-05-05. Using that current target, BaseLayer's latest sequential self-hosted snapshot was measured on 2026-06-01. The competitor positioning referenced in docs/browserarena-results.md uses BrowserArena c1 leaderboard numbers checked on 2026-06-01.

In the BrowserArena c1 leaderboard snapshot checked on 2026-06-01, this places BaseLayer above the top listed provider by sequential p50 lifecycle latency. For leaderboard-style comparisons, BaseLayer pools all successful iterations, computes the p50 of each lifecycle stage, then sums those stage p50s. The raw per-iteration total_ms.p50 is retained in artifacts for auditing, but it is not the headline comparison number.

Runtime / profileRun dateTopologyRunsSuccessCreate p50Connect p50Goto p50Release p50Lifecycle p50
baselayer-firecracker-headless-shell2026-06-01AWS t3.micro runner -> AWS m5zn.metal host, us-east-2, BrowserArena stages, example.com target, c1 x100, slowest clean run from 5 repeats100100/10074.8 ms54.4 ms82.6 ms11.8 ms223.6 ms

These are self-hosted, sequential benchmark results from five c1 x100 runs on 2026-06-01. The public headline uses the slowest clean 100/100 run from that batch, rounded to 224 ms, rather than the fastest or pooled value. The five-run batch ranged from 190.8 ms to 223.6 ms; pooled successful iterations summed to 216.3 ms across 498/500 successes. These are not official BrowserArena leaderboard submissions. The runs are reproducible, and this repo includes the benchmark harness used, run artifacts, and replication notes.

The latest concurrent c10 x10 run remains under review while the scheduler and demo harness are being hardened. The current public headline is the conservative sequential run above.

The one-shot self-hosted BrowserArena runner now provisions the AWS t3.micro runner and m5zn.metal provider host, bootstraps BaseLayer, waits for warm-pool readiness, runs BrowserArena, pulls artifacts, and tears down resources. See docs/BENCHMARKS.md for the exact command and benchmark knobs.

The current self-host runner path was revalidated on 2026-06-06 with https://example.com, c1 x100, and c10 x100. Three fresh-metal repeats were clean for both c1 and c10. Immediate back-to-back same-host repeats kept c1 stable and kept c10 p50 in range, but the third c10 repeat reached 98/100, so same-host density reruns should still be treated as reliability stress tests.

See docs/browserarena-results.md for the replication notes and caveats.

Google-Target Snapshot

The headline BaseLayer rows below used the BrowserArena hello-browser methodology as it existed at the time of the runs: page.goto against https://google.com/ with waitUntil: "domcontentloaded".

BrowserArena changed its default fairness URL to example.com on 2026-05-05. Current BrowserArena leaderboard numbers are therefore much lower and are not directly comparable with these Google-target rows.

Runtime / profileRun dateTopologyRunsSuccessCreate p50Connect p50Goto p50Release p50Lifecycle p50
baselayer-firecracker-headless-shell-cdp-warm-density2026-04-23AWS t3.micro runner -> AWS m5zn.metal provider, us-east-2, BrowserArena hello-browser, Google target10099/10093 ms56 ms618 ms5 ms769 ms
baselayer-firecracker-headless-shell-cdp-warm-density2026-04-24AWS t3.micro runner -> AWS m5zn.metal provider, us-east-1, BrowserArena hello-browser, Google target10099/10087 ms102 ms555 ms5 ms749 ms
baselayer-firecracker-full-chromium2026-05-07AWS t3.micro runner -> AWS m5zn.metal provider, public /v1, BrowserArena hello-browser, Google target100100/10096 ms61 ms619 ms10 ms784 ms
baselayer-firecracker-headless-shell2026-04-12AWS m5zn.metal, concurrent BrowserArena-style waves, Google targetc2424/24448 ms104 ms3788 ms9 ms4349 ms

See docs/browserarena-results.md and docs/current-best-profiles.md for replication notes and historical context.

What This Repo Contains

  • control-plane HTTP API for browser session lifecycle management
  • node-agent runtime for launching browser sessions
  • Docker-backed per-session runtime support
  • Firecracker snapshot/restore tooling for chromium-headless-shell
  • benchmark harnesses aligned to BrowserArena stage names
  • public result notes and profile documentation

BaseLayer is lower-level infrastructure for browser hosting experiments. It is not a managed browser automation product.

Replicate The Self-Hosted Methodology

The most direct in-repo replication path is the provider /v1 benchmark harness. It uses the same lifecycle stages as BrowserArena:

  • session_creation_ms
  • session_connect_ms
  • page_goto_ms
  • session_release_ms
  • total_ms

1. Prepare A Linux/KVM Provider Host

Use a Linux host with KVM enabled. The published AWS runs used m5zn.metal.

git clone https://github.com/Lasdw6/baselayer.git baselayer
cd baselayer
npm ci
npm run build
./scripts/bench/bootstrap-firecracker-linux.sh
sudo ./scripts/firecracker/build-headless-shell-rootfs.sh

Start the control plane and Firecracker node agent:

export CONTROL_PLANE_PORT=3000
export CONTROL_PLANE_ASYNC_SESSION_DELETE=1
export CONTROL_PLANE_ASYNC_SESSION_DELETE_DELAY_MS=120000
export CONTROL_PLANE_URL="http://127.0.0.1:3000"export NODE_AGENT_PORT=4000
export NODE_AGENT_PUBLIC_HOST="127.0.0.1"export NODE_AGENT_MODE="firecracker"export BASELAYER_SUPPORTED_RUNTIME_PROFILES="baselayer-firecracker-headless-shell"export MAX_SESSIONS=128
export FIRECRACKER_KERNEL_PATH="$PWD/artifacts/firecracker/vmlinux"export FIRECRACKER_ROOTFS_PATH="$PWD/artifacts/firecracker/rootfs.ext4"export FIRECRACKER_SNAPSHOT_DIR="$PWD/data/firecracker/snapshots"export FIRECRACKER_ALLOW_AUTO_SNAPSHOT=1
export FIRECRACKER_MAX_MICROVM_COUNT=128
export FIRECRACKER_NETWORK_POOL_SIZE=128
export FIRECRACKER_READY_SETTLE_MS=50
export FIRECRACKER_RESTORE_RETRIES=2
npm run dev:api
# in another shell:
npm run dev:agent

For public network tests, put the API behind your own firewall/reverse proxy and enable API-key auth. If you run a split public/internal deployment, set CONTROL_PLANE_PUBLIC_V1_ONLY=1 only on the public listener; the node agent still needs internal registration routes. See Public API Safety.

2. Run The Built-In BrowserArena-Stage Harness

From the provider host or a same-region runner:

export BASELAYER_API_URL="http://<provider-host>:3000/v1"export BASELAYER_RUNTIME_PROFILE="baselayer-firecracker-headless-shell"export BENCH_RUNS=100
export BENCH_CONCURRENCY=1
export BENCH_BROWSERARENA_PAGE_URL="https://example.com/"export BENCH_PAGE_GOTO_WAIT_UNTIL="domcontentloaded"export BENCH_CONNECT_RETRY_BUDGET_MS=15000
export BENCH_OUT="$PWD/data/benchmarks/provider-api-100.json"
npm run bench:provider-api

The output JSON includes p50/p95/p99 for each BrowserArena-style stage plus the raw per-iteration rows.

The current c1 replication rows used that shape with BENCH_RUNS=100 and BENCH_CONCURRENCY=1, repeated five times.

export BENCH_RUNS=10
export BENCH_CONCURRENCY=10
export BENCH_OUT="$PWD/data/benchmarks/provider-api-example-c10x10.json"
npm run bench:provider-api

In this harness, BENCH_RUNS=10 at BENCH_CONCURRENCY=10 means 10 waves of 10 sessions, or 100 measured sessions total.

To reproduce the historical Google-target rows, use:

export BENCH_BROWSERARENA_PAGE_URL="https://google.com/"

3. Run With The BrowserArena Harness

The headline rows above were produced with the BrowserArena hello-browser lifecycle against a self-hosted BaseLayer provider endpoint. Use the same topology when comparing numbers:

  • runner and provider in the same AWS region
  • target: https://example.com for current BrowserArena methodology, or Google only when reproducing the historical snapshot above
  • wait condition: domcontentloaded
  • runs: 100
  • concurrency: 1
  • bounded provider-side admission backpressure is allowed only when it is counted inside session_creation_ms
  • async delete enabled for latency-style runs

Shape:

BASELAYER_BASE_URL="http://<provider-host>:3000/v1" \
npm run bench -- --provider=baselayer --benchmark=hello-browser --runs=100 --concurrency=1

Use a short smoke first:

BENCH_RUNS=1 npm run bench:provider-api
BENCH_RUNS=5 npm run bench:provider-api
BENCH_RUNS=100 npm run bench:provider-api

Only compare a 100-run if the smoke runs are clean.

Requirements

  • Node.js 22+
  • npm
  • Docker for the container runtime path
  • Linux/KVM for Firecracker proof and benchmark paths

Windows and macOS are fine for editing, building TypeScript, and running unit tests that do not require Docker/KVM.

Local Quick Start

Install dependencies:

npm install

Build and test:

npm run build
npm test

Build the runtime image:

docker build -f Dockerfile.runtime -t baselayer-runtime:local .

Start the control plane:

npm run dev:api

Start a local node agent in managed mode:

export CONTROL_PLANE_URL="http://127.0.0.1:3000"export NODE_AGENT_PORT="4000"export NODE_AGENT_PUBLIC_HOST="127.0.0.1"export NODE_AGENT_MODE="managed"export RUNTIME_IMAGE="baselayer-runtime:local"
npm run dev:agent

Create a session:

curl -X POST http://127.0.0.1:3000/v1/sessions \
-H "content-type: application/json" \
-d '{"browser":"chromium","keepAlive":false,"timeoutSec":900,"idleTimeoutSec":120}'

Public API Safety

The control plane is designed for local and lab use by default. Before exposing /v1 outside a trusted network:

  • set CONTROL_PLANE_PUBLIC_V1_ONLY=1
  • set CONTROL_PLANE_ENFORCE_PROVIDER_API_KEY_AUTH=1
  • provide API keys through CONTROL_PLANE_PROVIDER_API_KEY_CONFIG_PATH
  • bind services behind a firewall or reverse proxy

Example config files live in config.

Repository Layout

  • src/api - control plane, scheduler, API routes, store
  • src/node-agent - host runtime, Docker launcher, Firecracker integration
  • src/runtime - browser runtime container entrypoint
  • src/bench - benchmark harnesses and runtime experiments
  • scripts/firecracker - rootfs and Firecracker image helpers
  • scripts/bench - Linux bootstrap and benchmark helpers
  • docs - public architecture, benchmark, and experiment notes
  • test - unit and contract tests

Documentation

License

MIT. See LICENSE.

About

Browser hosting control plane and host runtime for running Chromium sessions

Resources

Contributing

Security policy

Stars

36 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages