@machine-core

Deployment

Container requirements, runtime configuration, and operational notes.

Machine-core spawns one Webtop container per machine session through the Docker daemon. This page covers what the package needs from the deployment: the image, the socket, the environment, and the resource profile.

Container image

Sessions run the image named by NEURALIS_MACHINE_IMAGE (default neuralisapp/webtop-ubuntu-xfce:dev). The image is built on the LinuxServer Webtop ubuntu-xfce base (Selkies 2 + XFCE + nginx), pinned by digest, and bakes in:

  1. Chromium, launched with its DevTools port bound to loopback only — Chromium 115+ ignores a bind-address override — so the bridge in item 5 republishes it on the container's own interface and the browser is drivable over Playwright/CDP from the moment the desktop is up.
  2. Node 26 and the machine-agent sidecar — the in-container HTTP service that executes shell commands, synthesizes desktop input, and captures screenshots and recordings.
  3. Desktop-drive userspace — xdotool, wmctrl, xclip, maim, scrot, imagemagick, ffmpeg, x11-utils, and the AT-SPI accessibility stack (the read_a11y action returns 501 when the accessibility bindings are unavailable).
  4. ripgrep, which backs content search inside the container — this is what fs_search runs against a machine:// source; the in-container agent falls back to grep only if it fails.
  5. The CDP bridge — served by the in-container agent itself, forwarding the container interface's CDP port to Chromium's loopback DevTools endpoint so Playwright/CDP can reach it from outside. It answers only the container's own sidecar token, on the DevTools HTTP requests and the WebSocket upgrade alike, so another container on the same network cannot drive this browser.

The first beta's bundled desktop is Ubuntu + XFCE. Operators may set machineImage to their own compatible derivative; that image must provide the authenticated machine-agent sidecar and desktop/CDP services described above. A plain upstream Webtop image does not provide those Neuralis services.

Docker socket access

The package talks to Docker through the socket at NEURALIS_MACHINE_DOCKER_SOCKET (default /var/run/docker.sock). The default compose file bind-mounts the host socket into the Neuralis container so machine-core can spawn per-user Webtops.

The Docker socket is privileged

Treat socket access as root-equivalent on the host. Keep machine containers off any publicly routable network and route desktop/browser streams only through the authenticated package routes — the stream credential is injected server-side and never reaches the user's browser.

If the socket is unreachable (or dockerode is not installed) Neuralis still boots cleanly in zero-machine mode: machine tools return a structured "docker unavailable" error and the widget surfaces the reason instead of failing silently.

Runtime environment

Configuration arrives on two tracks. Five keys are platform config with an environment fallback: image, desktop variant, idle window, recording TTL and key-combo exceptions are admin-editable (Config → Platform Settings, category Machine) and fall back to the environment variable when unset — an admin edit applies at the next spawn, reap, sweep or key check without a restart. The infrastructure rows in the table below (socket, ports, network, seccomp) are env-only by design.

VariableDefaultPurpose
NEURALIS_MACHINE_IMAGEneuralisapp/webtop-ubuntu-xfce:devWebtop image tag
NEURALIS_MACHINE_DOCKER_SOCKET/var/run/docker.sockDocker daemon socket
NEURALIS_MACHINE_IDLE_MINUTES30Idle window after which a machine is stopped (never deleted)
NEURALIS_MACHINE_RECORDING_TTL_HOURS24Retention window for screen recordings; 0 disables the sweep
NEURALIS_MACHINE_DESKTOP_VARIANTubuntu-xfceReported variant string
NEURALIS_MACHINE_SIDECAR_PORT9400In-container machine-agent HTTP port
NEURALIS_MACHINE_SECCOMP_UNCONFINEDfalseRun sessions with seccomp=unconfined (Chromium compatibility on legacy kernels)
NEURALIS_MACHINE_SHARED_NETWORK(detected)Override the user-defined Docker network shared with Neuralis
NEURALIS_MACHINE_HOST_ADDRESSlocalhostHost address used in published-port mode (no shared network)
NEURALIS_MACHINE_KEY_COMBO_EXCEPTIONS(none)Comma-separated key combos to remove from the host-side desktop-key denylist. The in-container agent enforces the same list independently and does not receive this value, so a combo named here is still refused inside the machine

The remaining keys are admin-config only — they have no environment form. Some are read on the host at the next call or spawn; the rest are snapshotted into the container at spawn, so a running desktop keeps the caps it was born with until it is replaced. The Applies column says which.

KeyDefaultRangeApplies
machineSpawnSmokeSeconds6015–300Next spawn — the CDP + sidecar smoke window
machineBatchMaxSteps251–50Next call — child actions per machine_use batch
machineBatchBudgetMs1500005000–300000Next call — wall-clock budget for one batch
machineReadMaxCharsDefault8000500–120000Next read — default cap when maxChars is omitted
machineReadDomMaxChars120001000–120000Next read — HTML/DOM truncation cap
machineNavTimeoutMs300001000–60000Next call — default goto navigation timeout
machineActionTimeoutMs10000500–60000Next call — default actionability/wait timeout
machineExecOutputMaxChars80001000–20000Next call — model-visible Webtop shell stdout cap
machineRecordMaxMs300001000–300000Next spawn — recording duration cap
machineRecordMaxMb251–500Next spawn — finished-recording size cap
machineScreenshotMaxMb81–64Next spawn — desktop screenshot payload cap
machineExecTimeoutCeilingMs3000005000–600000Next spawn — Webtop ceiling over explicit execute.timeout; omission keeps the sidecar default
machineExecOutputMaxBytes13107216384–1048576Next spawn — in-container stdout/stderr accumulation cap
machineFsReadMaxBytes209715265536–16777216Next spawn — cap on one sidecar filesystem read
machineSidecarBodyLimitMb41–64Next spawn — request-body cap, so the largest file writable into the desktop
machineStreamCssCursortrue—Next spawn — the browser draws the mouse pointer instead of the server compositing it into the video
machineStreamFramerate(empty)—Next spawn — stream framerate, as a range (24-60) or a fixed value
machineStreamH264Crf(empty)—Next spawn — video quality factor for every encoder, as a range or a fixed value (lower is better quality)
machineStreamEncoder(empty)—Next spawn — comma-separated list of video encoders the desktop may offer

The last three behave differently from every other key, and the difference is worth knowing before you set one: leaving them empty keeps the image's own range AND the picker inside the desktop's own settings panel, while a fixed value locks that picker to what you set. Empty is therefore not "unconfigured" — it is the setting that leaves the choice with the person watching the screen. Set one only when you want every session pinned, for example to cap bandwidth on a constrained link. The value is passed through verbatim and validated by the desktop image, which logs an invalid value and falls back to its own default.

Networking

When Neuralis itself runs as a container on a user-defined Docker network, machine-core detects that network and attaches Webtop containers to it, so the sidecar and CDP ports are reachable container-to-container without any published ports. Without a shared network it falls back to published-port mode: the container's stream, CDP, and sidecar ports are published on ephemeral ports of the host's loopback interface (127.0.0.1, never every interface) and reached via NEURALIS_MACHINE_HOST_ADDRESS.

The desktop stream is served on the platform's companion HTTP server (the MCP HTTP port, default 3101): the workspace widget points its iframe at /machine/<sessionKey>/stream/… there, so the stream client's relative asset URLs and its WebSocket upgrade stay on the same origin as the iframe document, and the machine WebSocket server takes over the upgrade. The equivalent session/:key/stream/* package route proxies the same stream for direct API callers. All three surfaces run the request through the same project → feature → owner → ready authorization ladder and inject the stream credential server-side.

Storage and resources

  • Profile volume. Each machine gets a named Docker volume mounted at /config, so logins, installed apps, and files persist across stops, starts and even a container delete. DELETE /sessions/container?purge=1 removes it once the machine is stopped, and that is the only thing that does.
  • Recordings bind mount. The in-container recordings directory is bind-mounted from the machine's own project's data directory on the host, which makes disk-mode recordings addressable as data://machine-core/recordings/<file> in that project and keeps them out of every other project's reach. Inside the project they are project data, so any member who may read the project's data zone can open them. A machine created before this per-project layout keeps running until it is stopped, but will not start again: delete its container to re-create it (the /config profile is kept). When Neuralis runs in a container against the host's Docker daemon, this bind source is translated from the in-container NEURALIS_HOME to the real host path in NEURALIS_HOST_HOME, so the mount lands on the actual host filesystem.
  • Memory. Webtop containers run with a 512 MB /dev/shm allocation (Chromium needs it); budget roughly one desktop-class workload per concurrently active user. The idle window keeps the steady-state footprint proportional to RUNNING machines, not to the user count — a stopped machine costs disk, not memory.
  • Audit and logs. Machine audit records are appended to the data:// zone of the project each action belongs to (audit/machine-audit.jsonl), covered by the package's URI policy baseline, which keeps the trail unreadable for agents and members — only project owners and admins read it. The package's own log, the audit of a project being permanently deleted, and records that name no valid project are platform data, kept in the platform's own data directory and read only with platform audit access.

Session lifecycle in operation

Machines are created lazily — the first widget open or tool call for a source triggers the spawn — and touched on every tool call. A machine idle past NEURALIS_MACHINE_IDLE_MINUTES is stopped, never removed: the container, everything installed into it and the profile volume all survive, and the next start is a docker start measured in seconds. Running machines also survive a Neuralis restart — they are adopted again at boot. Owners manage all of this from the machine widget, the session routes, or the manage-machine-sessions skill — see machine-core skills.

Machine containers are labelled into a neuralis-machines Docker Compose group so they stay together in Docker Desktop. That grouping is label-derived, so a group-wide action there (or a file-less docker compose -p neuralis-machines down) stops and removes every machine container at once; profile volumes survive unless -v is passed.

Each container CREATE mints a fresh bearer token for the in-container agent, and an ADOPTED container keeps the token it booted with, so a stop and a later start never invalidate it. If a container is restarted out of band (for example a host resume) or re-created outside the manager, the shell and filesystem clients self-heal: a single authentication failure makes them re-adopt the live session and rebuild against the current token before retrying once, so Webtop shell and the machine:// filesystem keep working alongside an already-attached browser session. A genuine authentication failure still surfaces after that single retry.

On this page