- a preset: the settings every repository shares, such as labels, title rules, review rules and the default branch ruleset;
- a folder of templates: files every repository should have, such as
LICENSE,SECURITY.md,dependabot.ymland the smartcloud workflow itself.
- No drift. Forty repositories stop holding forty slightly different copies of the same files.
- One review for a policy change. Tightening a rule is one pull request in the hub, not one per repository.
- Rules that stick. A repository can add to the preset but not weaken it, and a pull request that edits a synced file by hand fails a check.
my-org. Replace my-org with your organisation’s login throughout. It takes about an hour, most of it writing your own templates. Resnovas runs its own repositories this way from Resnovas/.github, which is a useful full-size example once you have the basics working.
What you will build
- Someone changes the preset or a template in
my-org/.githuband it merges tomain. - Each repository runs its smartcloud workflow: on a push to its default branch, on a schedule, or when run by hand.
- Every run reads the preset, so a preset change applies straight away.
- The sync part of the run renders every template with that repository’s values and compares the result with its files. If anything differs, it opens or updates one pull request,
chore(sync): sync files from my-org/.github. - A maintainer reviews and merges it like any other pull request.
Before you start
You need:- to be an owner of the organisation, to create the app and organisation secrets;
- the GitHub CLI signed in (
gh auth login), to check things from your machine; - Node 24 or later, to run the smartcloud CLI (
npx @resnovas/smartcloud).
Step 1: create the hub repository
Create my-org/.github
.github in your organisation (New repository, owner my-org, name
.github). The name is not required by smartcloud, but GitHub gives a public .github repository special jobs,
such as default community health files and the organisation profile (profile/README.md), so it is the natural
home.Add the folders
smartcloud/ for the preset and templates/ for the shared files. You will fill both in the next steps.Step 2: create a GitHub App for the organisation
The workflow token every Actions run gets cannot read another private repository, cannot push changes to workflow files, and cannot change repository settings. Sync and settings need all three, so the hub setup acts as a GitHub App.Create the app
my-org-bot; its commits and pull requests appear as my-org-bot[bot]. It needs Contents, Pull requests and Workflows (read and write) for sync, and Administration (read and write) for settings.Install it on all repositories
Store its credentials once, for the whole organisation
- Variables tab:
SMARTCLOUD_APP_ID, the app’s ID. - Secrets tab:
SMARTCLOUD_APP_PRIVATE_KEY, the whole.pemfile from Generate a private key. Give it access to all repositories.
Step 3: write the preset
Createsmartcloud/base.yml in the hub. This is the organisation’s shared settings file: everything in it applies to every repository that extends it, and is locked there (Add, never change).
sync section is what makes this a hub:
Step 4: write the templates
Every file undertemplates/ is placed at the same path in each repository: templates/SECURITY.md becomes SECURITY.md, and templates/.github/dependabot.yml becomes .github/dependabot.yml. A template can be synced whole or in part.
Placeholders: the parts that differ per repository
Write{{KEY}} wherever a value differs, for example templates/SECURITY.md:
ORG_NAME, RESPONSE_DAYS). Anything else in double braces is left alone, so GitHub Actions expressions such as ${{ github.token }} in a workflow template are safe.
Where values come from:
- The preset’s
sync.valueshold what is the same everywhere. They are locked: a repository cannot change them. - A repository’s own
sync.valuesadd keys the preset does not set. That is how one repository saysRESPONSE_DAYS: '2'while the rest take the default.
Managed blocks: files a repository can add to
A template synced whole replaces the repository’s file every time. That suitsLICENSE or SECURITY.md. For files each repository needs to extend, such as dependabot.yml, mark the part the hub owns:
- Lines between
house:managed:beginandhouse:managed:endbelong to the hub. Every sync rewrites them. - Everything else belongs to the repository, and sync never touches it.
house:localmarks where its own lines go. - The marker names are fixed (they are not changed to your organisation’s name), and they only count on a comment line:
#in YAML, TOML and CODEOWNERS,//in JSON with comments,<!--in Markdown.
The two templates that tie it together
Two templates make the whole thing self-sustaining. The first is each repository’s settings file, whose managed block says “use the hub” and leaves the rest to the repository:0 7 * * 1, Mondays at 07:00 UTC) is how often each repository checks for template changes without a push. Pin third-party actions to a full commit SHA in production.
Add the rest of your shared files the same way: LICENSE (with {{COPYRIGHT_YEAR}} and {{COPYRIGHT_HOLDER}}), CODE_OF_CONDUCT.md, issue forms under .github/ISSUE_TEMPLATE/, a pull request template. An executable template, such as a script, makes its synced file executable too.
Step 5: let the hub sync itself
The hub is a repository too, and it should follow its own rules. Copy the two templates from step 4 into the hub’s own.github/ folder, exactly as they are, and commit everything to main.
- The hub extends its own preset. Reading a preset in the same repository always works, whatever the token.
- Its sync renders
templates/into the hub itself, so its own.github/smartcloud.yml, workflow andLICENSEstay in step with what every other repository receives. - The edit check is off in the hub, because changing templates is its job: a pull request that edits
templates/never failsSYNCthere.
Step 6: connect a repository
For each repository that should follow the hub:Add the two files
templates/.github/smartcloud.yml to the repository’s .github/smartcloud.yml, and templates/.github/workflows/smartcloud.yml to .github/workflows/smartcloud.yml. Everything else arrives with the first sync.Add what the preset leaves open
house:local line, add this repository’s own settings: its CI job, environments, extra labels, and any template it keeps its own copy of:Check it from your machine
validatereads the preset and printsBuilt from: my-org/.github/smartcloud/base.yml@main, .github/smartcloud.yml. A value that conflicts with the preset fails here, before it can fail a run.doctorchecks the token can read the hub, and thatSMARTCLOUD_APP_IDandSMARTCLOUD_APP_PRIVATE_KEYexist for the repository.sync --outwrites every file the first sync would propose into/tmp/api-sync, with its status (added, updated, unchanged), and lists conflicts. A missing placeholder value shows up here.
Commit and run
Step 7: the first sync pull request
Within a minute of that push, the repository has a pull request:- Title:
chore(sync): sync files from my-org/.github. - From the
org/syncbranch, bymy-org-bot[bot], labelledorg-syncby the preset’s labelling rule. - Body: every file, marked as added, updated or made executable.
- Commits are signed by GitHub, because they are made through the API with the app’s token, so they pass a ruleset that requires signed commits.
- A file synced whole is replaced by the template. Review the diff: if the repository needs its own version, add the path to
sync.excludeand the next sync drops it from the pull request. - A file with a managed block keeps the repository’s content: the first time, the old content is kept, commented out, at the
house:localline, so nothing is lost silently. Move back anything you still need as real lines, and delete the rest.
settings and creates its labels; the job summary lists every change.
Step 8: roll out a change to every repository
This is what the hub is for. Say you want every repository’s Dependabot to check npm weekly.Change the hub in a pull request
templates/.github/dependabot.yml (or smartcloud/base.yml, for a rule change) on a branch of my-org/.github, say npm-updates.Preview it against a real repository
my-org/api, and any conflict with that repository’s own lines. For a preset change, run smartcloud validate in a few repositories with the preset reference in their file pointed at your branch, to catch a repository whose own settings would now conflict with it.Merge it
@main:- a preset change applies on each repository’s next run of any kind;
- a template change arrives as a sync pull request on each repository’s next push to its default branch, its next scheduled run, or a manual run.
Roll it out now instead of waiting for the schedule
Everyday rules for repositories
- Add, never change. Below
house:local, add new labels and rules and the keys the preset leaves open. A different value for something the preset sets fails the run withcannot change "...": it is set by .... To change a shared value, change the preset. - Edit templates in the hub, not their copies. A pull request that edits a synced file, or the managed block of one, fails the
smartcloud / synccheck withSYNC(a warning for a maintainer). The next sync would put the content back anyway. - Opt out of a file with
sync.exclude, never by deleting it: deleting a synced file fails the check too. - Never push to the sync branch. It is force-updated on every run.
Common problems
Sync was skipped: restricted access
Sync was skipped: restricted access
SMARTCLOUD_APP_ID
and SMARTCLOUD_APP_PRIVATE_KEY are visible to it (organisation secrets can be limited to selected repositories).
smartcloud doctor --repo my-org/name names what is missing. Pull requests from forks and Dependabot are always
restricted; that is expected.SECURITY.md: no value for {{SECURITY_EMAIL}}
SECURITY.md: no value for {{SECURITY_EMAIL}}
sync.values sets. Add it to the
preset if it is the same everywhere, to the repository if it differs, or write {{SECURITY_EMAIL:-a default}} in
the template.Refusing to allow a GitHub App to create or update workflow
Refusing to allow a GitHub App to create or update workflow
.github/workflows/smartcloud.yml. Add it on
the app’s page; each organisation owner then accepts the new permission on the installation.A preset's rules were not checked on a pull request
A preset's rules were not checked on a pull request
smartcloud for them.cannot change ... it is set by my-org/.github/smartcloud/base.yml@main
cannot change ... it is set by my-org/.github/smartcloud/base.yml@main
The sync pull request keeps undoing a local change
The sync pull request keeps undoing a local change
house:local line, or add
the file to sync.exclude if the repository should own it outright.