Runners: where jobs run live
Every GitHub Actions job and every automation run gets a fresh Linux machine of its own: a Cloudflare Container that starts for the job and is destroyed when it ends. A lighter runner, a Dynamic Worker isolate for jobs that need no Linux, is designed but not built.
live Container (Linux), runs-on: ubuntu-*
planned Dynamic Worker (isolate),
runs-on: gitstalk-isolate
Container or isolate
Today there is one runner. The second column is the design, so you can see where it is going; nothing in it runs yet.
| Container (Linux) | Dynamic Worker (isolate) | |
|---|---|---|
| Status | live | planned Designed, not built. A job that asks for it does not run today. |
| Selected by |
runs-on: ubuntu-latest, ubuntu-24.04 or
ubuntu-22.04
|
runs-on: gitstalk-isolate, opt-in by label only |
| Start-up | About 2 s to start the container, about 6 s to the first step (measured); about 30 s more when a job pulls Docker images | Milliseconds (design estimate) |
| Languages | Anything that runs on Ubuntu 24.04 x86-64: shell, Node, Python, compilers | JavaScript only |
| What it runs |
run: steps, JavaScript, composite and Docker actions,
services:, container:
|
Gitstalk's own steps and HTTP calls; no run:, no Docker actions, no
marketplace JavaScript actions
|
| Size and limits | 4 vCPU, 12 GiB memory, 20 GB disk; 60 minutes per job | 5 s CPU per step (at most 30 s), 200 subrequests, 5 minutes wall clock |
| Network | Open internet egress | None by default; hosts allowed per file, secrets only in headers |
| Cost to you | Job minutes, rounded up per job: 100 per repository per month in the beta | Not decided |
Which should I use?
-
Almost always: the container. It is the only runner that exists.
Write
runs-on: ubuntu-latest, as on GitHub. -
Builds, tests, installs, deploys (
npm ci,cargo,wrangler deploy, any shell): the container, now and later. They need Linux processes and a disk. -
Jobs with
services:,container:or adocker://step: the container. Gitstalk starts Docker inside it for you (see Docker inside a job). - Automations, Prompt and Shell alike: you do not choose. Each run is one container job (see Automations).
- Small glue jobs that only call HTTP APIs and Gitstalk: the isolate, once it ships. Until then, use the container.
- macOS, Windows, ARM or self-hosted: not available. Such a job never runs.
From push to logs
- Pushthe stalk moves; a run starts
- Job queue4 per repository, 16 in all
- Containeror an isolate (planned)
- Logsstreamed to the run page
There is no queue service: a job that finds no free container waits, trying again every 10 seconds, for up to 5 minutes. After that it fails as an infrastructure error, not as a red test.
Container jobs live
Each job gets its own container, named after the job, so two jobs never share a filesystem. It runs the job with act and a small runner of ours, streams the log back while it runs, and is destroyed at the end. Nothing carries over to the next job except the dependency cache.
name: ci
on:
push:
branches: [main]
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm test
Which runs-on labels run
runs-on |
Runs on |
|---|---|
ubuntu-latest |
The Ubuntu 24.04 image |
ubuntu-24.04 |
The Ubuntu 24.04 image |
ubuntu-22.04 |
The same Ubuntu 24.04 image (there is no 22.04 image) |
Anything else: a list such as [self-hosted, linux], a
group:, an expression such as ${{ matrix.os }},
macos-*, windows-*, gitstalk-isolate
|
Never runs. The compatibility report says "only ubuntu-latest, ubuntu-24.04 and
ubuntu-22.04 run here", and a run that reaches the job ends it as
startup_failure.
|
Instance type
Every job runs on a Cloudflare standard-4 container: 4 vCPU, 12 GiB of
memory and 20 GB of disk. GitHub's ubuntu-latest has 4 vCPU and 16 GB, and
a real test suite ran at GitHub's speed on it. A workflow cannot choose another size: no
runs-on label and no Gitstalk key selects one.
planned Larger sizes by label (the design names
gitstalk-4cpu), with no date.
Docker inside a job
A job with services:, container: or a step that
uses: docker://… runs in Docker mode: Gitstalk starts
dockerd inside the job's own container, and the steps run in the
catthehacker/ubuntu:act-24.04 image rather than the image below. Pulling
images costs about 30 seconds a job. A Redis service container has run on staging; it is
beta and not yet verified on the hosted service. A
marketplace action that is itself a Docker action (runs.using: docker in
its action.yml) does not switch Docker mode on, so it is not supported yet.
on: [push]
jobs:
integration:
runs-on: ubuntu-latest
services:
redis:
image: redis:7-alpine
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run test:integration
Network
Containers have open internet egress, as on GitHub: actions/checkout,
action downloads and package installs all need it. Secrets are masked in the log; see
Secrets and variables for which jobs get them.
Dynamic Worker jobs planned
The design runs a job labelled gitstalk-isolate in a Cloudflare Dynamic
Worker: a V8 isolate loaded on demand, with no Linux, no processes and no filesystem. It
is meant for small jobs that only call HTTP APIs and Gitstalk, and would start in
milliseconds instead of seconds. None of it is built. There is no
Worker Loader binding in any Worker, and the label is refused like any other unknown
label.
What the design says, so you can plan for it:
-
Opt-in by label only. Gitstalk never moves a job to an isolate by
guessing from its steps;
ubuntu-*always means a container. -
JavaScript only. Validation would refuse
run:steps, Docker actions and marketplace JavaScript actions in such a job, so it never fails halfway because a step needed Linux. Steps would be Gitstalk's own actions, such as a plannedgitstalk/script@v1shaped likeactions/github-script. - No network by default. Only hosts the file allows, and a secret is put into request headers for its paired hosts only; it never enters the isolate.
- Limits per step: 5 seconds of CPU by default (at most 30), 200 subrequests, 5 minutes wall clock.
This is what such a job would look like. Today it is parsed, reported as never running,
and ends as startup_failure.
on:
push:
branches: [main]
jobs:
notify:
runs-on: gitstalk-isolate # planned: refused today
steps:
- uses: gitstalk/script@v1 # planned: does not exist yet
with:
script: |
await fetch('https://hooks.example.com/deploys', { method: 'POST' })
Automations
An automation has no runs-on. Gitstalk
compiles each run into one Actions job with runs-on: ubuntu-latest, so both
harnesses run in the same standard-4 container as any job, and count
against the same minutes.
| Harness | What runs in the container | Runner |
|---|---|---|
Prompt (harness: agent, the default) |
Gitstalk's agent loop under Node, with shell, file and git tools. Its model calls go to a Workers AI model through Gitstalk's model proxy, so the container never holds a model key. See Models. | Container |
Shell (harness: shell) |
Your run: script in bash, with no model |
Container |
on: bean_red
prompt: Read why the bean went red and fix it in a new bean.
harness: shell
on:
schedule: [{ cron: "*/15 * * * *" }]
run: git log -1 --format=%H
The design once put automations on isolates; it moved them to containers because a coding agent needs processes and a filesystem.
Images and preinstalled tools
The job image is built from packages/actions-executor/container/Dockerfile
and is the same for every job.
| What | In the image |
|---|---|
| Operating system | Ubuntu 24.04, x86-64 (linux/amd64), pinned by digest |
| User |
runner (uid 1001) with passwordless sudo; home
/home/runner, work under /home/runner/work
|
| Node |
24.21.0 on the path; 20.20.2 and 24.21.0 in the tool cache
(/opt/hostedtoolcache), so actions/setup-node for 20
or 24 downloads nothing
|
| Python | Ubuntu's python3 with pip and venv |
| Build tools | build-essential (gcc, make), pkg-config |
| Command-line tools |
git, git-lfs, openssh-client,
curl, wget, jq, gnupg,
zip, unzip, xz-utils, zstd,
iptables, tzdata, locales
|
| Docker | Docker 29.8.2 (static binaries), started only for Docker-mode jobs |
| Actions baked in (no download) |
actions/checkout@v4 and @v5,
actions/setup-node@v4 and @v5,
actions/cache@v4, actions/upload-artifact@v4,
actions/download-artifact@v4, actions/setup-python@v5,
actions/github-script@v7,
cloudflare/wrangler-action@v3. Other actions are fetched from
github.com once per job.
|
| Not in the image |
Go, Rust, Java, .NET, browsers and cloud CLIs. Install them in a step, or use a
setup-* action.
|
You cannot bring your own job image yet, other than through
container: in Docker mode.
Limits
| Limit | Container (hosted beta) |
|---|---|
| Job timeout |
60 minutes. A job without timeout-minutes gets 60 (GitHub gives
360); a larger value is capped.
|
| Minutes | 100 per repository per month, each job rounded up to a whole minute |
| Jobs at once | 4 per repository; 16 containers across the service |
| Wait for a free container | Up to 5 minutes, then an infrastructure failure |
| Matrix | 16 legs per job |
| CPU, memory, disk | 4 vCPU, 12 GiB, 20 GB (standard-4) |
node_modules |
A 6 GiB in-memory filesystem, restored by the dependency cache |
| Network egress | Open |
| Log lines | 64 KiB per line |
Every other number, including the dependency cache's and automations' own, is in Limits and plans.
Related pages
GitHub Actions
What runs, events, the compatibility report and OIDC.
Automations
Agents and scripts started by Gitstalk events.
Dependency cache
Start installs from a full node_modules.
Limits and plans
Minutes, timeouts and every other number.
Sources
- docs/claude-opus/26-actions-job-executor.md (the executor, its image and the measured start-up)
- docs/claude-opus/25-actions-and-automations.md (§3.9, the isolate design; §7.3, where automations run)