Self-hosted service
kernel/kernel-images avatar
kernel/kernel-images

Kernel's container images for a real Chrome you can watch

Browsers-as-a-service for automations and web agents

1,065 stars83 forksGoApache-2.0

At a glance

What is it?
The open repository behind a hosted browser service: headful Chromium in Docker or on a Unikraft unikernel, reachable over CDP and watchable through a live view.
Who is it for?
What makes this repository worth reading is that it treats a browser as something you watch, not just something you script. The CDP handshake is short because that part is standard, but the live view, the replay capture and the snapshot-and-resume standby behaviour are the reasons an agent that gets stuck is still debuggable.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 19 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A commercial service that ships its runtime

The repository is not a library you import. The README says plainly that it powers hosted services, and the sign-up link is at the top. What is published here is the runtime: a sandboxed, ready-to-use Chrome that browser automation frameworks can connect to.

The repository numbers suggest an early commercial product rather than a hobby: 1065 stars, 83 forks and 50 open issues, licensed Apache-2.0, with a last push on 2026-09-19. There are no tagged releases at all, which fits a service that ships continuously rather than versioned for download.

The feature list is four items, and they are the four things that distinguish this from running Chromium in a container yourself. A sandboxed Chrome that Playwright and Puppeteer can attach to over the DevTools protocol. Remote GUI access for visual monitoring. Configurable live view settings, including read-only mode and window dimensions. And controllable video replays of the session.

The last one is the interesting claim. Replay capture means you can watch what the agent did after the fact rather than only reading its trace, which is the difference between debugging a browser agent and guessing at one.

Connecting is a plain CDP handshake

The automation interface is deliberately unsurprising. Port 9222 is exposed through ncat, so anything that speaks the Chrome DevTools Protocol can drive the browser, and you can disconnect and reconnect to the same instance.

The first step is to ask the browser for its websocket endpoint. The Host header is noted as required when running on a unikernel.

typescript
const url = new URL("http://localhost:9222/json/version");
const response = await fetch(url, {
  headers: {
    "Host": "<this can be anything>" // Required if using a unikernel
  }
});
if (response.status !== 200) {
  throw new Error(
    `Failed to retrieve browser instance: ${
      response.statusText
    } ${await response.text()}`
  );
}
// webSocketDebuggerUrl should look like:
// ws:///devtools/browser/06acd5ef-9961-431d-b6a0-86b99734f816
const { webSocketDebuggerUrl } = await response.json();

From there it is a normal connection call, and the only difference between the two clients is which function name you use.

typescript
// Puppeteer
const browser = await puppeteer.connect({
  browserWSEndpoint: webSocketDebuggerUrl,
});
// Playwright
const browser = await chromium.connectOverCDP(webSocketDebuggerUrl);

Because the interface is CDP rather than a proprietary protocol, anything else that speaks it, including a raw websocket client, will also work. That matters for anyone who does not want a framework at all.

Running it in Docker, where the scripts are

The container path is two scripts in the images directory. The build script tags the image through an IMAGE variable and the run script launches it, and the README's example enables WebRTC for the live view.

sh
cd images/chromium-headful
IMAGE=kernel-docker ./build-docker.sh
IMAGE=kernel-docker ENABLE_WEBRTC=true ./run-docker.sh

The package.json at the repository root offers a second route, defining a kernel:build script and a kernel:run script that publishes ports 8501, 8080, 6080 and 9222. Port 9222 is the CDP endpoint and 6080 is the conventional noVNC port.

There is a mismatch worth flagging rather than smoothing over. The package.json build script points at containers/docker/Dockerfile, but the repository tree lists images/ and no containers/ directory. The README's own instructions point at images/chromium-headful, so the package.json script looks like it refers to a layout that has since moved. If you go that way, expect to fix the path.

The tree also contains server/, shared/, bench/, plans/, static/ and socket.yml, plus an AGENTS.md and a .semgrepignore, which is consistent with a service monorepo where the image build is one part.

Two live view backends, and one that is faster

The live view is how a human watches the agent, and the README offers two implementations that both map to port 443.

NoVNC is the default path and is what you get when ENABLE_WEBRTC is false. It supports read and write, meaning you can click inside the remote browser, not just watch it. WebRTC is the faster option and adds window resizing and copy and paste, and it is enabled by setting ENABLE_WEBRTC to true.

Two caveats come with them. Audio streaming over WebRTC is described as currently non-functional, with the README saying it needs to be fixed. And the view is read and write by default, which is the right default for interactive debugging and the wrong default for anything touching a logged-in account. To restrict it, add ENABLE_READONLY_VIEW=true as an environment variable in the docker run invocation.

