xcodon-runtime
A rootless container runtime for Docker images. It runs the images that coala and coala-runtime use on hosts without Docker and without root.
Two engines, chosen automatically:
- ns: kernel user namespaces plus overlayfs, no helper binary. Native speed. Needs a Linux kernel 5.11 or newer with unprivileged user namespaces enabled. Most workstations, cloud VMs, and current HPC nodes qualify.
- proot: a vendored static PRoot runs the container under ptrace. Works on any Linux, slower, and creation copies the image rootfs.
Install
pip install xc-xrunner # or: uv pip install xc-xrunner
xrunner info # shows the engine and probe results
Optional: pip install 'xc-xrunner[zstd]' for zstd-compressed layers.
On aarch64 hosts without user namespaces, provide a PRoot binary with
XCODON_PROOT=/path/to/proot. The command is xrunner. The package on PyPI is xc-xrunner, and the Python module is xcodon_runtime.
Use
xrunner pull python:3.12-slim
xrunner run --rm -v $PWD:/work -w /work python:3.12-slim python -c 'print("hi")'
xrunner create --name dev python:3.12-slim
xrunner start dev
xrunner exec dev pip install numpy # persists in the container's writable layer
xrunner exec dev python -c 'import numpy'
xrunner stop dev && xrunner rm dev
xrunner logs dev # the ns engine's keeper log
xrunner rmi python:3.12-slim # drop a tag, and the image with its last tag
xrunner prune # leftovers: half-built dirs and orphan blobs
xrunner prune --all # also unused layers and day-old exited containers
xrunner run --rm --env-dir $PWD/.xrunner-env python:3.12-slim pip install numpy
xrunner run --rm --env-dir $PWD/.xrunner-env python:3.12-slim python -c 'import numpy'
--env-dir keeps the container's writable layer in a host folder, keyed by
image id, so tools installed in one container are there for the next one.
Delete <env-dir>/<image-id> to reset. coala-runtime uses this through the
XRUNNER_ENV_DIR variable.
Two global flags come before the subcommand: --engine ns|proot forces an
engine, and --home DIR picks the state directory for this one command.
Images already in a local Docker daemon are reused through docker save, so
locally built images work without a registry. xrunner records the daemon's
image id at import. When the daemon's tag later points at another image
(for example after a docker build there), the next run, create, or
build that uses the tag imports it again. --pull never and image ids
skip this check.
Build and commit
xrunner build -t myapp:1 . # runs a Dockerfile subset
xrunner commit -m "installed numpy" dev myapp:2 # snapshot a stopped container
xrunner tag myapp:2 myapp:latest
xrunner build runs each Dockerfile instruction in its own container and
commits one image per step, so unchanged steps are cached. The cache lives
in <home>/build-cache.json. The step images are untagged, and
xrunner prune --all removes untagged images, which empties the cache in
effect. xrunner commit also works on an --env-dir folder instead of a
container: xrunner commit --env-dir $PWD/.xrunner-env --image myapp:1 myapp:2.
Docker-style names work too:
xrunner image inspect myapp:1 # also: image ls, image rm
xrunner docker build -t myapp:1 . # any docker verb xrunner supports
xrunner docker image inspect --format '{{.Id}}' myapp:1
xrunner docker VERB ... takes docker's own verbs and flags for build,
image inspect|ls|rm, images, rmi, tag, pull, run, create,
start, exec, stop, rm, ps, logs, commit, inspect, version, and
info. Other verbs exit with code 125 and a message.
For tools that shell out to a real docker binary directly — running
docker build or docker image inspect as a subprocess, rather than going
through cwltool's --user-space-docker-cmd — xrunner shim install writes
a docker script that forwards every call to xrunner docker:
xrunner shim install --dir ~/.local/bin
export PATH="$HOME/.local/bin:$PATH"
docker build -t myapp:1 .
docker image inspect myapp:1
The shim refuses to install while any real docker is anywhere on PATH,
even outside DIR, unless you pass --force. It also refuses to replace a
DIR/docker that is not an xrunner shim unless you pass --force, and it
never replaces a directory. xrunner also never mistakes its own shim for a
real Docker daemon: image pulls that would reuse a local docker save skip
a shim.
Limits on the Dockerfile subset: no multi-stage builds (a second FROM, or
--from=), no .dockerignore, and no remote ADD/COPY from a URL.
Tools without conda
Agents often install command-line tools with conda create, conda install and
conda run. On a host with no conda, xrunner can answer those calls itself:
xrunner shim install conda --dir ~/.xcodon/shim
export PATH="$HOME/.xcodon/shim:$PATH"
This downloads a pinned micromamba (2.9.0, from conda-forge, checksum-verified) to
<xrunner home>/bin/micromamba-2.9.0/micromamba and writes conda, mamba and
micromamba scripts that forward to xrunner conda.
Offline, pass --micromamba PATH to use a binary you already have.
- Environments live in the project's
.xrunner-env/conda(found from the working directory, or fromXRUNNER_ENV_DIR), else under the xrunner home. Downloads are cached once under the xrunner home. A.xrunner-envfound by searching upward is used only when you own it and no one else can write to it. - Launchers should export
XRUNNER_ENV_DIR=<project>/.xrunner-env, or create<project>/.xrunner-env, before the agent starts. Otherwise an early conda call falls back to the xrunner home: its envs land outside the project,conda env listin the project does not show them, and the agent'srun_shellguardrail refuses their paths. - A relative
-p PATHis always a folder under the working directory, even without a/. - Your own
~/.condaand~/.condarcare never read or written. conda run -n NAME CMDruns CMD with the env on PATH;conda activateis not supported, because it changes the calling shell.- Each env gets
conda-explicit.txt, listing every package URL and checksum, so it can be rebuilt withconda create -p PATH --file conda-explicit.txt. - The shim refuses to install while a real conda, mamba or micromamba is on PATH,
unless you pass
--force. - The shims use the default xrunner home, or
XCODON_RUNTIME_HOMEwhen it is set; they do not remember anxrunner --home Hgiven at install time. Afterxrunner --home H shim install conda ..., also exportXCODON_RUNTIME_HOME=Hwherever the shims run, or they will look for micromamba in the default home instead.
Sandbox activation
xrunner sandbox activate /abs/project/.xrunner-env writes docker, conda,
mamba and micromamba shims into the project's .xrunner-env/bin and prints
the two export lines (PATH first, then XRUNNER_ENV_DIR) that send a shell's
docker and conda calls to xrunner. The Python API is
xcodon_runtime.sandbox.activate(env_dir). opencodon calls it by itself when
its container engine is xrunner, so --container-engine xrunner is enough.
Environment record
xrunner keeps .xrunner-env/environment.json, a per-project record of the images a
project used (id, source, registry digests, and any build Dockerfile) and the
packages each install added, changed or removed.
For each image it also keeps the platform, package counts, and for built images
the packages the build added. .xrunner-env/packages/<image id>.json holds the full
package list of each image the project used, not only the changes.
The record updates automatically after conda changes and when containers stop.
xrunner env show prints it, xrunner env show --json prints the file, and
xrunner env record rebuilds it from what is on disk. Images keep their entries as
history; env record never removes one.
It is a record, not a restore mechanism: it does not recreate images or packages, only describes what happened. Images built before this feature have no Dockerfile on record. On the proot engine each container stop rescans the rootfs copy, which takes about 3-4 s on large images.
Hosts without docker
On a host with no docker daemon, a FROM name that only ever existed in a
local daemon, such as coala-runtime-python:latest, is looked up on Docker
Hub and not found there. Seed such base images once: pull the published
image and give it the local name.
xrunner pull hubentu/coala-runtime-python:latest
xrunner tag hubentu/coala-runtime-python:latest coala-runtime-python:latest
xrunner pull hubentu/coala-runtime-r:latest
xrunner tag hubentu/coala-runtime-r:latest coala-runtime-r:latest
After that, FROM coala-runtime-python:latest uses the stored image and
never goes to the network. xrunner does not map image names itself.
Clusters with shared storage
Overlayfs cannot write its upper layer to NFS, Lustre or GPFS. When the home is on shared storage, the overlay probe fails and xrunner falls back to the slower proot engine. Keep the image store on shared storage and put container folders on a node-local disk:
export XCODON_RUNTIME_HOME=/shared/$USER/xrunner
export XRUNNER_CONTAINER_DIR=${TMPDIR:-/tmp}/xrunner-containers
Images are pulled once into the shared home and read from there. Each container's
writable layer, its keeper log and its locks live under XRUNNER_CONTAINER_DIR.
Containers are then local to one node: xrunner ps on another node does not list
them. xrunner info shows both folders and the engine the probes chose.
The setting does not move an env folder. An env folder keeps a writable layer across
containers, so on the ns engine it must also be on a local disk. Point
XRUNNER_ENV_DIR at node-local storage, or set XCODON_ENGINE=proot to keep it on
shared storage.
Environment variables
| Variable | Meaning |
|---|---|
XCODON_RUNTIME_HOME |
State directory. Default ~/.xcodon/runtime. |
XRUNNER_CONTAINER_DIR |
Container folders. Default <home>/containers. |
XCODON_ENGINE |
ns or proot. Skips probing. |
XCODON_PROOT |
Path to a PRoot binary. |
XCODON_PROOT_ARGS |
Extra PRoot flags, for example -k 5.15.0. |
XCODON_LOG |
info or debug. |
Limits
- One uid inside the container. Every image file is owned by that uid.
chownto another user fails. Setuid binaries do not elevate. - Host network only. No
--net=none, no port mapping. - No cgroups.
--memoryand--cpusare accepted and ignored with a warning. - No GPU passthrough.
- Read-only binds apply to the top mount only; submounts under a bound host path stay writable.
coala
In coala, add one branch to configure_container_runner:
if container_runner == "xrunner":
runtime_context.user_space_docker_cmd = shutil.which("xrunner") or "xrunner"
cwltool then calls xrunner inspect, xrunner pull, and xrunner run with
docker-style flags.
coala-runtime
xcodon-runtime ships xcodon_runtime.coala_adapter.XcodonContainerManager,
which implements coala-runtime's ContainerManager interface. In
coala-runtime, add XRUNNER = "xrunner" to ContainerEngine, return the
adapter from make_container_manager for that value, and try it in
autodetection after Docker and Podman and before Apptainer.
Development
uv venv .venv && . .venv/bin/activate
uv pip install -e ".[dev,zstd]"
python scripts/fetch_proot.py # only if _bin/ is missing
pytest -q # unit + ns + proot on a capable host
XCODON_TEST_NETWORK=1 pytest -m network
Metadata
Release files for xc-xrunner 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| xc_xrunner-0.1.2.tar.gz | 948.9 kB | With provenance | Details for xc_xrunner-0.1.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| xc_xrunner-0.1.2-py3-none-any.whl | Python 3 | none | any | Details for xc_xrunner-0.1.2-py3-none-any.whl |
Total release size: 1.8 MB
Release files / xc_xrunner-0.1.2.tar.gz
| Download URL | xc_xrunner-0.1.2.tar.gz |
|---|---|
| Size | 948.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a6d127bdc789ad51f61954ca42bb8c6d4b16b6cec6b7c0daabfd3e61a5d63bfb
|
|
BLAKE2b-256 checksum How to use checksums |
0ed024a4245d47f5528c90ac02ca3791e1e391088cb2cb437fbee26d9ffb093e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.
Transparency logRelease files / xc_xrunner-0.1.2-py3-none-any.whl
| Download URL | xc_xrunner-0.1.2-py3-none-any.whl |
|---|---|
| Size | 888.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2eb3d9910729c3306ced1cae52a069f33f7359425c46c28ac256e5c0c4b1a312
|
|
BLAKE2b-256 checksum How to use checksums |
a63ffac2a8abba718f87a0cf10bf80b556860b5eabcd63639493d36d8e22eb0e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.
Transparency logSecurity for xc-xrunner 0.1.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for xc_xrunner-0.1.2.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.