# 2. Deploying an OpenEnv environment This section covers deploying OpenEnv environments locally, on clusters, and on Hugging Face Spaces. **Contents:** - [Local Development with Uvicorn](#local-development-with-uvicorn) - [Docker Deployment](#docker-deployment) - [Hugging Face Spaces](#hugging-face-spaces) - [Best Practices](#best-practices) ## 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://-.hf.space` | Agent and Public API | | **Repository** | Installable Python package | `pip install git+https://huggingface.co/spaces/-` | Code and client | | **Registry** | Docker container image | `docker pull registry.hf.space/-: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: ```python 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** ```bash 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. ```bash # 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:** ```python 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. ```bash # 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](https://huggingface.co/spaces/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:** ```python 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](https://huggingface.co/docs/hub/spaces) | [Environment Hub Collection](https://huggingface.co/collections/openenv/environment-hub) ## Local Development with Uvicorn The fastest way to iterate on environment logic is running directly with Uvicorn. ## Clone and run the environment locally ```bash # 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 ```bash # 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 ```bash # Run the environment locally from the space docker run -d -p 8000:8000 registry.hf.space/openenv-echo-env:latest ``` ### Build Image ```bash # 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 ```bash # 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 ```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("") 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 ```bash 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: ```yaml # 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:** ```bash TEXTARENA_ENV_ID=Wordle-v0 TEXTARENA_NUM_PLAYERS=1 TEXTARENA_MAX_TURNS=6 ``` **Coding Environment:** ```bash 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 ```bash 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 ```bash # 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: ```bash curl http://localhost:8000/health # {"status": "healthy"} ``` ## Step 3: Deploy to HF Spaces ```bash 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 ```bash curl https://openenv-echo-env.hf.space/health # {"status": "healthy"} ``` ## Step 4: install the environment ```bash 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](https://huggingface.co/spaces/openenv/echo_env?docker=true): ```bash # 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: ```python 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) ```