The read-only switch is worth remembering as the thing to set first when you are watching an agent operate on anything you care about.

The unikernel path buys resume, not speed of browsing

The Unikraft variant builds on top of the Docker image and changes what happens when the browser is idle. When there is no network activity the instance goes into standby, its state is snapshotted, and it can be restored exactly as it was. The README lists what that preserves: browser auth cookies, local file interactions, browser settings, and the exact page and window zoom you were on.

Cold restarts are stated as under 20 milliseconds. Services such as mutter and tint take a few seconds to start before that becomes true, which is the real cold start.

The deployment path uses the Kraft CLI, with a region and a token as environment variables and a build and run script per image.

sh
curl -sSfL https://get.kraftkit.sh | sh
export UKC_METRO=<region>
export UKC_TOKEN=<secret>
IMAGE=YOUR_UKC_USERNAME/chromium-headful-test:latest VOLIMPORT_PREFIX=official images/chromium-headful/run-unikernel.sh

Deployment prints a name, a UUID, a metro region, a public domain, a memory figure of 8192 MiB and the args passed to the image. The memory floor is explicit: at least 8 GB.

There are two operational warnings worth repeating. WebRTC on the unikernel requires a TURN server, because direct exposure of UDP ports is not supported on the platform. And the generated URL is public, meaning anyone with it can reach the remote GUI, so the README limits the unikernel deployment to non-sensitive browser interactions and tells you to delete the instance when finished.

Disconnecting does not close the browser

One behaviour catches people out and the README spells it out: calling browser.close() ends the websocket connection but does not actually close the browser. After it, the unikernel goes into standby once network activity ends, and you can reconnect to the same instance over CDP.

For a Docker deployment the same disconnect and reconnect story holds, since the README says you can disconnect from the browser and reconnect to it. The practical effect is that a crashed or finished agent script does not lose the browser session. That is the feature: the page you were on, with whatever state it had, is still there.

The related environment variable is VCPUS, settable to a value like 8 to change the vCPU count, which matters because the default allocation is not stated and MuJoCo-scale browser workloads want more than one.

One oddity in the repository's own metadata is worth mentioning: the language field says Go while the root package.json declares ES modules and depends on bun types, and the tree carries a bun.lock. The repository is evidently a mix, with the server side in Go and the tooling in TypeScript, which matches a service that runs images plus a control plane.

Editorial conclusion

What makes this repository worth reading is that it treats a browser as something you watch, not just something you script. The CDP handshake is short because that part is standard, but the live view, the replay capture and the snapshot-and-resume standby behaviour are the reasons an agent that gets stuck is still debuggable. The caveats are stated in the README itself: audio over WebRTC does not work, the unikernel wants at least 8 GB of memory, and a deployed unikernel's URL is public, so anyone holding it can drive your browser. Run the Docker path on your own machine first, connect with Playwright or Puppeteer on port 9222, and only reach for Kraft when cold restart latency is the actual constraint.

Frequently asked questions

What is the Kernel kernel-images repository?

It is the open runtime behind a hosted browser service: a sandboxed, ready-to-use Chrome that Playwright and Puppeteer can connect to over the DevTools protocol, with remote GUI access and session replays. The README states that this repository powers the hosted services at kernel.sh.

How do you connect Playwright or Puppeteer to a Kernel browser?

Fetch the websocket endpoint from the /json/version endpoint on port 9222, then connect. Puppeteer uses connect with the browserWSEndpoint option, and Playwright uses chromium.connectOverCDP with the same URL. The Host header must be set when running on a unikernel.

Is there a way to watch what a browser agent is doing?

Yes. The live view maps to port 443 and comes in a noVNC implementation and a faster WebRTC one that adds window resizing and copy and paste. Both support read and write, and setting ENABLE_READONLY_VIEW=true restricts the view to read-only. Audio streaming over WebRTC is currently non-functional.

What does the Unikraft version of kernel-images actually add?

Standby behaviour. With no network activity the instance sleeps, its state is snapshotted and restored exactly, preserving cookies, local files and the page you were on, with cold restarts stated as under 20 milliseconds once services have started. The trade-offs are an 8 GB memory floor and a public deployment URL.

Official sources

  1. Issues
  2. kernel/kernel-images on GitHub
  3. License: Apache-2.0
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/kernel-kernel-images.svg)](https://hysenlabs.com/projects/kernel-kernel-images)