Skip to content

feat(plugin-oci): run targets in containers with an oci_runner - #421

Draft
raphaelvigee wants to merge 1 commit into
raphaelvigee/exec-runners-coveragefrom
raphaelvigee/exec-runners-oci
Draft

feat(plugin-oci): run targets in containers with an oci_runner#421
raphaelvigee wants to merge 1 commit into
raphaelvigee/exec-runners-coveragefrom
raphaelvigee/exec-runners-oci

Conversation

@raphaelvigee

@raphaelvigee raphaelvigee commented Aug 23, 2026

Copy link
Copy Markdown
Member

A runner target naming an image; every target under it is docker exec'd into
one container started for that environment.

oci_runner(name = "ctr", image = "ubuntu@sha256:…")
target(name = "build", driver = "bash", run = "…", runner = ":ctr")

The second real user of the Wrap lane, and the one it was designed for:
WrapEnv::Args exists precisely because docker exec creates the process on
the far side of the daemon socket, so the environment heph sets belongs to the
CLI on this side and the container sees none of it. Each variable is rendered
into argv as -e K=V.

Building it surfaced three things WrapSession could not express, each added
here as a builder rather than a fourth and fifth constructor argument:

  • with_cwd_argsdocker exec -w <cwd>. spec.cwd is where the wrapper
    starts, which says nothing about the process inside the container. Without it
    every target runs in the image's WORKDIR, usually /, and a build reading a
    relative path fails a long way from the cause.
  • with_trailing_argsdocker exec is [OPTIONS] CONTAINER COMMAND, so the
    container id is an operand: it cannot sit in prefix_argv, which precedes the
    options. The three knobs now map onto exactly that grammar.
  • with_teardown — a container outlives the build unless something removes it.
    TeardownJob was written for this case and WrapSession had no way to carry
    one.

Hermeticity: a digest is content the cache key already covers, so the session
reports Pinned; a tag reports Asserted and says why, rather than being
refused — choosing a weakly-pinned environment is the user's call. The sandbox
root is mounted at the same path inside the container, because targets address
$OUT/$SRC absolutely. The artifact is local-cache only: an oci_load ref
names an image in this machine's daemon and would not resolve on another.

Teardown is docker rm -f, with --rm as the backstop for an abort that never
reaches it and --init so a target's own children are reaped inside.

Scope: serves any driver. A plugin driver asks the host to create its
processes (#418), so go_* targets build inside the container like anything
else.

Tests cover the argv grammar, the cwd, env-through-argv, the digest/tag identity
split, and that a container session does not flatten to an environment. The one
part needing a daemon — docker run producing a container id — is the one part
they do not exercise.

Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01RC5ykWRMPjRKGk1Tz1KTja


Stack created with GitHub Stacks CLIGive Feedback 💬

A runner target naming an image; every target under it is `docker exec`'d into
one container started for that environment.

```python
oci_runner(name = "ctr", image = "ubuntu@sha256:…")
target(name = "build", driver = "bash", run = "…", runner = ":ctr")
```

The second real user of the `Wrap` lane, and the one it was designed for:
`WrapEnv::Args` exists precisely because `docker exec` creates the process on
the far side of the daemon socket, so the environment heph sets belongs to the
CLI on this side and the container sees none of it. Each variable is rendered
into argv as `-e K=V`.

Building it surfaced three things `WrapSession` could not express, each added
here as a builder rather than a fourth and fifth constructor argument:

- `with_cwd_args` — `docker exec -w <cwd>`. `spec.cwd` is where the *wrapper*
  starts, which says nothing about the process inside the container. Without it
  every target runs in the image's `WORKDIR`, usually `/`, and a build reading a
  relative path fails a long way from the cause.
- `with_trailing_args` — `docker exec` is `[OPTIONS] CONTAINER COMMAND`, so the
  container id is an operand: it cannot sit in `prefix_argv`, which precedes the
  options. The three knobs now map onto exactly that grammar.
- `with_teardown` — a container outlives the build unless something removes it.
  `TeardownJob` was written for this case and `WrapSession` had no way to carry
  one.

Hermeticity: a digest is content the cache key already covers, so the session
reports `Pinned`; a tag reports `Asserted` and says why, rather than being
refused — choosing a weakly-pinned environment is the user's call. The sandbox
root is mounted at the *same path* inside the container, because targets address
`$OUT`/`$SRC` absolutely. The artifact is local-cache only: an `oci_load` ref
names an image in this machine's daemon and would not resolve on another.

Teardown is `docker rm -f`, with `--rm` as the backstop for an abort that never
reaches it and `--init` so a target's own children are reaped inside.

**Scope, stated plainly:** this serves targets built by heph's built-in drivers
(`exec`, `bash`). A target built by a plugin driver under this runner is refused,
because only the environment crosses the plugin seam and a container is not one.
Lifting that needs the live session on the run lane — the follow-up already
named in the commit that added the refusal.

Tests cover the argv grammar, the cwd, env-through-argv, the digest/tag identity
split, and that a container session does not flatten to an environment. The one
part needing a daemon — `docker run` producing a container id — is the one part
they do not exercise.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RC5ykWRMPjRKGk1Tz1KTja
@raphaelvigee
raphaelvigee force-pushed the raphaelvigee/exec-runners-oci branch from 76f025f to 4111d6c Compare August 24, 2026 08:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant