Declarative trunk-based CI/CD for GitHub Actions.
Define what to build and where to deploy in one manifest.
cascade generates the GitHub Actions wiring, tracks deployment state, manages releases,
and cascades promotions through your environments.
The manifest (.github/manifest.yaml) is the single source of truth. It holds both the pipeline configuration and the live deployment state for every environment. You run cascade generate-workflow once; after that the generated workflows own their own execution.
Merge to trunk
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Orchestrate workflow (generated) │
│ Setup → Validate → Build(s) → Deploy(s) → Finalize │
│ │
│ • Change detection: only run what changed │
│ • Version computation: next semver RC from commits │
│ • State written to manifest.yaml on every run │
└─────────────────────────────────────────────────────────────┘
│
▼ state[dev] updated, draft release created
│
│ workflow_dispatch (promote)
▼
┌─────────────────────────────────────────────────────────────┐
│ Promote workflow (generated) │
│ Preflight → Deploy(s) → Publish callback → Finalize │
│ │
│ • Same artifacts, never rebuilt on promote │
│ • Breaking-change gate at prerelease → release boundary │
│ • Per-deploy change detection: skip unchanged │
│ • Release published, RC tags cleaned up │
└─────────────────────────────────────────────────────────────┘
go install github.com/stablekernel/cascade/cmd/cascade@latest
# or pin a specific version:
go install github.com/stablekernel/cascade/cmd/cascade@v0.1.0# .github/manifest.yaml
ci:
config:
trunk_branch: main
cli_version: v0.1.0
environments: [dev, test, uat, prod]
builds:
- name: app
workflow: .github/workflows/build-app.yaml
triggers: [src/**, go.mod]
deploys:
- name: infra
workflow: .github/workflows/deploy-infra.yaml
triggers: [cdk/**]
- name: app
workflow: .github/workflows/deploy-app.yaml
depends_on: [app] # waits for build-app to succeed
changelog:
contributors: truecascade generate-workflow --config .github/manifest.yaml
# Creates: .github/workflows/orchestrate.yaml
# .github/workflows/promote.yamlCommit the generated files. cascade re-generates them whenever you update the manifest; the -f flag overwrites in place.
cascade calls your workflows via workflow_call and passes standard inputs. You own the build and deploy logic.
# .github/workflows/build-app.yaml
on:
workflow_call:
inputs:
environment:
type: string
required: true
sha:
type: string
required: true
outputs:
artifact_id:
description: 'Immutable artifact identifier (e.g., Docker image digest)'
value: ${{ jobs.build.outputs.artifact_id }}cascade is a metadata courier. You construct the registry and deploy operations yourself.
cascade generates workflows that handle the orchestration layer. Your callback workflows handle the domain logic. The manifest gives you control over:
- Change detection: builds and deploys run only when their declared
triggersmatch changed paths. - Dependency ordering:
depends_onchains builds and deploys in the right order. - Matrix builds: fan out a single build over a matrix of inputs.
- Per-job runner selection: set
runs_onat the config or per-build/deploy level. - Concurrency control: configurable group and cancel-in-progress on orchestrate, promote, release, and external-update workflows.
- Extra triggers: attach
schedule,repository_dispatch,workflow_run, andmerge_groupevents to orchestration. - Dispatch inputs: expose operator-facing manual-run inputs on the generated
workflow_dispatch. - PR plan preview: a comment on each PR shows which builds and deploys would run.
- Merge queue lane: a dedicated gate job runs before merge to protect trunk.
- Action pinning:
pin_mode: shaemits pinned SHA references for all cascade-managed action calls. Override individual actions viaaction_pins. - Breaking-change gate:
feat!:orBREAKING CHANGE:commits block the prerelease-to-release boundary unless you override them. - Artifact passing: the
artifact_idoutput from build callbacks is stored in state and forwarded to deploys and the publish callback. - Publish callback: once a release is published, a separate workflow call lets you retag RC artifacts in your registry.
- Schema version enforcement: every CLI invocation checks
schema_versionon the manifest and rejects incompatible manifests with a clear error.
For a no-environment project (library or CLI), omit environments entirely. Commits produce RC pre-releases; a promote dispatch publishes the final release.
Promotions are triggered via workflow_dispatch on the generated promote.yaml.
| Mode | Behavior |
|---|---|
default |
Advance the chain one logical step |
dev-to-test |
Promote dev to test |
dev-to-uat |
Cascade: dev → test → uat (all intermediates updated atomically) |
dev-to-prod |
Full cascade through all environments |
uat-to-prod |
Partial cascade from uat onward |
The same artifacts built on the first merge are promoted through the chain; nothing is rebuilt.
The manifest tracks deployment state automatically. The state: section is managed by cascade; do not edit it by hand.
ci:
state:
dev:
sha: abc123
version: v1.2.0-rc.3
committed_at: "2025-01-15T10:30:00Z"
committed_by: github-actions[bot]
builds:
app:
sha: abc123
artifact_id: sha256:def456
built_at: "2025-01-15T10:30:00Z"
deploys:
infra:
sha: abc123
deployed_at: "2025-01-15T10:31:00Z"
release:
sha: abc000
version: v1.1.0
latest_release:
version: v1.1.0
sha: abc000| Command | Description |
|---|---|
generate-workflow |
Generate orchestrate.yaml and promote.yaml |
orchestrate setup |
Detect changes, compute version, plan execution |
orchestrate finalize |
Update state, manage release, commit manifest |
promote preflight |
Validate, compute promotions, check breaking changes |
promote finalize |
Update state after promotion deploys complete |
generate-changelog |
Create changelog from conventional commits |
manage-release |
Create, update, or publish GitHub releases |
next-version |
Calculate next semantic version |
detect-changes |
Determine which builds/deploys a file change triggers |
parse-config |
Validate and print the parsed manifest (with schema warnings) |
reset |
Wipe releases and state (for testing or a fresh start) |
Full flag reference: docs/cli-reference.md.
| Document | Description |
|---|---|
| Getting Started | Step-by-step setup guide |
| Configuration | Full manifest reference |
| Workflows | Orchestrate and Promote explained |
| CLI Reference | All commands and flags |
| Callback Contract | How to write build/deploy/publish workflows |
| Architecture | System design and internals |
| Schema Versioning | Compatibility policy and migration guide |
cascade is functional and self-hosted. Its own releases page shows the full pipeline running end to end. The remaining work before the v1.0.0 schema freeze falls into two areas.
Schema coverage. A few GitHub Actions capabilities are modeled in the manifest shape but not yet emitted by the generator: environment gates, OIDC token configuration, and per-environment runner overrides. These sit on the direct path to v1.0.0.
Hardening. This covers schema version enforcement (shipped), compatibility docs (docs/versioning.md), and more e2e coverage. The added tests confirm that the generated workflows behave correctly under edge cases such as empty builds, cross-repo coordination, and rollback to N-1.
The manifest schema field shapes were frozen in v0.1.0 as the v1 contract baseline. Minor versions between now and v1.0.0 may add new optional fields; no existing fields will be removed or renamed before v1.0.0.
Open work is tracked in GitHub Issues.
cascade follows these conventions in its own codebase and in the generated workflows it produces:
- Additive manifest changes: new fields are always optional with sensible defaults, so existing manifest files keep working across minor version bumps.
- Conventional commits: commit messages follow
type: subject(for examplefeat:,fix:,docs:), and the changelog generator reads this format. - Callback isolation: generated workflows call your workflows via
workflow_call, and cascade never reaches into your callback logic. - Metadata courier: cascade passes artifact identifiers and versions between stages. It never touches your container registry, package registry, or deployment target directly.
# Build
go build -o cascade ./cmd/cascade
# Test (all packages)
go test ./...
# E2E tests (requires Docker)
cd e2e && go test -v -timeout 20m ./...
# Lint
golangci-lint run ./...
# Regenerate cascade's own workflows (uses itself)
go run ./cmd/cascade generate-workflow --config .github/manifest.yaml -fContributions are welcome. Please read CONTRIBUTING.md for development setup and workflow details.
cascade uses the Developer Certificate of Origin. Sign off each commit with git commit -s. By participating you agree to the Code of Conduct.
Apache 2.0. See LICENSE.