Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,42 @@ for event in connection.events():
print(event)
```

### Cache remote Dockerfile builds

Use the public context fingerprint when deriving a cache name. It uses the same
Dockerfile source selection and `.dockerignore` rules as remote packaging,
including file contents, modes, paths, and symlinks. It ignores timestamps and
does not compress or stage the context on disk.

```python
from hyperbrowser.build_context import docker_build_context_fingerprint

fingerprint = docker_build_context_fingerprint("./app")
# Include build options such as platform and image_init in your cache key too.
image_name = f"app-{fingerprint[:32]}"
build = client.sandboxes.build_image_from_dockerfile(
context_path="./app",
image_name=image_name,
expected_context_fingerprint=fingerprint,
)
```

If the archived inputs differ from the fingerprint, the SDK raises
`DockerBuildContextChangedError` before creating or uploading a build. Compute a
fresh fingerprint and repeat the lookup/build operation. Use the same `dockerfile`
and `force_full_context` selection when fingerprinting and building (the latter
is named `remote_full_context` on the build method). The expected fingerprint is
supported only for remote builds.

Fingerprinting streams included files and performs blocking I/O. Async callers
should use `await asyncio.to_thread(docker_build_context_fingerprint, "./app")`
(Python 3.9+) or an executor. It does not resolve mutable base-image tags or
network resources fetched by a Dockerfile; rebuild explicitly when those change.

Image listings distinguish `ready` from `uploaded`: a completed team image can
be ready to launch before its durability backup is uploaded. `ready` is `None`
when talking to an older server. Keep the returned image ID to pin that revision.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
8 changes: 8 additions & 0 deletions hyperbrowser/build_context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Public helpers for identifying inputs to remote Dockerfile builds."""

from .client.managers.sandboxes.image_build import (
DockerBuildContextChangedError,
docker_build_context_fingerprint,
)

__all__ = ["DockerBuildContextChangedError", "docker_build_context_fingerprint"]
6 changes: 6 additions & 0 deletions hyperbrowser/client/managers/async_manager/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,7 @@ async def _build_image_from_remote_dockerfile(
dockerfile,
platform: str,
remote_full_context: bool,
expected_context_fingerprint: Optional[str],
image_init: Optional[Union[SandboxImageInitDict, SandboxImageInit]],
image_config_user: Optional[str],
builder_cpus: Optional[int],
Expand All @@ -741,6 +742,7 @@ async def _build_image_from_remote_dockerfile(
context_path,
dockerfile=dockerfile,
force_full_context=remote_full_context,
expected_context_fingerprint=expected_context_fingerprint,
temp_dir=temp_dir,
)
build_id = None
Expand Down Expand Up @@ -803,6 +805,7 @@ async def build_image_from_dockerfile(
dockerfile="Dockerfile",
remote: bool = True,
remote_full_context: bool = False,
expected_context_fingerprint: Optional[str] = None,
docker_tag: Optional[str] = None,
platform: str = IMAGE_BUILD_SOURCE_PLATFORM,
build_args: Optional[Dict[str, str]] = None,
Expand All @@ -829,6 +832,7 @@ async def build_image_from_dockerfile(
dockerfile=dockerfile,
platform=platform,
remote_full_context=remote_full_context,
expected_context_fingerprint=expected_context_fingerprint,
image_init=image_init,
image_config_user=image_config_user,
builder_cpus=builder_cpus,
Expand All @@ -840,6 +844,8 @@ async def build_image_from_dockerfile(
temp_dir=temp_dir,
upload_timeout=upload_timeout,
)
if expected_context_fingerprint is not None:
raise ValueError("expected_context_fingerprint requires remote=True")
tag = docker_tag or make_temp_docker_tag()
remove_tag = docker_tag is None
try:
Expand Down
Loading