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:
- 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.
- Node 26 and the machine-agent sidecar — the in-container HTTP service that executes shell commands, synthesizes desktop input, and captures screenshots and recordings.
- Desktop-drive userspace —
xdotool,wmctrl,xclip,maim,scrot,imagemagick,ffmpeg,x11-utils, and the AT-SPI accessibility stack (theread_a11yaction returns501when the accessibility bindings are unavailable). ripgrep, which backs content search inside the container — this is whatfs_searchruns against amachine://source; the in-container agent falls back togreponly if it fails.- 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.
| Variable | Default | Purpose |
|---|---|---|
NEURALIS_MACHINE_IMAGE | neuralisapp/webtop-ubuntu-xfce:dev | Webtop image tag |
NEURALIS_MACHINE_DOCKER_SOCKET | /var/run/docker.sock | Docker daemon socket |
NEURALIS_MACHINE_IDLE_MINUTES | 30 | Idle window after which a machine is stopped (never deleted) |
NEURALIS_MACHINE_RECORDING_TTL_HOURS | 24 | Retention window for screen recordings; 0 disables the sweep |
NEURALIS_MACHINE_DESKTOP_VARIANT | ubuntu-xfce | Reported variant string |
NEURALIS_MACHINE_SIDECAR_PORT | 9400 | In-container machine-agent HTTP port |
NEURALIS_MACHINE_SECCOMP_UNCONFINED | false | Run 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_ADDRESS | localhost | Host 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.
| Key | Default | Range | Applies |
|---|---|---|---|
machineSpawnSmokeSeconds | 60 | 15–300 | Next spawn — the CDP + sidecar smoke window |
machineBatchMaxSteps | 25 | 1–50 | Next call — child actions per machine_use batch |
machineBatchBudgetMs | 150000 | 5000–300000 | Next call — wall-clock budget for one batch |
machineReadMaxCharsDefault | 8000 | 500–120000 | Next read — default cap when maxChars is omitted |
machineReadDomMaxChars | 12000 | 1000–120000 | Next read — HTML/DOM truncation cap |
machineNavTimeoutMs | 30000 | 1000–60000 | Next call — default goto navigation timeout |
machineActionTimeoutMs | 10000 | 500–60000 | Next call — default actionability/wait timeout |
machineExecOutputMaxChars | 8000 | 1000–20000 | Next call — model-visible Webtop shell stdout cap |
machineRecordMaxMs | 30000 | 1000–300000 | Next spawn — recording duration cap |
machineRecordMaxMb | 25 | 1–500 | Next spawn — finished-recording size cap |
machineScreenshotMaxMb | 8 | 1–64 | Next spawn — desktop screenshot payload cap |
machineExecTimeoutCeilingMs | 300000 | 5000–600000 | Next spawn — Webtop ceiling over explicit execute.timeout; omission keeps the sidecar default |
machineExecOutputMaxBytes | 131072 | 16384–1048576 | Next spawn — in-container stdout/stderr accumulation cap |
machineFsReadMaxBytes | 2097152 | 65536–16777216 | Next spawn — cap on one sidecar filesystem read |
machineSidecarBodyLimitMb | 4 | 1–64 | Next spawn — request-body cap, so the largest file writable into the desktop |
machineStreamCssCursor | true | — | 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=1removes 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'sdatazone 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/configprofile is kept). When Neuralis runs in a container against the host's Docker daemon, this bind source is translated from the in-containerNEURALIS_HOMEto the real host path inNEURALIS_HOST_HOME, so the mount lands on the actual host filesystem. - Memory. Webtop containers run with a 512 MB
/dev/shmallocation (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.