This section covers deploying OpenEnv environments locally, on clusters, and on Hugging Face Spaces.
Contents:
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.
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
/wsWebSocket 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"}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-envThis 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")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:latestFind the registry URL for any Space:
- Go to the Space page (e.g., openenv/echo-env)
- Click ⋮ (three dots) → "Run locally"
- Copy the
docker runcommand
| 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
The fastest way to iterate on environment logic is running directly with Uvicorn.
# 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# 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 provides isolation and reproducibility for production use.
# Run the environment locally from the space
docker run -d -p 8000:8000 registry.hf.space/openenv-echo-env:latest# 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 .# 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:latestimport 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)| 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
- Open the Space on HuggingFace Hub
- Click ⋮ (three dots) menu
- Select "Run locally"
- Copy the provided
docker runcommand
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 --privateThe openenv.yaml manifest controls Space settings:
# openenv.yaml
name: my_env
version: "1.0.0"
description: My custom environmentHardware 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 |
Some environments have custom variables:
TextArena:
TEXTARENA_ENV_ID=Wordle-v0
TEXTARENA_NUM_PLAYERS=1
TEXTARENA_MAX_TURNS=6Coding Environment:
SANDBOX_TIMEOUT=30
MAX_OUTPUT_LENGTH=10000This demo walks through the full workflow: create an environment, test locally, deploy to HF Spaces, and use it.
openenv init my_env
cd my_envThis 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
# Start the server
uv run server
# Or with uvicorn directly
uvicorn server.app:app --host 0.0.0.0 --port 8000 --reloadTest the health endpoint:
curl http://localhost:8000/health
# {"status": "healthy"}openenv push --repo-id username/my-envYour environment is now live at:
- Web UI: https://username-my-env.hf.space/web
- API Docs: https://username-my-env.hf.space/docs
- Health: https://username-my-env.hf.space/health
curl https://openenv-echo-env.hf.space/health
# {"status": "healthy"}uv pip install git+https://huggingface.co/spaces/openenv/echo_envPull 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:latestNow 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)