npm i @fkn/container

Ship a Docker image to the browser.
Then fetch it.

The build step turns your Dockerfile into a WebAssembly artifact. Your app imports it, starts it in a worker, and makes real HTTP requests to the service running inside. No backend, no CORS, no network round trip.

import { createContainer } from '@fkn/container'
import image from './api/Dockerfile?container'

const api = createContainer({ image, ports: [8080] })
const response = await api.fetch('/health')
0 servers 1 worker TCP + UDP egress

Live demo

A container is one click away. Send it a request.

The image below is alpine:3.21 with a busybox HTTP service on port 8080, converted to WebAssembly ahead of time. Everything after the button runs in this tab. It answers with what it saw, so the JSON below describes your request rather than repeating a canned string.

  1. Downloadidle
  2. Compileidle
  3. Boot Linuxidle
  4. First responseidle
Request app.fetch

Start the container to enable requests.

Response waiting

The container has not answered yet.

Container stdout linux/riscv64

            

This service forks a busybox shell per connection, which is most of the per-request time under emulation. A process that accepts in a loop is considerably cheaper.

Three files

The whole integration.

The plugin builds the image when the Dockerfile changes and hands your import a URL. It also sets the cross-origin isolation headers the runtime needs, so there is no header archaeology on the first run.

vite.config.tsbuild
import { containers } from '@fkn/container/vite'

export default {
  plugins: [containers()],
}
app.tsruntime
import { createContainer } from '@fkn/container'
import image from './api/Dockerfile?container'

const api = createContainer({
  image,
  ports: [8080],
})

const res = await api.fetch('/users/42')
console.log(await res.json())
api/Dockerfileimage
FROM alpine:3.21

EXPOSE 8080
CMD ["/bin/sh", "/srv/serve.sh"]

What is under the fetch

A complete network path, not a fetch shim.

The guest emits Ethernet frames into an in-browser gVisor stack. Your request enters through a loopback listener and is dialled into the guest as real TCP. Outbound traffic leaves over FKN, so the container also gets working DNS, TCP and UDP.

01fetchyour app
02gVisorWASM netstack
03Linuxemulated riscv64
04your serviceport 8080

Build one in the browser

Or skip the build step entirely.

The playground boots a builder guest with Buildah inside it, pulls your base image through FKN, and runs the result. Slower than a prebuilt artifact, but nothing is installed and nothing leaves the tab.

Open the playground