Containers
What you’ll build: Two isolated containers running in the same JS process, each with a private filesystem and process tree.
Prerequisites: Filesystem →
What is a container?
Section titled “What is a container?”A fishbowl container is a process group with:
- A private filesystem namespace
- A layered image built from overlay filesystems
- Its own init system and service graph
- A private
/devand/proc
The host kernel manages all processes. Containers cannot see each other’s process trees.
instance.run(cmd) executes a shell command and returns its output. instance.shell() starts an interactive session.
Boot a container
Section titled “Boot a container”import { Unix } from '@fishnet/core'import { stdSystem } from '@fishnet/core/presets'import { nodeRuntime } from '@fishnet/runtime/platform/node'
const image = Unix().use(stdSystem()).build()
// Boot two independent instances from the same imageconst a = await image.boot(nodeRuntime())const b = await image.boot(nodeRuntime())
// Each instance has its own process table and filesystemawait Promise.all([a.run('echo hello from A'), b.run('echo hello from B')])import { Unix } from '@fishnet/core'import { stdSystem } from '@fishnet/core/presets'import { browserRuntime } from '@fishnet/runtime/platform/browser'
const image = Unix().use(stdSystem()).build()
const a = await image.boot(browserRuntime())const b = await image.boot(browserRuntime())
await Promise.all([a.run('echo hello from A'), b.run('echo hello from B')])Layer a custom image
Section titled “Layer a custom image”Each boot gets its own overlay layer — writes from one instance never affect another. You can also bake files into the base image before freezing it.
import { Unix, memoryFS, overlayFS } from '@fishnet/core'import { stdSystem } from '@fishnet/core/presets'import { nodeRuntime } from '@fishnet/runtime/platform/node'
const base = memoryFS()base.writeFile('/etc/motd', 'Welcome to container A')base.freeze()
const upper = memoryFS()
const image = Unix() .use(stdSystem()) .use({ mounts: [{ path: '/', server: overlayFS(upper, base) }] }) .build()
const instance = await image.boot(nodeRuntime())// cat /etc/motd → "Welcome to container A"// Writing to /etc/motd writes to `upper` onlyimport { Unix, memoryFS, overlayFS } from '@fishnet/core'import { stdSystem } from '@fishnet/core/presets'import { browserRuntime } from '@fishnet/runtime/platform/browser'
const base = memoryFS()base.writeFile('/etc/motd', 'Welcome to container A')base.freeze()
const upper = memoryFS()
const image = Unix() .use(stdSystem()) .use({ mounts: [{ path: '/', server: overlayFS(upper, base) }] }) .build()
const instance = await image.boot(browserRuntime())Further reading
Section titled “Further reading”- Container architecture → — Isolation model, namespace fork semantics
- Fileservers → — overlayFS, whiteouts, copy-on-write
- Init system → — Service graph, supervisor, restart policies