OpenEnv documentation
Deploying an Environment
Deploying an Environment
This page covers packaging and sharing an environment once it runs locally: dependencies and the Dockerfile, openenv build, openenv validate, openenv push and connecting to the deployed environment. To create the environment first, follow Your First Environment.
| Command | Description |
|---|---|
openenv build | Build the Docker image locally |
openenv validate --level static --skip-build | Validate the manifest contract in openenv.yaml |
openenv push | Deploy to a Hugging Face Space |
openenv push --repo-id NAME | Deploy to a specific Space |
openenv push --private | Deploy as a private Space |
openenv push --registry ghcr.io/ORG | Push the image to a Docker registry instead |
See the CLI reference for every command and flag.
Dependencies and Dockerfile
Declare Python dependencies in the environment’s pyproject.toml and regenerate uv.lock with uv lock. Install anything else (system packages, binaries) in server/Dockerfile, before the step that removes temporary files.
The template’s server/Dockerfile is a multi-stage build on ghcr.io/huggingface/openenv-base that installs your dependencies with uv sync and serves server.app:app on port 8000. Keep building from openenv-base so shared tooling stays available.
Build the image
From the environment folder (needs Docker):
openenv build
openenv build works for standalone environments and for ones inside the OpenEnv repo, and sets the build arguments accordingly. Useful flags:
--tag/-t: override the default tag,openenv-<env_name>without its_envsuffix (openenv-myformy_env)--build-arg KEY=VALUE: pass Docker build arguments (repeatable)--dockerfile/-f/--context/-c: custom locations when experimenting--no-cache: force fresh dependency installs
Validate
openenv validate --level static --skip-build
openenv validate reads the validation: contract in openenv.yaml, checks the normalized manifest against the selected severity policy and exits non-zero when a required check fails. The report’s levels_run field records which levels ran. To validate a running server, use openenv validate --url http://localhost:8000.
Push to Hugging Face Spaces
# Push the environment in the current folder to <your-username>/<env_name>
openenv push
# Push to a specific repo or namespace
openenv push --repo-id my-org/my-env
# Push to a Docker registry (web UI disabled by default)
openenv push --registry ghcr.io/my-org
# Override the base image and make the Space private
openenv push --base-image ghcr.io/huggingface/openenv-base:latest --private
# Set Space variables and secrets at push time
openenv push -e OPENSPIEL_GAME=tic_tac_toe --secret OPENAI_API_KEY=sk-...Options:
DIRECTORY(positional): path to the environment (defaults to the current directory)--repo-id/-r: Space name, asnameornamespace/name--registry: push the image to Docker Hub, GHCR, etc.--interface/--no-interface: toggle the web UI (on by default for Spaces)--base-image/-b: override the DockerfileFROM--private: make the Space private--env-var/-e KEY=VALUE: set a public Space variable (repeatable), overriding matching keys fromvariables:inopenenv.yaml--secret KEY=VALUE: set a private Space secret (repeatable). The value is never logged--hardware/-H: Space hardware (for examplet4-medium)--count/-n: deploy several Space instances, each with a numeric suffix--create-pr: open a Pull Request instead of pushing to the default branch--exclude: an ignore file with globs to leave out of the upload
The command logs you in if needed, validates openenv.yaml, adds the Hugging Face frontmatter to the README when needed and uploads the bundle. It deletes the remote files the environment no longer ships: files at the root of the Space or under a top-level directory of the bundle (e.g. server/) that are not uploaded again. Each one is listed before the upload. Files under other directories (e.g. assets/ added by hand on the Space) and files matching the ignore patterns (the defaults and --exclude) are kept, so list in --exclude any root-level file you manage on the Space directly. Local build artifacts (build/, *.egg-info, __pycache__, dotfiles) are never uploaded.
Space variables and secrets are only applied on direct Hugging Face Space pushes. They are not available with --registry and cannot be staged through --create-pr.
Declare public variables in openenv.yaml
Defaults that belong with the environment (game name, benchmark, max steps) go in a variables: block in openenv.yaml. openenv push applies them to the Space:
variables:
OPENSPIEL_GAME: catchCLI -e overrides matching keys. Put secrets (API keys, tokens) only on the CLI with --secret KEY=VALUE, never in the yaml.
To fork or update someone else’s environment, see Contributing Environments.
Use the deployed environment
from my_env import MyAction, MyEnv
# Pull the Space's image and run it locally (needs Docker)
client = MyEnv.from_env("my-org/my-env").sync()
# Or start a container from a local image
client = MyEnv.from_docker_image("openenv-my:latest").sync()
# Or connect to a server that is already running
client = MyEnv(base_url="http://localhost:8000").sync()
with client:
result = client.reset()
result = client.step(MyAction(message="Hello!"))
state = client.state()from_docker_image() and from_env() return a lazy bootstrap handle, not a connected client. Nothing starts until you resolve it: chain .sync() for a synchronous client, or await the handle from async code. Using the handle directly in a with block raises TypeError: '_BootstrapResult' object does not support the context manager protocol.
The async version:
import asyncio
from my_env import MyAction, MyEnv
async def main():
client = await MyEnv.from_docker_image("openenv-my:latest")
async with client:
result = await client.reset()
result = await client.step(MyAction(message="Hello!"))
asyncio.run(main())See Async vs Sync for when to prefer each style and Runtime Providers to run the image somewhere other than local Docker.
Build in CI (OpenEnv repo only)
For an environment inside the OpenEnv repo, add it to the matrix in .github/workflows/docker-build.yml to build its image on every push to main:
strategy:
matrix:
image:
- name: echo-env
dockerfile: envs/echo_env/server/Dockerfile
context: envs/echo_env
- name: my-env # Add your environment here
dockerfile: envs/my_env/server/Dockerfile
context: envs/my_env