Compose Services
Compose Services
AsyncSandboxCompose takes a Compose YAML file, validates its requirements,
and starts each service through the sandbox API. It manages dependency order,
health checks, service aliases, and shared volumes.
Start two services
Install NeMo Gym’s sandbox extra for the provider SDKs. The adapter reads YAML
with Gym’s existing PyYAML dependency. No Docker daemon, Compose executable, or
additional Python packages are required on the creating server, including Slurm
nodes. The adapter does not inspect images or contact an image registry; the
sandbox provider pulls the configured images.
Configure an OpenSandbox provider whose
sandbox IPs can reach each other. The example images run as root so the provider
can install service names in /etc/hosts.
Save this as compose.yaml:
Save this as run_compose.py. Set OPENSANDBOX_DOMAIN and
OPENSANDBOX_API_KEY for your deployment, then run python run_compose.py:
The adapter waits for peer to become healthy before starting main.
main reaches peer directly by service name; this example needs no port
forwarding. Leaving the context stops both services and closes the provider.
Each entry in collection.services supports the normal AsyncSandbox API,
including file transfer and endpoint(port).
Pass the collection to another server
Inside the creating server’s active collection context, serialize all named services into a JSON-compatible payload:
Send payload in your server request. The receiving server supplies its own
provider configuration and reconnects without the YAML file or provisioning new sandboxes:
This uses the existing ConnectableProvider interface for every service.
Working directories are preserved. scope is forwarded to providers that mint
leases; OpenSandbox ignores it. Connection configuration is supplied separately.
Keep the creating server’s collection context alive until the receiving server
finishes. The creator owns service processes, forwarding tasks, and managed-volume
cleanup, and must also stop its collection. A connected collection’s stop()
uses the provider’s normal sandbox-close semantics; on OpenSandbox, it deletes
the connected service sandboxes. This is access to a running collection, not
transfer of lifecycle ownership.
Input and deployment configuration
On start(), the adapter loads the file with yaml.safe_load and validates its
supported runtime requirements before creating services. It does not run Compose
normalization, interpolate environment variables, read .env files, or merge
Compose files. Resolve these inputs upstream.
Use mappings for environment and depends_on, and long-form mappings for
ports and volumes. Supply byte counts for mem_limit and shm_size, and
resolved absolute bind paths. Strings are passed literally, including $ and
$$; write shell variables as $VAR rather than escaping them for Compose.
The YAML must reference prebuilt images and contain no build entries. This is
an adapter for launching task services through the sandbox API, with a supported
subset of Compose behavior.
Resolve each service’s startup command upstream and include it as entrypoint,
command, or both in the YAML. The adapter concatenates these arguments and
rejects services without a command before provisioning. It does not inherit
startup defaults from the image. Upstream preparation must also supply any
required working_dir and expose/ports values.
Health checks come from YAML and CMD-SHELL uses /bin/sh. Image environment
variables remain the sandbox runtime’s responsibility; YAML environment values
are passed to the API. Images need a POSIX shell and basic utilities. Lifecycle
and health commands use the image’s default user unless YAML overrides it.
Host-file configuration requires root access by default.
Deployment-specific settings remain configurable:
service_specs={name: SandboxSpec(...)}supplies sandbox TTL, resources and provider options. Compose values override corresponding spec fields.timeout_sbounds startup;poll_interval_scontrols startup polling.volume_init_imageselects the shared-volume helper image.
Networking and shared storage
Providers must implement SupportsSandboxNetwork, including for single-service
collections. OpenSandbox requires networking.enabled=True and mutually reachable
sandbox IPs. Service names and aliases are installed in each sandbox’s /etc/hosts.
Client-facing proxy endpoints cannot substitute for direct service communication.
For network_mode: service:<name>, OpenSandbox can opt into TCP loopback
forwarding with networking.loopback_forwarding=True. This forwards the target’s
YAML-declared TCP ports on both 127.0.0.1 and ::1. It provides the localhost
connectivity used by these services; it does not create a shared Linux network
namespace or forward UDP, undeclared ports, or reverse connections. Providers
must implement SupportsSandboxPortForwarding. The forwarding interpreter is
configurable through networking.python_executable (default python3).
Forwarding requires operations.background_exec=True. If the image lacks the
interpreter, networking.setup_command can install it before listeners start;
setup failures stop collection startup.
cap_add and shm_size require SupportsSandboxRuntimeRequirements.
OpenSandbox’s runtime_requirements.capability_probes maps capability names to
commands that verify support already provided by the deployment. The adapter
does not grant kernel capabilities. The validation hook can return metadata that
the adapter includes when creating each service.
For OpenSandbox deployments that allocate shared memory from create metadata, set:
Compose’s shm_size byte count becomes the value of that metadata key. This takes
precedence over a service label or spec metadata for the same key. After creation,
the provider checks the allocation without root; a mismatch fails startup. No
remount or additional capability is needed.
On cell-3, SandboxSpec(metadata={"nemo.nvidia.com/shm": "true"}) requests half the
sandbox memory limit, while "8Gi" requests an explicit size.
For example, a 16 GiB sandbox with "true" gets 8 GiB of /dev/shm. The tmpfs is
charged to the sandbox’s memory limit. Inspect it with df -h /dev/shm.
For Compose, use an explicit shm_size that fits the sandbox memory limit; a
server-side allocation below the requested size will fail the provider’s check.
The deployed server may report a mount capacity larger than the sandbox memory
limit; the mount capacity does not grant additional memory.
The metadata key is deployment configuration, not built into the adapter.
Volumes require SupportsSandboxSharedStorage. Configure OpenSandbox with
shared_storage={"host_path": "/mnt/shared/compose", "metadata": {}}, using a path
backed by storage shared across sandbox nodes and allowed by the server. Metadata
can supply deployment-specific placement settings. No cluster or EFS paths are
built into the adapter.
Unmapped named volumes use a unique directory for the collection. Empty volumes
receive the image mount target’s contents unless volume.nocopy is set. These
volumes are ephemeral: stopping the collection deletes its managed data.
For existing data, supply volume_sources={source: relative_shared_path}. Keys
are resolved absolute bind source paths or Compose named-volume keys; values
are existing directories beneath the configured shared root. These directories
are preserved on cleanup. Local bind data is not uploaded automatically.
Read-only mounts remain read-only.
Optional Gym-specific extensions
x-sandbox is a Gym adapter extension, not a standard Compose service option.
Docker Compose permits custom x- fields
and ignores their contents; only this adapter interprets the options below.
The basic example above does not need them. These options support non-root
services whose sandbox deployment cannot provide normal service-name resolution.
A service that requires no injected host names can explicitly set
x-sandbox: {hosts: []} in its YAML. This allows a non-root service to run without
modifying /etc/hosts; other services still receive its alias. It opts that
service out of peer-name resolution and must not be used when its workload needs
those names.
For services that connect through environment URLs, explicitly select variables whose service hostname should resolve to the sandbox IP:
Only selected URLs are rewritten, for the service process and its health checks. The URL must name a Compose service; credentials, port, path, query and fragment are preserved. This avoids host-file writes for non-root services. It does not provide DNS for other commands or rewrite hostnames embedded elsewhere.
Supported scope
Supported fields include upstream-resolved commands, entrypoints, environment, working directory,
users, CPU and memory limits, labels, default-network aliases, TCP endpoints,
shared volumes, health checks, and all three required dependency conditions:
service_started, service_healthy, and service_completed_successfully.
Unsupported requirements fail explicitly, including builds, custom networks,
unsupported network modes, restart policies, optional dependencies,
user:group, fixed published ports, host IP bindings, and volume drivers or unsupported mount options.