TILLFORGE · CI & RUNNERS

Your code, your hardware, your gate.

GA

TillForge CI runs the jobs you declare in .tillforge/ci.yml on runners you host. A push, a pull request, or a manual trigger freezes each job’s spec at the exact commit and queues it; a registered runner claims it, runs every step in its own locked-down container, and reports a check back. Those checks feed the same branch protection and merge queue that guard your branches — CI plugs into the gate, it doesn’t sit beside it. Deploys ride the same runner.

01Config

Declare jobs in .tillforge/ci.yml

Commit a .tillforge/ci.yml at the repo root. It’s read at the exact commit that triggered the run and frozen into the job — a config push mid-queue never retargets a job already waiting. A repo with no file simply runs no CI; a present-but-invalid file surfaces one visible error run rather than failing silently.

yaml
# .tillforge/ci.yml — read at the exact commit, frozen into each job.
# 1–10 jobs; each runs in its own locked-down container on your runner.
jobs:
  test:
    image: node:22-alpine        # any plain container reference (default: alpine:3)
    timeout: 900                 # seconds, 30–3600 (default 900)
    env:                         # up to 20 UPPER_SNAKE keys
      CI: "true"
    steps:                       # 1–50 shell steps, run in order; non-zero fails
      - npm ci
      - npm test
  lint:
    image: node:22-alpine
    steps:
      - npm ci
      - npm run lint
FieldRule
jobs1–10 named jobs; names are 1–32 chars of [a-z0-9._-].
imageAny plain container reference. Default alpine:3.
steps1–50 shell steps, run in order; the first non-zero exit fails the job.
timeoutSeconds, 30–3600. Default 900.
envUp to 20 UPPER_SNAKE keys, values ≤ 500 chars.
02Runners

Register and run a runner

A runner is a machine you own that executes queued jobs — nothing runs on shared infrastructure. Register one under Forge → Runners to mint a tfr_ token (shown once, stored only as a hash), drop it into the runner’s env file, and start tillforge-runner on an isolated host.

bash
# 1 · Register a runner in the dashboard (Forge → Runners). The tfr_ token
#     is shown ONCE and stored only as a hash. Put it in the runner's env file:

# /etc/tillforge/tillforge-runner.env
TILLFORGE_RUNNER_URL=https://tilldev.dev
TILLFORGE_RUNNER_TOKEN=tfr_xxxxxxxxxxxxxxxx
TILLFORGE_RUNNER_NAME=ci-1

# 2 · Install tillforge-runner on an isolated machine and start it. It claims
#     the oldest queued job for your org, runs each in its own container, streams
#     logs back, and writes a ci/<job> check. With nothing queued it simply waits,
#     polling for work — no jobs run until you push, open a PR, or trigger by hand.
Each job is contained; each run is attributed
Every job runs in its own locked-down container, so a repo’s CI can’t reach across to another’s. A pull-request trigger only auto-runs when the PR author holds write access on the base repository — a fork PR from a read-only member never executes on your hardware until a maintainer runs it deliberately.
Liveness is honest
The Runners screen shows a runner online while it is actively polling; the dot reads real poll activity, so a runner that stopped shows offline within a minute or two.
03The gate

Checks feed branch protection

When a job finishes it writes a ci/<job> check against the commit. Name those checks in a branch’s protection rule under required checks and a pull request can’t merge until they pass — and if the branch uses the merge queue, the queue re-runs them on the exact merge result, not just the branch tip. This is why CI lives inside the same machinery as reviews and signed commits: one gate, every requirement.

04Triggers

Push, pull request, or on demand

Pushes and pull requests enqueue CI automatically. Trigger a run by hand — for a re-check or a branch that predates the config — from the CLI:

bash
# Runs fire on push and on pull requests automatically. Trigger one by hand:
tilldev forge ci run --repo acme-api                 # default branch tip
tilldev forge ci run --repo acme-api --ref feat/x    # a specific ref

tilldev forge ci list --repo acme-api                # runs, newest first
tilldev forge ci logs <job-id> --repo acme-api       # one run's full log
05Deploys

Deploys ride the same runner

A deployment is a CI-shaped job the same runner claims, clones, and builds — then a final step ships the output through your provider’s CLI (or, for a desktop bundle, signs and publishes native installers). The provider credential is decrypted only at claim time and injected into that one build’s environment — never stored in the job, never printed in the log. So the runner you stand up for CI is the same one that ships production.


Next: Branch protection to require these checks, or Deployments to ship on the same runner. Back to the TillForge overview.