Carry the execution environment in the runner, not in the job image #32

Open
opened 2026-08-04 07:33:44 +00:00 by day01 · 0 comments
Owner

The runner takes its execution environment out of the job image instead of
carrying it. That single decision is why arbitrary images cannot serve as job
environments, and why the job image has to ship in lockstep with the product.

How it works today:

  • crates/runner-oci/src/engine/oci_tools.rs:13,38 starts a container from the
    job image and copies /opt/syncode-runner/externals into a tools volume;
  • crates/runner-execution/src/runtime.rs:29-45 hardcodes
    /opt/syncode-runner/externals/node{20,24}[-musl]/bin/node;
  • crates/runner-workflow-github-actions/src/worker/expression_effects.rs:111
    resolves hashFiles to /opt/syncode-runner/externals/hash-files.js;
  • the runner's own image installs none of it — Dockerfile copies a binary and
    an entrypoint into debian:bookworm-slim.

Consequences:

  1. A user cannot bring their own image. Anything without a baked
    /opt/syncode-runner/externals fails to run JavaScript actions, which is
    most of the ecosystem. container: in a workflow, a different distribution,
    a language-specific image — none of them work.
  2. The job image is a product artifact. It carries the runner's ABI, so it
    is versioned with the runner even though its own inputs changed three times
    in this repository's history.
  3. It cannot live anywhere else. Splitting it into its own repository or
    workflow today would separate two things that genuinely change together.

Proposed inversion: the runner ships node20 and node24 for gnu and musl plus
hash-files.js in its own image, and populates the tools volume from its own
filesystem rather than from the job image. oci_tools.rs::prepare stops
starting a container from the job image; the paths in runtime.rs become the
mount target the runner controls.

What that buys:

  • any OCI image works as a job environment, which is a product capability and
    not a cleanup;
  • images/ubuntu-24.04 becomes an optional convenience image — a preinstalled
    toolcache — free to move to its own repository and its own schedule, the way
    actions/runner and actions/runner-images are separated;
  • releases stop publishing an artifact whose content is unrelated to the
    version on it.

Cost: the runner image grows by four node runtimes, roughly 150-200 MB. It is
already multi-platform, so that is size, not complexity.

Out of scope here and already handled: #31 stops rebuilding the job image when
its inputs did not change, which removes the waste without touching this
design.

The runner takes its execution environment out of the job image instead of carrying it. That single decision is why arbitrary images cannot serve as job environments, and why the job image has to ship in lockstep with the product. How it works today: - `crates/runner-oci/src/engine/oci_tools.rs:13,38` starts a container from the job image and copies `/opt/syncode-runner/externals` into a tools volume; - `crates/runner-execution/src/runtime.rs:29-45` hardcodes `/opt/syncode-runner/externals/node{20,24}[-musl]/bin/node`; - `crates/runner-workflow-github-actions/src/worker/expression_effects.rs:111` resolves `hashFiles` to `/opt/syncode-runner/externals/hash-files.js`; - the runner's own image installs none of it — `Dockerfile` copies a binary and an entrypoint into `debian:bookworm-slim`. Consequences: 1. **A user cannot bring their own image.** Anything without a baked `/opt/syncode-runner/externals` fails to run JavaScript actions, which is most of the ecosystem. `container:` in a workflow, a different distribution, a language-specific image — none of them work. 2. **The job image is a product artifact.** It carries the runner's ABI, so it is versioned with the runner even though its own inputs changed three times in this repository's history. 3. **It cannot live anywhere else.** Splitting it into its own repository or workflow today would separate two things that genuinely change together. Proposed inversion: the runner ships node20 and node24 for gnu and musl plus `hash-files.js` in its own image, and populates the tools volume from its own filesystem rather than from the job image. `oci_tools.rs::prepare` stops starting a container from the job image; the paths in `runtime.rs` become the mount target the runner controls. What that buys: - any OCI image works as a job environment, which is a product capability and not a cleanup; - `images/ubuntu-24.04` becomes an optional convenience image — a preinstalled toolcache — free to move to its own repository and its own schedule, the way `actions/runner` and `actions/runner-images` are separated; - releases stop publishing an artifact whose content is unrelated to the version on it. Cost: the runner image grows by four node runtimes, roughly 150-200 MB. It is already multi-platform, so that is size, not complexity. Out of scope here and already handled: `#31` stops rebuilding the job image when its inputs did not change, which removes the waste without touching this design.
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: syncode/runner#32