Skip browser dependency build in Dockerfile when browser is disabled (#9815)

This commit is contained in:
Boxuan Li
2025-07-21 08:34:11 -07:00
committed by GitHub
parent df75116184
commit 19ca52f954
11 changed files with 97 additions and 18 deletions

View File

@@ -32,6 +32,7 @@ def _generate_dockerfile(
base_image: str,
build_from: BuildFromImageType = BuildFromImageType.SCRATCH,
extra_deps: str | None = None,
enable_browser: bool = True,
) -> str:
"""Generate the Dockerfile content for the runtime image based on the base image.
@@ -39,6 +40,7 @@ def _generate_dockerfile(
- base_image (str): The base image provided for the runtime image
- build_from (BuildFromImageType): The build method for the runtime image.
- extra_deps (str):
- enable_browser (bool): Whether to enable browser support (install Playwright)
Returns:
- str: The resulting Dockerfile content
@@ -55,6 +57,7 @@ def _generate_dockerfile(
build_from_scratch=build_from == BuildFromImageType.SCRATCH,
build_from_versioned=build_from == BuildFromImageType.VERSIONED,
extra_deps=extra_deps if extra_deps is not None else '',
enable_browser=enable_browser,
)
return dockerfile_content
@@ -111,6 +114,7 @@ def build_runtime_image(
dry_run: bool = False,
force_rebuild: bool = False,
extra_build_args: list[str] | None = None,
enable_browser: bool = True,
) -> str:
"""Prepares the final docker build folder.
@@ -125,6 +129,7 @@ def build_runtime_image(
- dry_run (bool): if True, it will only ready the build folder. It will not actually build the Docker image
- force_rebuild (bool): if True, it will create the Dockerfile which uses the base_image
- extra_build_args (List[str]): Additional build arguments to pass to the builder
- enable_browser (bool): Whether to enable browser support (install Playwright)
Returns:
- str: <image_repo>:<MD5 hash>. Where MD5 hash is the hash of the docker build folder
@@ -142,6 +147,7 @@ def build_runtime_image(
force_rebuild=force_rebuild,
platform=platform,
extra_build_args=extra_build_args,
enable_browser=enable_browser,
)
return result
@@ -154,6 +160,7 @@ def build_runtime_image(
force_rebuild=force_rebuild,
platform=platform,
extra_build_args=extra_build_args,
enable_browser=enable_browser,
)
return result
@@ -167,9 +174,10 @@ def build_runtime_image_in_folder(
force_rebuild: bool,
platform: str | None = None,
extra_build_args: list[str] | None = None,
enable_browser: bool = True,
) -> str:
runtime_image_repo, _ = get_runtime_image_repo_and_tag(base_image)
lock_tag = f'oh_v{oh_version}_{get_hash_for_lock_files(base_image)}'
lock_tag = f'oh_v{oh_version}_{get_hash_for_lock_files(base_image, enable_browser)}'
versioned_tag = (
# truncate the base image to 96 characters to fit in the tag max length (128 characters)
f'oh_v{oh_version}_{get_tag_for_versioned_image(base_image)}'
@@ -188,6 +196,7 @@ def build_runtime_image_in_folder(
base_image,
build_from=BuildFromImageType.SCRATCH,
extra_deps=extra_deps,
enable_browser=enable_browser,
)
if not dry_run:
_build_sandbox_image(
@@ -226,7 +235,7 @@ def build_runtime_image_in_folder(
else:
logger.debug(f'Build [{hash_image_name}] from scratch')
prep_build_folder(build_folder, base_image, build_from, extra_deps)
prep_build_folder(build_folder, base_image, build_from, extra_deps, enable_browser)
if not dry_run:
_build_sandbox_image(
build_folder,
@@ -251,6 +260,7 @@ def prep_build_folder(
base_image: str,
build_from: BuildFromImageType,
extra_deps: str | None,
enable_browser: bool = True,
) -> None:
# Copy the source code to directory. It will end up in build_folder/code
# If package is not found, build from source code
@@ -282,6 +292,7 @@ def prep_build_folder(
base_image,
build_from=build_from,
extra_deps=extra_deps,
enable_browser=enable_browser,
)
dockerfile_path = Path(build_folder, 'Dockerfile')
with open(str(dockerfile_path), 'w') as f:
@@ -301,10 +312,13 @@ def truncate_hash(hash: str) -> str:
return ''.join(result)
def get_hash_for_lock_files(base_image: str) -> str:
def get_hash_for_lock_files(base_image: str, enable_browser: bool = True) -> str:
openhands_source_dir = Path(openhands.__file__).parent
md5 = hashlib.md5()
md5.update(base_image.encode())
# Only include enable_browser in hash when it's False for backward compatibility
if not enable_browser:
md5.update(str(enable_browser).encode())
for file in ['pyproject.toml', 'poetry.lock']:
src = Path(openhands_source_dir, file)
if not src.exists():
@@ -378,6 +392,10 @@ if __name__ == '__main__':
parser.add_argument('--build_folder', type=str, default=None)
parser.add_argument('--force_rebuild', action='store_true', default=False)
parser.add_argument('--platform', type=str, default=None)
parser.add_argument('--enable_browser', action='store_true', default=True)
parser.add_argument(
'--no_enable_browser', dest='enable_browser', action='store_false'
)
args = parser.parse_args()
if args.build_folder is not None:
@@ -409,6 +427,7 @@ if __name__ == '__main__':
dry_run=True,
force_rebuild=args.force_rebuild,
platform=args.platform,
enable_browser=args.enable_browser,
)
_runtime_image_repo, runtime_image_source_tag = (
@@ -444,6 +463,9 @@ if __name__ == '__main__':
logger.debug('Building image in a temporary folder')
docker_builder = DockerRuntimeBuilder(docker.from_env())
image_name = build_runtime_image(
args.base_image, docker_builder, platform=args.platform
args.base_image,
docker_builder,
platform=args.platform,
enable_browser=args.enable_browser,
)
logger.debug(f'\nBuilt image: {image_name}\n')

View File

@@ -127,7 +127,9 @@ RUN \
/openhands/micromamba/bin/micromamba run -n openhands poetry install --only main,runtime --no-interaction --no-root && \
# Update and install additional tools
# (There used to be an "apt-get update" here, hopefully we can skip it.)
{% if enable_browser %}
/openhands/micromamba/bin/micromamba run -n openhands poetry run playwright install --with-deps chromium && \
{% endif %}
# Set environment variables
/openhands/micromamba/bin/micromamba run -n openhands poetry run python -c "import sys; print('OH_INTERPRETER_PATH=' + sys.executable)" >> /etc/environment && \
# Set permissions