OpenEnv documentation

Deploying an Environment

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v0.8.0).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

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.

CommandDescription
openenv buildBuild the Docker image locally
openenv validate --level static --skip-buildValidate the manifest contract in openenv.yaml
openenv pushDeploy to a Hugging Face Space
openenv push --repo-id NAMEDeploy to a specific Space
openenv push --privateDeploy as a private Space
openenv push --registry ghcr.io/ORGPush 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 _env suffix (openenv-my for my_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, as name or namespace/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 Dockerfile FROM
  • --private: make the Space private
  • --env-var/-e KEY=VALUE: set a public Space variable (repeatable), overriding matching keys from variables: in openenv.yaml
  • --secret KEY=VALUE: set a private Space secret (repeatable). The value is never logged
  • --hardware/-H: Space hardware (for example t4-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: catch

CLI -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
Update on GitHub