2. Deploying an OpenEnv environment

April 18, 2026 · View on GitHub

This section covers deploying OpenEnv environments locally, on clusters, and on Hugging Face Spaces.

Contents:

HF Spaces are the infrastructure for OpenEnv environments

Every HF Space provides three things that OpenEnv environments need:

ComponentWhat it providesHow to accessUsed as
ServerRunning environment endpointhttps://<username>-<space-name>.hf.spaceAgent and Public API
RepositoryInstallable Python packagepip install git+https://huggingface.co/spaces/<username>-<space-name>Code and client
RegistryDocker container imagedocker pull registry.hf.space/<username>-<space-name>:latestDeployment

This means a single Space deployment gives you all the components you need to use an environment in training.

1. Server: A running environment endpoint

When you deploy to HF Spaces, your environment runs as a server. The client connects via WebSocket (/ws) for a persistent session:

from echo_env import EchoEnv, EchoAction

# Connect directly to the running Space (WebSocket under the hood)
# Async (recommended):
async with EchoEnv(base_url="https://openenv-echo-env.hf.space") as client:
    result = await client.reset()
    result = await client.step(EchoAction(message="Hello"))

# Sync (using .sync() wrapper):
with EchoEnv(base_url="https://openenv-echo-env.hf.space").sync() as client:
    result = client.reset()
    result = client.step(EchoAction(message="Hello"))

Endpoints available:

EndpointProtocolDescription
/wsWebSocketPersistent session (used by client)
/healthHTTP GETHealth check
/resetHTTP POSTReset environment (stateless)
/stepHTTP POSTExecute action (stateless)
/stateHTTP GETGet current state
/docsHTTP GETOpenAPI documentation
/webHTTP GETInteractive web UI

Note: The Python client uses the /ws WebSocket endpoint by default. HTTP endpoints are available for debugging or stateless use cases.

Example: Check if a Space is running

curl https://openenv-echo-env.hf.space/health
# {"status": "healthy"}

2. Repository: Installable Python package

Every Space is a Git repository. OpenEnv environments include a pyproject.toml, making them pip-installable directly from the Space URL.

# Install client package from Space
pip install git+https://huggingface.co/spaces/openenv/echo-env

This installs:

  • Client class (EchoEnv) — Handles HTTP/WebSocket communication
  • Models (EchoAction, EchoObservation) — Typed action and observation classes
  • Utilities — Any helper functions the environment provides

After installation:

from envs.echo_env import EchoEnv, EchoAction, EchoObservation

# Now you have typed classes for the environment
action = EchoAction(message="Hello")

3. Registry: Docker container image

Every Docker-based Space has a container registry. You can pull and run the environment locally.

# Pull the image
docker pull registry.hf.space/openenv-echo-env:latest

# Run locally on port 8001
docker run -d -p 8001:8000 registry.hf.space/openenv-echo-env:latest

Find the registry URL for any Space:

  1. Go to the Space page (e.g., openenv/echo-env)
  2. Click (three dots) → "Run locally"
  3. Copy the docker run command

Choosing an access method

MethodUse whenProsCons
ServerQuick testing, low volumeZero setupNetwork latency, rate limits
RepositoryNeed typed classesType safety, IDE supportStill need a server
DockerLocal dev, high throughputFull control, no networkRequires Docker

Typical workflow:

import asyncio
from echo_env import EchoEnv, EchoAction

async def main():
    # Development: connect to remote Space
    async with EchoEnv(base_url="https://openenv-echo-env.hf.space") as client:
        result = await client.reset()

    # Production: run locally for speed
    # docker run -d -p 8001:8000 registry.hf.space/openenv-echo-env:latest
    async with EchoEnv(base_url="http://localhost:8001") as client:
        result = await client.reset()

    # Or let the client manage Docker for you
    client = await EchoEnv.from_env("openenv/echo-env")  # Auto-pulls and runs
    async with client:
        result = await client.reset()

asyncio.run(main())

# For sync usage, use the .sync() wrapper:
with EchoEnv(base_url="http://localhost:8001").sync() as client:
    result = client.reset()

Reference: HF Spaces Documentation | Environment Hub Collection

Local Development with Uvicorn

The fastest way to iterate on environment logic is running directly with Uvicorn.

Clone and run the environment locally

# Clone from HF Space
git clone https://huggingface.co/spaces/burtenshaw/openenv-benchmark
cd openenv-benchmark

# Install in editable mode
uv sync

# Start server
uv run server

# Run isolated from remote Space
uv run --isolated --project https://huggingface.co/spaces/burtenshaw/openenv-benchmark server

Uvicorn directly in python

# Full control over uvicorn options
uvicorn benchmark.server.app:app --host "$HOST" --port "$PORT" --workers "$WORKERS"

# With reload for development
uvicorn benchmark.server.app:app --host 0.0.0.0 --port 8000 --reload

# Multi-Worker Mode For better concurrency:
uvicorn benchmark.server.app:app --host 0.0.0.0 --port 8000 --workers 4
FlagPurpose
--reloadAuto-restart on code changes
--workers NRun N worker processes
--log-level debugVerbose logging

Docker Deployment

