Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

171 linesCodeBlameRaw
1---
2title: Actions, runners and sandboxes
3description: How a workflow job is planned and run, on g1t's runners or yours, what walls each job in, how secrets reach a job without the job holding them, and what else runs in a sandbox.
4---
5
6import Aside from '../../../components/Aside.astro';
7import Diagram from '../../../components/Diagram.astro';
8
9Everything that runs code you did not write runs in a sandbox: workflow
10jobs, merge checks, deployments, dependency updates, backups. Three pieces
11share the work.
12
13| Piece | Is | Does |
14| --- | --- | --- |
15| `services/actions` | A Rust Worker, D1 `g1t-actions` | Reads workflows, starts runs, plans jobs, keeps logs, secrets and self-hosted runners |
16| `services/runner` | A TypeScript Worker on Cloudflare Containers | Starts a sandbox per job, and owns what leaves it |
17| `crates/runner` | The `g1t-runner` binary | Runs inside every sandbox and on every self-hosted runner, does one job, and exits |
18
19`crates/actions` holds the workflow language itself, with no I/O:
20syntax, `${{ }}` expressions, filters, matrices and cron. The Worker plans
21with it and the sandbox runs with it, so the two cannot read a workflow
22differently.
23
24## A workflow job
25
26<Diagram name="actions-job" />
27
281. **Trigger.** `actions` reads its event queue, `g1t-events-actions`, ten
29 at a time (step 1), and matches each event to the workflows in
30 `.g1t/workflows` at the commit the event is about (the default branch
31 for schedules, work items and manual runs; `services/actions/src/sync.rs`).
32 g1t never reads `.github/workflows`. The files are GitHub Actions
33 syntax, unchanged; [GitHub
34 Actions](/guides/actions/) lists what is supported.
352. **Plan.** It expands matrices, orders jobs by `needs`, and reports the
36 run as pending on the commit (steps 2 and 3).
373. **Start.** A hosted job is taken by flipping its row from `queued` to
38 `in_progress` in one statement, so two starters can never both take it.
39 `actions` asks `runner` for a sandbox (step 4), and `runner` starts a
40 Container for that job alone (step 5).
414. **Run.** The sandbox reports its steps and logs to the API, with the
42 job's own token (step 6), which passes them to `actions` (step 7). The
43 answer can tell the job to stop, which is how **Cancel** reaches a
44 running job (step 8).
455. **Finish.** The run's conclusion goes onto the commit as a status
46 (step 9), where [required checks](/architecture/work/#checks-and-statuses)
47 find it, and `workflow.completed` is published (step 10).
48
49A job that says nothing for 10 minutes is taken as lost. A cron in
50`actions` runs every minute for schedules, jobs waiting for room, and
51jobs whose runner went quiet.
52
53### Limits
54
55| | |
56| --- | --- |
57| Hosted jobs running at once | 4 per workspace always, up to 12 while the pool of 40 sandboxes keeps 8 free for other workspaces |
58| A hosted job's timeout | 60 minutes at most |
59| A self-hosted job's timeout | 24 hours at most |
60| Logs | 4 MiB a job, kept 90 days |
61| Workflows per repository | 50 |
62| Schedules | Paused after 60 days without a push |
63
64### Logs, caches and artifacts
65
66Logs are kept in D1, in chunks, deduplicated so a report sent twice is
67stored once. Cache entries and artifacts go to the R2 bucket
68`g1t-actions-cache`, uploaded through the API: cache entries under `c/`
69for 30 days, artifacts under `a/` for 91. `actions` lists them and decides
70what is kept.
71
72## Sandboxes on g1t's runners
73
74`runner` defines three Container classes, picked by `runs-on`, each a
75named Cloudflare instance type:
76
77| `runs-on` | Class | Size |
78| --- | --- | --- |
79| Any other hosted label, and every other kind of work | `AttemptSandbox` | `standard-3`: 2 vCPU, 8 GiB memory, 16 GB disk |
80| `g1t-2core` | `Sandbox2Core` | `standard-3`: 2 vCPU, 8 GiB memory, 16 GB disk |
81| `g1t-4core` | `Sandbox4Core` | `standard-4`: 4 vCPU, 12 GiB memory, 20 GB disk |
82
83`g1t-2core` is kept as a name of the standard machine, so workflows that
84ask for it keep working; it keeps its class and its own `max_instances`
85in `services/runner/wrangler.jsonc`. The contracts' instance table
86(`INSTANCE_TYPES`) and billing's price keeper say the same sizes, and a
87test holds them to `wrangler.jsonc`.
88
89Every sandbox starts from one image: `services/runner/base/Dockerfile`,
90which has Node, Python, Go, Rust, Java, .NET, Ruby and Docker, with the
91`g1t-runner` binary on top. Hosted runners are Linux only.
92
93<Diagram name="sandboxes" />
94
95### Egress
96
97A sandbox starts with no internet. Every HTTP and HTTPS request it makes
98goes through the runner Worker's outbound handler (`src/egress.ts`,
99`src/guard.ts`), which decides what may leave. Other protocols have no
100route out at all.
101
102### Secrets a job never holds
103
104A secret can be **brokered**: the job sees a stand-in, `g1tsec_…`, and
105the egress handler swaps in the real value only on a request to a host
106the secret allows (`src/broker.ts`). On a self-hosted runner, the job sends
107the request to `POST /actions/jobs/{job}/broker` instead, and g1t makes
108it, so the value never reaches your machine at all.
109
110## Runners of your own
111
112A [self-hosted runner](/guides/self-hosted-runners/) only ever calls out,
113over HTTPS, so it needs no open port:
114
115| Call | What it does |
116| --- | --- |
117| `POST /runners/register` | Trades a registration token, `g1trt_…`, valid for an hour, for a runner credential, `g1tr_…` |
118| `POST /runners/{id}/poll` | Waits up to 20 seconds for a job; doubles as the heartbeat |
119| `POST /runners/{id}/finished` | Says the job is over |
120| `POST /runners/{id}/remove` | Signs the runner out |
121
122The runner credential is stored only as a hash and rotates every day on a
123poll. A runner that has not polled for 90 seconds is offline; one offline
124for 14 days is removed. A job goes to a runner that has every one of its
125`runs-on` labels and whose group allows the repository, and waits up to 24
126hours for one.
127
128### Three walls
129
130On your machine, `g1t-runner` puts three walls between a job and the
131host:
132
1331. **A VM.** WSL2 on Windows, with drives, interop and the Windows `PATH`
134 turned off, vfkit on macOS, or QEMU on Linux (`deploy/sandbox`).
1352. **A Sysbox container**, the runner itself, with its own dockerd.
136 It refuses to start if the host's `docker.sock` is mounted
137 (`deploy/runner`).
1383. **A container per job**, new each time.
139
140Native Windows jobs run in Windows Sandbox instead. Each runner reports
141its isolation, `nested-container`, `vm`, `container` or `host`, and a
142`host` runner never gets a public repository's jobs. The [desktop
143app](/guides/desktop/) runs the sandboxed runner by default while it is
144open; Docker or direct execution happen only if you choose them.
145
146## Other work in sandboxes
147
148`g1t-runner` takes a `MODE`, so the same image does more than workflows:
149
150| Mode | Started by | Does |
151| --- | --- | --- |
152| `actions` | `actions` | A workflow job |
153| `mergecheck` | `runner`, when a pull request changes | Checks whether a pull request merges cleanly |
154| `deploy` | `deployments` | Builds an app ([Deployments](/architecture/deployments/)) |
155| `bump` | `security` | Makes a dependency update |
156| `backup` | `runner`'s sweep, every 5 minutes | Cuts a repository backup ([Git](/architecture/git/#backups)) |
157
158The merge queue needs no sandbox of its own: `work` builds each queued
159state in the repository itself ([Work](/architecture/work/#the-merge-queue)),
160and only the workflows that run on `merge_group` run in sandboxes, as
161workflow jobs.
162
163Compute is admitted through billing: a sandbox starts only after
164`billing` holds its estimated cost (`reserve`), and the hold is settled
165with the real cost when it ends (`settle`).
166
167<Aside type="caution" title="Self-hosted installations">
168 A [self-hosted g1t](/guides/self-hosting-architecture/) binds `runner`
169 to a Worker that answers that it is off, so workflows, the merge queue
170 and deployments do not run there yet.
171</Aside>