python_env / tutorial /tutorial2.md
uvpatel7271's picture
Upload folder using huggingface_hub
c8e832f verified

2. Deploying an OpenEnv environment

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:

Component What it provides How to access Used as
Server Running environment endpoint https://<username>-<space-name>.hf.space Agent and Public API
Repository Installable Python package pip install git+https://huggingface.co/spaces/<username>-<space-name> Code and client
Registry Docker container image docker pull registry.hf.space/<username>-<space-name>:latest Deployment

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:

Endpoint Protocol Description
/ws WebSocket Persistent session (used by client)
/health HTTP GET Health check
/reset HTTP POST Reset environment (stateless)
/step HTTP POST Execute action (stateless)
/state HTTP GET Get current state
/docs HTTP GET OpenAPI documentation
/web HTTP GET Interactive 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

Method Use when Pros Cons
Server Quick testing, low volume Zero setup Network latency, rate limits
Repository Need typed classes Type safety, IDE support Still need a server
Docker Local dev, high throughput Full control, no network Requires 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
Flag Purpose
--reload Auto-restart on code changes
--workers N Run N worker processes
--log-level debug Verbose 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

Method Container WebSocket On close()
from_hub(repo_id) Starts Connects Stops container
from_hub(repo_id, use_docker=False) None (UV) Connects Stops UV server
from_docker_image(image) Starts Connects Stops container
MyEnv(base_url=...) None Connects Disconnects 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:

Tier vCPU RAM Cost
CPU Basic (Free) 2 16GB Free
CPU Upgrade 8 32GB $0.03/hr

OpenEnv environments support configuration via environment variables.

Variable Default Description
WORKERS 4 Uvicorn worker processes
PORT 8000 Server port
HOST 0.0.0.0 Bind address
MAX_CONCURRENT_ENVS 100 Max WebSocket sessions
ENABLE_WEB_INTERFACE Auto Enable 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:8000") 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:8000").sync() as env:
    result = env.reset()
    print(result.observation)
    result = env.step(EchoAction(message="Hello"))
    print(result.observation)