Bring-Your-Own Container

A bring-your-own container is a Docker image you build yourself: you write the Dockerfile and a small entrypoint, and RLMesh runs the image. The same image works locally and on the hosted platform. Sandbox helpers like SandboxModel are experimental.

The runnable files live in examples/python/byo_container. There are two images: env/ serves a Gymnasium environment, and model/ serves a policy. Both serve on RLMESH_ADDRESS (default 0.0.0.0:50051).

Environment Container

The env image installs rlmesh and the environment’s dependencies, then runs an entrypoint that serves a Gymnasium environment with EnvServer:

import os

import gymnasium as gym
from rlmesh import EnvServer


def make_env():
    return gym.make("CartPole-v1")


address = os.environ.get("RLMESH_ADDRESS", "0.0.0.0:50051")
EnvServer(make_env(), address).serve()

Build the image and run it, then dial it with rlmesh.RemoteEnv:

docker build -t my-env:latest examples/python/byo_container/env
docker run --rm -p 50051:50051 my-env:latest
import rlmesh

env = rlmesh.RemoteEnv("127.0.0.1:50051")
obs, info = env.reset(seed=0)

The Dockerfile is examples/python/byo_container/env/Dockerfile and the entrypoint is examples/python/byo_container/env/entrypoint.py.

Model Container

The model image serves a policy. Its entrypoint wraps a predict function in Model and serves it on the same address:

import os

from rlmesh.numpy import Model


def load_policy():
    def predict(observation):
        return 0  # always push the cart left

    return predict


address = os.environ.get("RLMESH_ADDRESS", "0.0.0.0:50051")
Model(load_policy()).serve(address)

Build the tag, then drive it against an environment. SandboxModel runs a prebuilt image:// tag directly, with no build step, and opens a route from the environment’s contract:

docker build -t my-model:latest examples/python/byo_container/model
import rlmesh

env = rlmesh.RemoteEnv("127.0.0.1:50051")
model = rlmesh.SandboxModel("image://my-model:latest").against(env)

obs, _ = env.reset()
model.reset()
done = False
while not done:
    action = model.predict(obs)
    obs, reward, terminated, truncated, _ = env.step(action)
    done = terminated or truncated

The same loop drives a model that is already running: swap the construction line for rlmesh.RemoteModel("127.0.0.1:50052").against(env) (a distinct port, since the environment already holds 50051). A prebuilt image:// tag runs from its own baked configuration, so SandboxModel does not inject a bootstrap payload.

The Dockerfile is examples/python/byo_container/model/Dockerfile and the entrypoint is examples/python/byo_container/model/entrypoint.py.

Both Sides in a Sandbox

The same drive loop runs when RLMesh owns both containers. A SandboxEnv builds the environment container from a Gymnasium or Hugging Face source, and a SandboxModel runs your prebuilt image:// tag. A try/finally stops both owned containers when the run ends:

import rlmesh

env = rlmesh.SandboxEnv("CartPole-v1", packages=["gymnasium==1.3.0"], imports=["gymnasium"])
model = rlmesh.SandboxModel("image://my-model:latest").against(env)
try:
    obs, _ = env.reset()
    model.reset()
    done = False
    while not done:
        action = model.predict(obs)
        obs, reward, terminated, truncated, _ = env.step(action)
        done = terminated or truncated
finally:
    model.close()
    env.close()

A sandboxed environment is built from a source, so it takes a Gymnasium id or gym:///hf:// reference rather than an image:// tag; the bring-your-own image:// path is for the model. See Sandbox Examples for the environment side.

Version Pinning

The protocol handshake pins a provisional workflow edition and fails closed. Until the 2026.06 edition seals at the final 0.1.0, a hand-built container’s rlmesh build must match the host that drives it, so pin the same rlmesh version in your Dockerfile as the host. To run on the hosted platform, docker push the tag to a registry the platform can reach; it runs the identical image.