Docker provides isolation and reproducibility for production use.

Run the environment locally from the space

# Run the environment locally from the space
docker run -d -p 8000:8000 registry.hf.space/openenv-echo-env:latest

Build Image

# Clone from HF Space
git clone https://huggingface.co/spaces/burtenshaw/openenv-benchmark
cd openenv-benchmark

# Using OpenEnv CLI (recommended)
openenv build -t openenv-benchmark:latest

# Or with Docker directly
docker build -t openenv-benchmark:latest -f server/Dockerfile .

Run Container

# Basic run
docker run -d -p 8000:8000 my-env:latest

# With environment variables
docker run -d -p 8000:8000 \
    -e WORKERS=4 \
    -e MAX_CONCURRENT_ENVS=100 \
    my-env:latest

# Named container for easy management
docker run -d --name my-env -p 8000:8000 my-env:latest

Connect from Python

import asyncio
from echo_env import EchoEnv, EchoAction

async def main():
    # Async usage (recommended)
    async with EchoEnv(base_url="http://localhost:8000") as client:
        result = await client.reset()
        result = await client.step(EchoAction(message="Hello"))
        print(result.observation)

    # From Docker image
    client = await EchoEnv.from_docker_image("<local_docker_image>")
    async with client:
        result = await client.reset()
        print(result.observation)

asyncio.run(main())

# Sync usage (using .sync() wrapper)
with EchoEnv(base_url="http://localhost:8000").sync() as client:
    result = client.reset()
    result = client.step(EchoAction(message="Hello"))
    print(result.observation)

Container Lifecycle

MethodContainerWebSocketOn close()
from_hub(repo_id)StartsConnectsStops container
from_hub(repo_id, use_docker=False)None (UV)ConnectsStops UV server
from_docker_image(image)StartsConnectsStops container
MyEnv(base_url=...)NoneConnectsDisconnects only

Find Docker Commands for Any Space

  1. Open the Space on HuggingFace Hub
  2. Click ⋮ (three dots) menu
  3. Select "Run locally"
  4. Copy the provided docker run command

Deploy with CLI

cd my_env

# Deploy to your namespace
openenv push

# Deploy to specific repo
openenv push --repo-id username/my-env

# Deploy as private
openenv push --repo-id username/my-env --private

Space Configuration

The openenv.yaml manifest controls Space settings:

# openenv.yaml
name: my_env
version: "1.0.0"
description: My custom environment

Hardware Options:

TiervCPURAMCost
CPU Basic (Free)216GBFree
CPU Upgrade832GB$0.03/hr

OpenEnv environments support configuration via environment variables.

VariableDefaultDescription
WORKERS4Uvicorn worker processes
PORT8000Server port
HOST0.0.0.0Bind address
MAX_CONCURRENT_ENVS100Max WebSocket sessions
ENABLE_WEB_INTERFACEAutoEnable web UI

Environment-Specific Variables

Some environments have custom variables:

TextArena:

TEXTARENA_ENV_ID=Wordle-v0
TEXTARENA_NUM_PLAYERS=1
TEXTARENA_MAX_TURNS=6

Coding Environment:

SANDBOX_TIMEOUT=30
MAX_OUTPUT_LENGTH=10000

DEMO: Deploying to Hugging Face Spaces

This demo walks through the full workflow: create an environment, test locally, deploy to HF Spaces, and use it.

Step 1: Initialize a new environment

openenv init my_env
cd my_env

This creates the standard OpenEnv structure:

my_env/
├── server/
│   ├── app.py           # FastAPI server
│   ├── environment.py   # Your environment logic
│   └── Dockerfile
├── models.py            # Action/Observation types
├── client.py            # HTTP client
├── openenv.yaml         # Manifest
└── pyproject.toml

Step 2: Run locally

# Start the server
uv run server

# Or with uvicorn directly
uvicorn server.app:app --host 0.0.0.0 --port 8000 --reload

Test the health endpoint:

curl http://localhost:8000/health
# {"status": "healthy"}

Step 3: Deploy to HF Spaces

openenv push --repo-id username/my-env

Your environment is now live at:

curl https://openenv-echo-env.hf.space/health
# {"status": "healthy"}

Step 4: install the environment

uv pip install git+https://huggingface.co/spaces/openenv/echo_env

Step 5: Run locally via Docker (optional)

Pull and run the container from the HF registry, or open the browser:

# Pull from HF Spaces registry
docker pull registry.hf.space/openenv-echo-env:latest

# Run locally
docker run -it -p 7860:7860 --platform=linux/amd64 \
	registry.hf.space/openenv-echo-env:latest

Now connect to your local instance:

import asyncio
from echo_env import EchoEnv, EchoAction

# Async (recommended)
async def main():
    async with EchoEnv(base_url="http://localhost:7860") as env:
        result = await env.reset()
        print(result.observation)
        result = await env.step(EchoAction(message="Hello"))
        print(result.observation)

asyncio.run(main())

# Sync (using .sync() wrapper)
with EchoEnv(base_url="http://localhost:7860").sync() as env:
    result = env.reset()
    print(result.observation)
    result = env.step(EchoAction(message="Hello"))
    print(result.observation)