Spaces:
Build error
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
/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"}
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:
- Go to the Space page (e.g., openenv/echo-env)
- Click โฎ (three dots) โ "Run locally"
- Copy the
docker runcommand
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
- Open the Space on HuggingFace Hub
- Click โฎ (three dots) menu
- Select "Run locally"
- Copy the provided
docker runcommand
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:
- 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"}
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)