docker

The docker package provides image building, Compose orchestration, and file staging for workflows that run containers on a remote parent host.

Docker support for otto.

This package provides a library API that the CLI (otto docker ...) and project instructions/suites both call into. Anything the CLI can do, an instruction can do too:

from otto.docker import build_images, compose_up, compose_down, composed

@instruction()
async def smoke():
    async with composed(repo, lab, own=True) as containers:
        await containers["api"].run("./run-tests")

See the design notes in docs/design/docker_hosts.md for the full architecture (parent-delegation pattern, hop inheritance, naming scheme).

async otto.docker.build_images(repo, parent, *, image_names=None, rebuild=False)

Build all (or selected) images for repo on parent.

Parameters:
  • repo – The Repo whose [docker] settings declare the images.

  • parent – A docker-capable lab host. Builds happen here.

  • image_names – Optional filter — only build images whose name is in this iterable. None builds everything declared.

  • rebuild – When True, skip the context-hash existence check and always invoke docker build.

Return type:

dict[str, tuple[Status, str]]

Returns:

Mapping of image name to (Status, message_or_tag). Status is Status.Skipped for images that already existed, Status.Success for fresh builds, and a failure status otherwise. The message is the full tag on success/skip, or the captured stderr on failure.

async otto.docker.compose_down(repo, lab, *, on=None, project_name=None, stop_timeout=1)

Tear down repo’s compose stack and unregister its container hosts.

stop_timeout is the per-container graceful-shutdown grace period in seconds passed to docker compose down --timeout. Defaults to 1s rather than docker’s default of 10s — otto’s typical workload is integration tests with disposable stacks where waiting 10s on every teardown adds up fast (4 tests × 10s = 40s of wall time on the serialized docker_e2e group). Pass a larger value for stacks where graceful shutdown matters.

Return type:

Status

async otto.docker.compose_ps(parent)

Return a list of dicts describing running containers on parent.

Uses docker ps --format '{{json .}}' so the output is structured.

Return type:

list[dict[str, Any]]

async otto.docker.compose_up(repo, lab, *, on=None, project_name=None, build=True)

Bring up repo’s compose stack on a parent host.

Idempotent at the project-name level: if a stack with the same project_name is already running on parent, this becomes a lookup instead of a fresh up. Either way, returns a dict mapping each declared service to its DockerContainerHost, with the hosts also registered in lab.hosts so --list-hosts and otto host <id> see them.

Parameters:

build – When True (the default) and the repo declares [[docker.images]], run build_images() first so locally- built images exist on the parent before compose tries to pull them. The build is idempotent via the context-hash skip, so this is cheap when nothing changed. Pass build=False if the compose file references only published images (or if you already built explicitly).

Return type:

dict[str, DockerContainerHost]

otto.docker.composed(repo, lab, *, on=None, project_name=None, own=False, build=True)

Context manager wrapping compose_up / compose_down.

By default the stack is not torn down on exit if it was already running on entry — this lets a suite-level fixture hold the stack while inner instructions also call composed without yanking it from each other. Pass own=True to force teardown.

build is forwarded to compose_up().

Return type:

AsyncIterator[dict[str, DockerContainerHost]]

otto.docker.context_hash(image)

Return a stable sha256 hex digest of image’s build inputs.

Return type:

str

otto.docker.get_container_host(host_id)

Look up a registered container host by id. Raises if not present.

Return type:

DockerContainerHost

otto.docker.get_user_compose_project(repo_name, suffix=None)

Return a compose project name unique enough to coexist with other runs.

Format: otto-<repo>-<suffix>. suffix defaults to the OS username, or OTTO_COMPOSE_SUFFIX if set in the environment. Lowercase only — compose project names must be lowercase.

Return type:

str

otto.docker.image_full_tag(registry_url, project, image, hash_hex)

Construct the <project>-<image>:<hash> tag (with registry prefix when non-default).

Return type:

str

otto.docker.image_latest_tag(registry_url, project, image)
Return type:

str