Skip to content

Sandbox and Multi-user xunc / xunx

xunc runs one session in Docker; xunx runs a long-lived container per user behind a single gateway. Both share the same image (default xun); see Installation for how to build it.

xunc: container session

xunc does not run an agent itself. It does exactly three things: prepare /workspace, forward environment variables, and start xuns (or the command you specify) inside the container.

xunc                       # temporary workspace in the container, starts the web service
xunc .                     # bind-mount the current directory as /workspace
xunc --copy .              # copy it into /workspace instead (changes never sync back to the host)
xunc --exec "xun"          # run the terminal entrypoint in the container
xunc --exec ""             # empty string, falls back to the image's default CMD
xunc --env "FOO=1,PATH"    # direct assignment + forwarding by name
Argument Type Default Description
mount positional none directory to mount / copy into /workspace
--copy flag off copy instead of bind-mounting
--image str xun image to use
--env str, repeatable [] comma-separated: NAME=VALUE assigns directly, anything else is forwarded from the host by wildcard
--name str xun-<uuid8> container name
--network bridge / host bridge network mode
--port str, repeatable ["18960"] ports published in bridge mode; several may be given comma-separated
--exec str xuns command run inside the container; an empty string falls back to the image's default CMD

Mount and copy

  • Without mount: the container's own working directory (/home/app) becomes the workspace, invisible to the host.
  • Bind mount (default): changes inside the container land directly in the host directory; --userns=host keeps ownership consistent.
  • --copy: copied in with docker cp and owned exclusively by the container; the original host directory is untouched.
  • Either way, config.json, extensions/, and skills/ under the host $XUN_HOME are copied into the container's /.xun; x/ (the xunx user database) and conversation/ (session archives) are skipped.

Environment forwarding

The container pins ENV XUN_HOME=/.xun, so the forwarding rules from the host environment are:

  • XUN_* and _XUN_* are always forwarded; XUN_HOME is explicitly excluded.
  • In --env, NAME=VALUE assigns directly; entries without = are matched against host variables by wildcard; a bare NAME is forwarded as an exact match.
  • --env may be repeated, and the values accumulate.

For example, to bring the host's proxy settings and a custom model endpoint into the container:

export XUN_OPENAI_BASE_URL=https://gateway.internal/v1
xunc . --env "HTTP_PROXY,NO_PROXY,XUN_OPENAI_*"

Ports and time zone

In bridge mode, only the ports listed in --port get -p <port>:<port>; with host networking everything passes straight through. The container's time zone follows the host's /etc/localtime.

xunx: multi-user gateway

xunx maintains one long-lived container per user and serves them all through a single gateway port; the internal container port always starts at 18960.

xunx user-add alice
xunx serve --host 0.0.0.0 --port 18960
xunx shell alice --cwd /workspace
xunx pause alice
xunx resume alice
xunx upgrade --all
xunx user-list

Subcommands

Subcommand Purpose
user-add <user> add a user, generate a random token, and print it once
user-del <user> delete a user and its token, and remove its container on a best-effort basis
user-list list user names and tokens
serve run the multi-user gateway
pause <user> / resume <user> pause / resume a container (state is kept; reached through the gateway address)
upgrade [user …] / --all set a flag; the next reconciliation rebuilds the container from the current image
shell <user> enter the user container's shell or run a given command

serve arguments

Argument Default Description
--host 0.0.0.0 gateway listen address
--port 18960 gateway's external port; 0 lets the system assign one
--port-range 17960-18959 host port range assigned to user containers; must be disjoint from --port
--image xun image; within the range its availability is validated per user-instance
--env [] same forwarding logic as xunc --env

Lifecycle and caveats

  • serve reconciles every 5 seconds: if the user is running, forward to its address; if the user exists but the container is gone, rebuild it; containers that should not exist (user deleted, image replaced, --image changed) are stopped and removed.
  • Gateway tokens and user tokens are both cached for 10 seconds (positive and negative caching alike), so after user-add you wait at most that long for it to take effect.
  • pause only stops, never removes; resume starts it again. Reclamation triggered by user-del or a configuration change removes the container outright.
  • Containers are ephemeral instances: stopping xunx does not stop them, but a Docker daemon restart or xunx upgrade removes them.
  • The gateway validates requests by the Xun-Auth header or ?token=. A user token is a 44-character urlsafe string (exactly the length of an internal session token), so the gateway URL can be handed to users directly.
  • user-add, user-list, and user-del only read and write $XUN_HOME/x/xunx.db; they do not require serve to be running.

What the image contains

docker/Dockerfile is a multi-stage build that produces a runtime with a full toolchain: a Python virtual environment (dependencies resolved from pyproject.toml), Node and the frontend build, the Playwright browsers (system dependencies and Chinese fonts included), a Rust toolchain, ffmpeg / Git / ripgrep and other common commands, plus a dedicated non-root user. Inside the image ENV XUN_HOME=/.xun and XUN_INFO_TOOL_OVERRIDE=1, and the startup command is xuns --host 0.0.0.0 --manage-sessions ..

Containers start with --cap-drop ALL; xunc adds no hardening of its own. If you need stronger isolation, layer your own resource and network limits on top.

  • What runs inside the container is exactly the xun / xuns session.
  • To run extension commands inside a container: xunc --exec "xune stat …", see Extension CLI Entry xune.