Getting Started
This guide walks through setting up cascade in your repository. For the big picture first, read the Stage Graph to see how trunk, your environments, and the release boundary fit together.
Prerequisites
Section titled “Prerequisites”- Go 1.25+ (for the CLI)
- A GitHub repository with Actions enabled
- Trunk-based development (single primary branch)
Step 1: Install the CLI
Section titled “Step 1: Install the CLI”# Install latest stable releasego install github.com/stablekernel/cascade/cmd/cascade@latest
# Install bleeding edge from mastergo install github.com/stablekernel/cascade/cmd/cascade@master
# Install a specific versiongo install github.com/stablekernel/cascade/cmd/cascade@v2.0.4
# Verifycascade versionIn GitHub Actions, generated workflows install the CLI for you via the setup action, so you don’t need to add it explicitly. To pin a version, set cli_version in your manifest.
If you need to invoke it manually:
- uses: stablekernel/cascade/.github/actions/setup-cli@master with: token: ${{ secrets.GITHUB_TOKEN }} # version: latest # or 'beta', or a specific version like 'v2.0.4'The setup action downloads the release archive (tar.gz) from GoReleaser and installs the cascade binary on PATH.
Fast path: scaffold with cascade init
Section titled “Fast path: scaffold with cascade init”If you want a working configuration in one step, run cascade init. It renders the manifest and the callback workflow stubs for you, verifies them through the real generator, and writes them into your repository:
# Two-environment pipeline (dev, prod) in the current directorycascade init --topology two-env
# Or choose your own ordered environments; the last is the release stagecascade init --envs staging,production --name my-service
# Preview without writing anythingcascade init --topology two-env --dry-runThis produces .github/manifest.yaml plus build and deploy stubs under .github/workflows. The manifest already carries a $schema directive, so your editor gives you autocomplete and validation while you fill in the stubs. If a target file already exists, init aborts and lists the conflicts unless you pass --force.
Once scaffolded, skip ahead to Step 3 to fill in the callbacks, then generate the orchestration workflows. The manual walkthrough below covers the same files step by step if you would rather build them yourself.
Step 2: Create the manifest
Section titled “Step 2: Create the manifest”Create .github/manifest.yaml in your repository:
ci: config: trunk_branch: master environments: [dev, test, prod] cli_version: v2.0.4
# Optional pre-build validation validate: workflow: .github/workflows/validate.yaml
builds: - name: app workflow: .github/workflows/build-app.yaml triggers: - "src/**" - "Dockerfile" - "go.mod"
deploys: - name: infra workflow: .github/workflows/deploy-infra.yaml triggers: - "infra/**"
- name: services workflow: .github/workflows/deploy-services.yaml depends_on: [app] # waits for build-app to succeed
# Optional: retag artifacts when an RC is published as final publish: workflow: .github/workflows/publish.yaml
changelog: contributors: true
state: dev: {} test: {} prod: {}The framework owns state: and latest_release:. The state: { dev: {}, ... } skeleton is enough. The workflows fill in the details on every run.
See Configuration Reference for every field.
No-environment mode
Section titled “No-environment mode”For library/CLI projects that publish releases without environment deployments, omit environments:
ci: config: trunk_branch: master cli_version: v2.0.4 builds: - name: cli workflow: .github/workflows/build-cli.yaml triggers: [cmd/**, internal/**, go.mod] changelog: contributors: trueCommits create RC pre-releases automatically; a promote dispatch (default mode) publishes the final release.
Step 3: Create Callback Workflows
Section titled “Step 3: Create Callback Workflows”The framework calls your workflows. Create them following the Callback Contract.
Build Workflow Example
Section titled “Build Workflow Example”.github/workflows/build-app.yaml:
name: Build App
on: workflow_call: inputs: environment: type: string required: true sha: type: string required: true dry_run: type: boolean required: false default: false outputs: artifact_id: description: Immutable artifact identifier (e.g., image digest) value: ${{ jobs.build.outputs.artifact_id }} image_tag: description: Docker image tag value: ${{ jobs.build.outputs.image_tag }}
jobs: build: runs-on: ubuntu-latest outputs: artifact_id: ${{ steps.push.outputs.digest }} image_tag: ${{ steps.meta.outputs.tag }} steps: - uses: actions/checkout@v4 with: ref: ${{ inputs.sha }}
- name: Generate tag id: meta run: | TAG="${{ github.sha }}-$(date +%s)" echo "tag=$TAG" >> "$GITHUB_OUTPUT"
- name: Build image run: docker build -t myrepo/app:${{ steps.meta.outputs.tag }} .
- name: Push image id: push if: ${{ !inputs.dry_run }} run: | docker push myrepo/app:${{ steps.meta.outputs.tag }} DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' \ myrepo/app:${{ steps.meta.outputs.tag }} | cut -d@ -f2) echo "digest=$DIGEST" >> "$GITHUB_OUTPUT"Deploy Workflow Example
Section titled “Deploy Workflow Example”.github/workflows/deploy-services.yaml:
name: Deploy Services
on: workflow_call: inputs: environment: type: string required: true sha: type: string required: true image_tag: type: string required: true dry_run: type: boolean required: false default: false
jobs: deploy: runs-on: ubuntu-latest environment: ${{ inputs.environment }} steps: - uses: actions/checkout@v4 with: ref: ${{ inputs.sha }}
- name: Deploy if: ${{ !inputs.dry_run }} run: | echo "Deploying ${{ inputs.image_tag }} to ${{ inputs.environment }}" # Your deployment logicPublish Workflow Example
Section titled “Publish Workflow Example”If you configured publish: in the manifest, create the callback. It runs once per build when an RC is published as a final release:
name: Publish
on: workflow_call: inputs: build_name: type: string required: true old_version: type: string required: true # e.g., v1.0.0-rc.2 new_version: type: string required: true # e.g., v1.0.0 sha: type: string required: true artifact_id: type: string required: false # immutable digest if your build declares it
jobs: retag: runs-on: ubuntu-latest steps: - name: Retag image run: | docker pull myrepo/${{ inputs.build_name }}:${{ inputs.old_version }} docker tag \ myrepo/${{ inputs.build_name }}:${{ inputs.old_version }} \ myrepo/${{ inputs.build_name }}:${{ inputs.new_version }} docker push myrepo/${{ inputs.build_name }}:${{ inputs.new_version }}Step 4: Generate Orchestration Workflows
Section titled “Step 4: Generate Orchestration Workflows”The CLI generates orchestration and promotion workflows from the manifest:
# Previewcascade generate-workflow --dry-run
# Write the filescascade generate-workflow --forceThis creates:
.github/workflows/orchestrate.yamlruns on merge to trunk.github/workflows/promote.yamlhandles manual promotion between environments
Step 5: Validate
Section titled “Step 5: Validate”cascade parse-configValidates the manifest and prints any errors.
Step 6: Commit and Push
Section titled “Step 6: Commit and Push”git add .github/manifest.yaml .github/workflows/git commit -m "feat: add trunk-based CI/CD"git push origin masterThe orchestrate workflow runs automatically on the next merge.
What Happens Next
Section titled “What Happens Next”-
On every merge to trunk:
- Framework detects which files changed
- Runs validation (if configured)
- Triggers relevant builds and deploys
- Updates state in
.github/manifest.yaml - Creates/updates a draft pre-release with the changelog
-
To promote to test:
- Actions -> Promote workflow
- Select
dev-to-test - Run
-
To promote to prod:
- Actions -> Promote workflow
- Select
test-to-prod(ordev-to-prodfor full cascade) - Run
The release is published, the publish callback fires, and a git tag is created.
Common Issues
Section titled “Common Issues”Workflow not triggering
Section titled “Workflow not triggering”- Branch name matches
trunk_branchin the manifest - Trigger patterns match the changed files
- Workflow file is in
.github/workflows/
Callback not found
Section titled “Callback not found”- Workflow path in the manifest matches the actual file path
- Workflow has
on: workflow_call - Required inputs/outputs are declared
Permission errors
Section titled “Permission errors”The generated workflows include the necessary permissions. If you wrap them in your own workflow, ensure:
permissions: contents: write actions: write # promote dispatches release buildsNext Steps
Section titled “Next Steps”- Configuration Reference for every field
- Callback Contract for callback inputs/outputs
- Workflows for generated workflow internals