gitstalk
Docs: Runners: where jobs run

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 a docker:// 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

  1. Pushthe stalk moves; a run starts
  2. Job queue4 per repository, 16 in all
  3. Containeror an isolate (planned)
  4. 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.

.github/workflows/ci.yml
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.

.github/workflows/integration.yml
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 planned gitstalk/script@v1 shaped like actions/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.

.github/workflows/notify.yml (planned)
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
.gitstalk/automations/fix-red.yml (Prompt)
on: bean_red
prompt: Read why the bean went red and fix it in a new bean.
.gitstalk/automations/heartbeat.yml (Shell)
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

Sources