This is the public, secret-free starting point for an organization's private ci-fleet configuration repository. It records which trusted projects may use each CI pool, the reviewed desired state for controller machines, infrastructure capacity budgets, logical deployment environments, and the standardized commands every project must expose.
It does not contain runner registration tokens, deploy credentials, private keys, host addresses, VM IDs, storage names, backup identifiers, or .env files. The sole address exception is a reviewed Docker default_address_pools[].base CIDR after an adopter has staged engine support. It is fleet capacity policy, not a host or service address. Public examples and fixtures use RFC 5737 ranges only.
flowchart LR
E[Public ci-fleet engine] -->|pinned engine commit| C[Private configuration]
C -->|pool policy and capacity budget| A[Controller at site A]
C -->|pool policy and capacity budget| B[Controller at site B]
P[Authorized project repositories] -->|all independent jobs| G[GitHub runner group]
A --> G
B --> G
G --> R[Ephemeral Docker workers]
P -->|approved image digest| D[Development hosts]
P -->|manual approval and same digest| X[Production hosts]
S[GitHub Environments or host secret store] -. secret values .-> P
classDef public fill:#dff4ff,stroke:#1570a6,color:#102a43
classDef private fill:#fff3cd,stroke:#9a6700,color:#3d2b00
class E public
class C,A,B,P,G,R,D,X,S private
-
Create a private repository from this public template.
-
Clone it and initialize the first project and controller:
./scripts/init.sh \ --organization your-org \ --project your-app \ --controller ci-01 \ --location primary-site \ --capacity-budget 1 \ --max-runners 1 \ --engine-ref <reviewed-ci-fleet-commit>
-
Edit
fleet.jsonto add the organization's real logical mappings. -
Run the strict policy check:
./scripts/validate.sh --strict
-
Configure secret values in GitHub Environments, root-owned host files, or an external secret manager. The repository stores only names such as
DEPLOY_AUTH.
The initializer refuses to replace a configured file unless --force is explicit. Run ./scripts/init.sh --help for repository, registry, runner-group, controller, location, capacity, resource, and output options.
template-compatibility.json pins the reviewed embedded template to ci-fleet commit 0aed0d7e85e10050028b7d11fb12b84b3619e638. It records schema and validator/initializer support, optional staged capabilities, the standalone new-controller safety check, and the unchanged fictional example engine pin. engine-rollout-evidence.json records active per-controller capability evidence in the shape consumed by that pinned core. New status-reporting or Docker network-policy fields require prior-state evidence. Upgrade an engine with active optional capabilities in exactly two integrated commits:
- Keep
fleet.jsonandengine-rollout-evidence.jsonon engine A. Add reviewed engine B capability evidence to optional adopter-ownednext-engine-rollout-evidence.json, using the same schema-version-1 record shape. - Change
engine_refto engine B, promote the matching sidecar record toengine-rollout-evidence.json, and remove the sidecar when it has no remaining staged records.
The committed examples and initializer omit both optional fields because their engine capability has not been staged. When a private adopter later stages docker_network_policy, it must replace the RFC 5737 review fixture with a reviewed operational pool before strict validation passes. Initial release preparation and exclusions are in docs/RELEASE.md; no tag or GitHub release is published by this repository change.
fleet.json is the reviewed authority for logical controller state. Each entry in controllers has a unique ID and declares:
- its runner pool and logical location;
- whether it is
active,drained, ordisabled; - its unique GitHub scale-set name;
- an
experimental,stable, orretiringlifecycle; - the full reviewed ci-fleet commit SHA it runs;
- a zero managed minimum and reviewed maximum runner capacity;
- CPU and memory available to each ephemeral runner.
The controller ID is how a target host selects its declaration. A location is a non-sensitive logical slug such as primary-site or remote-site, never an address. Runtime-generated configuration and credentials remain host-local.
Each runner pool has a capacity_budget and a runner group that must not be assigned to any other pool. Unique runner-group assignment keeps routing and repository authorization unambiguous. The semantic validator enforces this cross-object rule because JSON Schema cannot compare values stored in separate object properties.
The validator totals the maximum capacity of every active or drained controller assigned to the pool and rejects overcommit. Drained capacity remains reserved so an undrain cannot silently exceed the reviewed budget. Disabled controllers do not reserve capacity.
Application repositories do not encode the number of available workers. They submit all independent tasks and shards. Do not use GitHub Actions strategy.max-parallel to model fleet size; controllers and the private configuration decide how many jobs run simultaneously. An application may limit concurrency only for a separately documented external-system constraint, not worker availability.
This separation lets one infrastructure change add, remove, drain, or resize controllers without editing every project workflow.
The public ci-fleet engine owns the host installer. Its intended interface consumes one logical controller from a pinned private configuration revision:
sudo ./scripts/install-worker-controller.sh \
--config-repo example-org/example-fleet-config \
--controller example-ci-01 \
--ref <reviewed-config-commit> \
--installUse --adopt instead of --install to bring an existing controller under Git-authored desired state. The engine contract also provides --check, --upgrade, --rollback, and --uninstall modes.
The command runs on the target Linux Docker machine. It validates the pinned configuration, renders host-local runtime state, preserves root-owned secrets, drains before disruptive changes, verifies a recoverable checkpoint, installs maintenance services, checks health, and reports drift without exposing credentials. OpenClaw or another agent may invoke it, but no agent is required.
The engine-side implementation is scripts/install-worker-controller.sh in the parent ci-fleet repository. Its accepted scope is isolated ordinary-CI fleet hosts under reviewed schema-v3 desired state; this configuration repository never installs a controller by itself.
Host retirement is an explicit reviewed transition:
- Change the controller state to
drainedand merge the private configuration change. - Converge the host and verify that it accepts no new work and has no active runner.
- Remove only fleet-owned residue and verify replacement capacity.
- Unregister its scale set and revoke that host's credentials.
- Change the declaration to
disabledor remove it in a later reviewed change. - Delete or repurpose the machine according to the installation's declared infrastructure policy.
Deleting one generic controller must not require application workflow changes. Legacy project-specific hosts should remain only until CI, promotion, and deployment no longer reference them.
- Public repositories never receive direct access to the trusted self-hosted runner pool.
- Every project publishes
scripts/ci/plan.jsonand implements./scripts/ci/run.sh <task> --shard INDEX/TOTALin its own Docker-defined test environment. ./scripts/ci/run.sh fastandfullremain aggregate developer commands; fleet scheduling expands their named tasks across available workers.- Every matrix job has a five-minute hard timeout, while expected test payload targets four minutes or less to reserve startup and reporting time.
- Application workflows submit all independent jobs; infrastructure configuration alone controls worker capacity.
- A GitHub runner group is assigned to exactly one runner pool.
- CI runner pools and deployment host groups are separate trust roles.
- Production deployment is manual and requires GitHub Environment approval.
- Controller engine revisions, reusable workflows, and third-party actions are pinned to immutable commits.
- Configuration contains logical identifiers only. Secret values, private host details, and credentials never enter Git.
- Promoted artifacts are container image digests; production does not rebuild a different image.
fleet.schema.json provides editor completion and structural documentation. scripts/validate.py is the authoritative dependency-free policy check, including cross-object relationships JSON Schema cannot express clearly.
Projects divide their total test-minutes into independent named tasks and deterministic shards. Forty-five test-minutes require at least nine perfectly balanced five-minute jobs in theory. In practice, projects should create additional shards targeting four minutes of test payload so checkout, image preparation, and reporting remain inside the five-minute job ceiling.
flowchart LR
P[plan.json] --> M[GitHub matrix]
M --> A[lint]
M --> B[unit 1 of 4]
M --> C[unit 2 of 4]
M --> D[integration 1 of 3]
M --> E[other independent shards]
Adding workers reduces wall-clock time only while independent shards remain queued. A genuinely indivisible test longer than five minutes must be optimized, split, or moved into an explicitly slower scheduled class outside ordinary CI.
| Path | Purpose |
|---|---|
fleet.json |
Fictional, valid schema-v3 configuration with one controller |
fleet.schema.json |
JSON Schema draft 2020-12 editor contract |
template-compatibility.json |
Exact reviewed core/template compatibility and prepared release state |
engine-rollout-evidence.json |
Active optional-capability evidence, empty in the public template |
next-engine-rollout-evidence.json |
Optional adopter-owned next-engine evidence sidecar; not committed by this public template |
scripts/init.sh |
Safe first-project and first-controller initializer |
scripts/validate.sh |
Structural, relational, capacity, and secret-boundary validation |
scripts/test_policy.py |
Regression tests proving unsafe configurations fail closed |
examples/multi-host/fleet.json |
Fictional two-project, two-location controller topology |
SECURITY.md |
Secret handling and vulnerability reporting |
AGENTS.md |
Non-negotiable rules for humans and coding agents |
docs/UPDATING.md |
Keeping a derived private repository current: schema vs template versions, releases, migrations, Dependabot |
docs/RELEASE.md |
Prepared initial release notes and immutable-tag publication checklist |
| Safe in this public template | Belongs in the private config repo | Belongs only outside Git |
|---|---|---|
| Schema, validator, fictional examples | Real repository names and runner-group policy | Tokens, passwords, private keys |
| Standard CI entrypoint names | Logical controller IDs and locations | Host addresses, VM IDs, SSH material |
| Controller state and lifecycle vocabulary | Capacity budgets and per-runner limits | Rendered runtime configuration |
| Reusable engine interface | Required secret names | Secret values and application credentials |
The public engine and this template use the Unlicense. See THIRD_PARTY_NOTICES.md before copying third-party material into a derived repository.