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-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:
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.
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.
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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,38starts a container from thejob image and copies
/opt/syncode-runner/externalsinto a tools volume;crates/runner-execution/src/runtime.rs:29-45hardcodes/opt/syncode-runner/externals/node{20,24}[-musl]/bin/node;crates/runner-workflow-github-actions/src/worker/expression_effects.rs:111resolves
hashFilesto/opt/syncode-runner/externals/hash-files.js;Dockerfilecopies a binary andan entrypoint into
debian:bookworm-slim.Consequences:
/opt/syncode-runner/externalsfails to run JavaScript actions, which ismost of the ecosystem.
container:in a workflow, a different distribution,a language-specific image — none of them work.
is versioned with the runner even though its own inputs changed three times
in this repository's history.
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.jsin its own image, and populates the tools volume from its ownfilesystem rather than from the job image.
oci_tools.rs::preparestopsstarting a container from the job image; the paths in
runtime.rsbecome themount target the runner controls.
What that buys:
not a cleanup;
images/ubuntu-24.04becomes an optional convenience image — a preinstalledtoolcache — free to move to its own repository and its own schedule, the way
actions/runnerandactions/runner-imagesare separated;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:
#31stops rebuilding the job image whenits inputs did not change, which removes the waste without touching this
design.