Note
Stop copy-pasting workflow files across repos. Define canonical templates once, sync them everywhere, and get PRs when repos drift.
orchestrator repo
βββ orchestrator.toml # which repos to manage
βββ templates/
β βββ rust-ci.yml # Tera-powered workflow templates
β βββ node-ci.yml
β βββ pr-title.yml
βββ .github/workflows/
βββ sync.yml # scheduled Action that runs orcastrate
Orcastrate runs as a scheduled GitHub Action in your central orchestrator repo. On each run it:
- Reads your repo list from
orchestrator.toml - Scans each repo's
.github/workflows/for files with@orcastratefrontmatter - Renders the referenced template with the declared params
- Compares the rendered output against the current file
- Opens one PR per drifted workflow
Workflow files opt in to management via a comment block at the top:
# @orcastrate
# template: rust-ci
# params:
# toolchain: stable
# features: ["serde", "async"]
# @end-orcastrate
name: CI
on: [push]
# ... rest of workflow managed by orcastrateOnly files with this block are touched. Everything else is ignored.
Create a new repo in your org (e.g. myorg/workflow-orchestrator).
# orchestrator.toml
[orchestrator]
templates_dir = "templates"
[[repos]]
name = "myorg/service-api"
[[repos]]
name = "myorg/service-web"# templates/rust-ci.yml
name: CI
on:
push:
branches: [{{ default_branch | default(value="main") }}]
pull_request:
branches: [{{ default_branch | default(value="main") }}]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@{{ toolchain | default(value="stable") }}
- run: cargo test --all-features# .github/workflows/sync.yml
name: Orcastrate Sync
on:
schedule:
- cron: "0 8 * * 1-5"
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo install orcastrate
- run: orcastrate --config orchestrator.toml sync
env:
ORCASTRATE_TOKEN: ${{ secrets.ORCASTRATE_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}In each managed repo, add the frontmatter block to workflow files:
# @orcastrate
# template: rust-ci
# params:
# toolchain: stable
# @end-orcastrate
name: CI
# ... orcastrate will manage the restorcastrate sync # sync all repos, open PRs for drift
orcastrate sync --dry-run # see what would change without modifying anything
orcastrate sync --repo org/repo # sync a single repo
orcastrate validate # check config + templates are valid
orcastrate drift # check drift status without creating PRs
orcastrate list-repos # show configured + discovered repos
orcastrate list-templates # show available templates
Verbosity: -v for debug, -vv for trace, -q for quiet.
[orchestrator]
templates_dir = "templates" # where templates live
branch_prefix = "orcastrate/sync" # PR branch prefix
pr_label = "orcastrate" # label added to PRs
dry_run = false # global dry-run toggle
[[repos]]
name = "myorg/repo-a"
[[repos]]
name = "myorg/repo-b"
enabled = false # temporarily skip
# auto-discover repos by org topic
[discovery]
org = "myorg"
topic = "managed-workflows"Templates use Tera (Jinja2-style) syntax. Params declared in frontmatter are injected at render time:
# @orcastrate
# template: rust-ci
# params:
# toolchain: nightly
# features: ["serde", "tokio"]
# default_branch: develop
# @end-orcastrateTemplates can use defaults: {{ toolchain | default(value="stable") }}
Orcastrate uses two tokens for different operations:
| Operation | Token | Why |
|---|---|---|
| Git writes (tree, commit, ref) | ORCASTRATE_TOKEN |
Needs workflows scope for .github/workflows/ files |
| PR creation, labels | GITHUB_TOKEN |
PRs appear as github-actions[bot] |
- Create a fine-grained PAT with permissions: Contents (R/W), Pull requests (R/W), Workflows (R/W)
- Add it as a repo secret named
ORCASTRATE_TOKEN GITHUB_TOKENis provided automatically by GitHub Actions
For org-wide use, create a GitHub App instead of a PAT:
- Repository contents: Read & Write
- Pull requests: Read & Write
- Workflows: Read & Write
Install it on your org, then set:
ORCASTRATE_APP_IDORCASTRATE_PRIVATE_KEYORCASTRATE_INSTALLATION_ID
When orcastrate detects drift, it opens one PR per workflow file:
- Branch:
orcastrate/sync/{workflow-name}(e.g.orcastrate/sync/pr-title) - Title:
chore(ci): sync `pr-title` from template `pr-title` - Unified diff in the PR body
- The
orcastratelabel for easy filtering
If a PR already exists for the same workflow, it updates the existing PR instead of creating a new one.
- No external server β runs entirely within GitHub Actions
- State in git β config and templates are version-controlled, auditable
- PR-based updates β never force-pushes to your default branch
- One PR per workflow β review and merge each change independently
- Opt-in per file β only workflows with
@orcastratefrontmatter are managed - Template validation β rendered output is validated as YAML before opening a PR
- Conventional commits β PR titles and commits follow conventional commit format
MIT
