A Python client library for Enroot, providing a docker-py inspired interface for managing Enroot containers and images.
enroot-py offers a Pythonic API to interact with Enroot, NVIDIA's container runtime for HPC environments. It provides a familiar interface similar to docker-py, making it easy to manage containers, pull images, and execute commands within Enroot containers.
- Image Management: Pull, list, and retrieve Docker images converted to Enroot's
.sqshformat - Container Operations: Create, start, stop, remove, and execute commands in containers
- Port Mapping: Automatic port allocation and mapping for containerized services
- Environment Variables: Pass environment variables to containers
- Resource Limits: CPU and memory limits via
systemd-run(when available) - Docker Compatibility: Automatically imports Docker images if not found locally
- Python 3.8 or higher
- Enroot installed and available in your
PATH - Linux operating system (Enroot is Linux-only)
This library is not yet packaged on PyPI. To use it, clone the repository and add it to your PYTHONPATH:
git clone https://github.com/your-username/enroot-py.git
cd enroot-py
export PYTHONPATH=$PYTHONPATH:$(pwd)Alternatively, you can install it in development mode:
pip install -e .from enroot.client import from_env
client = from_env()
# Check if Enroot is available
if client.ping():
print("Enroot is running!")Pull a Docker image and convert it to Enroot's .sqsh format:
# Pull with explicit tag
image = client.images.pull("ubuntu", "20.04")
print(f"Pulled image: {image.id}")
# Pull latest tag (default)
image = client.images.pull("python", "3.9")
# Pull from a custom registry
image = client.images.pull("my-image", "latest", registry_host="registry.example.com")images = client.images.list()
for image in images:
print(f"Image: {image.id}, Tags: {image.tags}")image = client.images.get("ubuntu+20.04") # Use '+' instead of '/' or ':'
print(f"Found image: {image.id}")container = client.containers.run(
"ubuntu:20.04", # Image name (will be imported if needed)
command="sleep 3600", # Command to run
name="my-container", # Optional container name
detach=True, # Required: must be True
ports={"8080/tcp": "8080"}, # Port mapping (container_port: host_port)
environment={ # Environment variables
"MY_VAR": "my_value",
"DEBUG": "1"
},
cpu_count=2.0, # Optional: CPU limit (requires systemd-run)
mem_limit="2G" # Optional: Memory limit (requires systemd-run)
)
print(f"Started container: {container.name}")
print(f"Status: {container.status}")Note: detach=True is required. Non-detached mode is not currently supported.
You can also use local .sqsh files directly:
container = client.containers.run(
"/path/to/image.sqsh",
command="bash",
detach=True
)containers = client.containers.list()
for container in containers:
print(f"- {container.name} ({container.status})")container = client.containers.get("my-container")
print(f"Container status: {container.status}")
print(f"Container attributes: {container.attrs}")result = container.exec_run("echo 'Hello from container!'")
print(f"Exit code: {result.exit_code}")
print(f"Output: {result.output}")
# Execute with a list of arguments
result = container.exec_run(["ls", "-la", "/tmp"])# Stop (kill) a container
container.kill()
# Remove a container
container.remove()
# Force remove a container
container.remove(force=True)# Reload status from Enroot
container.reload()
# Get current status
status = container.status # "running" or "exited"
# Get container attributes (including port mappings)
attrs = container.attrsENROOT_HOME: Override the default Enroot home directory (default:~/.cache/enroot)ENROOT_IMAGES_PATH: Override where pulled.sqshimages are stored (default:<ENROOT_HOME>/images)ENROOT_DEBUG: Set to"1"to enable debug logging (shows Enroot command execution)ENROOT_ASYNC: Set to"1"to make the synchronousrun_enroothelper transparently use the async backend when no event loop is already runningXDG_CACHE_HOME: Used for cache directory ifENROOT_HOMEis not set
When you specify ports in containers.run(), the library automatically allocates a free port on the host and maps it to the container port. The allocated port is available in the container's attrs:
container = client.containers.run(
"nginx",
detach=True,
ports={"80/tcp": "80"}
)
# Access port mapping
port_mapping = container.attrs["NetworkSettings"]["Ports"]
# The actual host port is stored hereThe allocated port is also set as the PORT environment variable inside the container.
CPU and memory limits are applied using systemd-run when available:
container = client.containers.run(
"my-image",
detach=True,
cpu_count=1.5, # 1.5 CPUs
mem_limit="512M" # 512 MB memory limit
)Note: Resource limits require systemd-run to be available. If it's not found, a warning is issued and the limits are ignored.
Main client class for interacting with Enroot.
ping() -> bool: Check if Enroot is availableimages: Access to image operations (Imagesinstance)containers: Access to container operations (Containersinstance)
Image management operations.
pull(repository: str, tag: str | None = None, registry_host: str = "", directory: str | None = None) -> Image: Pull and import a Docker image. Whenregistry_hostis empty (the default), Enroot uses its built-in default registry; pass e.g."registry.example.com"to override.list() -> List[Image]: List all available imagesget(ref: str) -> Image: Get a specific image by reference
Container management operations.
run(image, command=None, name=None, timeout=120, detach=False, remove=False, ports=None, mount=None, environment=None, cpu_count=None, mem_limit=None, cpus=None, **_) -> Container: Create and start a container (detach=Trueis required).create(image, command=None, name=None, ports=None, mount=None, environment=None, cpu_count=None, mem_limit=None, cpus=None, timeout=120, **_) -> Container: Create a container without starting it (similar todocker create). The optionalcommandis stashed and used as the default forContainer.start(...).create_async(...) -> Container: Coroutine variant ofcreate.list() -> List[Container]: List all containersget(ident: str) -> Container: Get a container by name
Represents a single container instance.
name: str/id: str/short_id: str: Container identifiers (Enroot uses the name as the id).status: str: Container status ("created","running", or"exited").attrs: Dict: Container attributes (similar to Docker API).pids: List[int]: Cached in-container PIDs fromenroot list --fancy.reload()/reload_async(): Refresh state fromenroot list.start(command=None, environment=None, mount=None, cpu_count=None, mem_limit=None, cpus=None, timeout=30, **_): Start a container previously created withcontainers.create(...).start_async(...): Async variant ofstart.stop(timeout=10, **_): Gracefully stop and remove the container.kill(**_)/kill_async(timeout=10, **_): Forcefully stop and remove.remove(force=False, **_): Remove the container viaenroot remove.exec_run(cmd, *, stdout=True, stderr=True, demux=False, workdir=None, user=None, environment=None, detach=False, mem_limit=None, mem_guard_limit=None, mem_guard_poll_interval=1.0, timeout=None, **_) -> ExecResult: Execute a command in the container. Supports host-sidetimeout(raisesenroot.errors.TimeoutErroron exit 124), prlimit-basedmem_limit, and psutil-based RSS capmem_guard_limit(raisesenroot.errors.MemGuardErroron exit 137).exec_run_async(...) -> ExecResult: Async variant ofexec_run.copy_to(local_path, container_path)/put_archive(path, data): Copy files into a running container.
Represents a container image.
id: str: Image ID (path to .sqsh file)tags: List[str]: Image tags
- Detached Mode Only: The
detachparameter must beTrue. Non-detached containers are not supported. - No Logs: The
logs()method returns an empty bytes object. Enroot doesn't provide a built-in logging mechanism. - Resource Limits: CPU and memory limits require
systemd-runto be available on the system. - Port Mapping: All ports are mapped to a single automatically-allocated free port. Individual port mappings are not fully supported.
The test suite uses pytest. Install the dev extras and run it:
pip install -e ".[dev]"
pytestSlow integration checks that pull large images are gated behind the slow marker:
pytest -m slowContributions are welcome! Please feel free to submit a Pull Request.
MIT License (see pyproject.toml for details)