Skip to content

Adoption Guide

This guide ties the reference docs together into one path: how to think about cascade, how to build a pipeline from nothing, and how to migrate an existing pipeline (and existing tools) onto it. If you have never run cascade, start with Getting Started for installation, then come back here for the bigger picture.

cascade owns orchestration. It generates the GitHub Actions workflows that promote a commit through your environments, hold per-environment state, pin each promotion to a specific SHA, enforce a breaking-change gate at the release boundary, and provide hotfix, rollback, and cross-repo artifact tracking. It derives versions and changelogs from your commit history.

You own the verbs. Build, deploy, validate, and publish are your logic, supplied as reusable workflows that cascade calls with a fixed input contract. cascade never runs your scripts inline; it calls a workflow_call reusable workflow you point at. That is the central rule of adoption: every callback must be a reusable workflow. Inline run: and shell: callbacks were removed.

The flow in one line: you write a manifest plus callback workflows, run cascade generate-workflow, and commit the generated orchestration workflows into your repository. From then on, GitHub Actions runs them: cascade orchestrates on merge, promotes between environments, and releases at the terminal environment.

  • GitHub Actions enabled on the repository, with trunk-based development (a single primary branch).
  • Conventional Commits - required. cascade derives the semver bump, the changelog, and breaking-change detection entirely from Conventional Commit messages. This is not optional. Commits that do not follow the convention are not processed correctly and version derivation can fail. See Versioning and schema compatibility.
  • GitHub setup: environments for each deploy stage, branch and tag protection, the secrets your callbacks consume, and scoped tokens. The Security and Hardening checklist is the authoritative list; wire it up before your first production promotion.
  • The cascade CLI for local generation (Go 1.23+ to go install; in Actions, the setup action installs it for you). See Getting Started.

Fast path: cascade init does the first three steps below for you. It scaffolds the manifest and the callback stubs, verifies them through the real generator, and writes them into your repository:

Terminal window
cascade init --topology two-env # dev, prod
cascade init --envs staging,production # your own ordered names

Pick a preset with --topology (no-env, two-env, three-env, four-env) or supply your own ordered list with --envs. Then jump to step 4 to generate and commit. The walkthrough below explains each piece init produces, so you understand what you are filling in. See the CLI Reference for every flag.

Environments are positional, not named by meaning. cascade attaches no semantics to a name like prod; it reads the list by position:

  • The last environment is the release stage (typically prod).
  • The second-to-last is the prerelease environment.
  • The crossing into the last environment is the publish boundary, where the breaking-change gate runs and the publish callback fires.

So environments: [dev, test, prod] means dev is first, test is the prerelease stage, and prod is the release stage. The names are yours to choose; only the order carries meaning. See the Manifest Reference for the full structural rules, including zero-environment (release-only) mode.

A three-environment manifest with one build, one deploy, optional validation, and an optional publish callback:

ci:
config:
trunk_branch: master
environments: [dev, test, prod]
cli_version: v2.0.4
validate:
workflow: .github/workflows/validate.yaml
builds:
- name: app
workflow: .github/workflows/build-app.yaml
triggers: ["src/**", "Dockerfile", "go.mod"]
deploys:
- name: services
workflow: .github/workflows/deploy-services.yaml
depends_on: [app] # receives the build's outputs as inputs
publish:
workflow: .github/workflows/publish.yaml
state:
dev: {}
test: {}
prod: {}

cascade manages state: and latest_release:; the empty skeleton is enough. See the Manifest Reference for every field.

For autocomplete and inline validation while you edit the manifest, register the JSON Schema with your editor. See Editor support.

Each callback is a reusable workflow with an on: workflow_call trigger. cascade passes a fixed set of inputs and reads back any outputs: you declare. The exact, full YAML for each lives in the Callback Contract; the contract below is the summary.

Callbackcascade passes (inputs)You return (outputs)
Validateenvironment, sha, dry_runnone required
Buildenvironment, sha, dry_run, plus custom inputsartifact_id (recommended), plus custom
Deployenvironment, sha, dry_run, plus the build’s declared outputs (for example image_tag, artifact_id)custom (optional)
Changelog (custom)changelog_base_sha, head_sha, repochangelog
Publishbuild_name, old_version, new_version, sha, artifact_idnone required

Two mechanics to internalize:

  • Output chaining. cascade parses your build workflow for declared on.workflow_call.outputs. When a deploy declares depends_on: [app], every output the app build declares (say image_tag) is forwarded to the deploy as an input of the same name automatically. Declare outputs explicitly or they will not chain.
  • Dry run. Every callback receives dry_run and should guard mutating steps with if: ${{ !inputs.dry_run }}.

A minimal deploy callback skeleton, receiving image_tag from its build dependency:

name: Deploy Services
on:
workflow_call:
inputs:
environment: { type: string, required: true }
sha: { type: string, required: true }
dry_run: { type: boolean, required: false, default: false }
image_tag: { type: string, required: true } # from the app build's outputs
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }} # protection gate lives here, not on the caller
steps:
- uses: actions/checkout@v4
with: { ref: ${{ inputs.sha }} }
- if: ${{ !inputs.dry_run }}
run: ./deploy.sh "${{ inputs.image_tag }}"

The environment: key must sit on the job inside your reusable workflow. GitHub Actions rejects environment: on a job that calls a reusable workflow with uses:, so the caller cascade generates cannot carry the gate; your workflow applies it. Full build, deploy, validate, and publish skeletons are in the Callback Contract.

Terminal window
cascade generate-workflow --config .github/manifest.yaml

Commit the generated orchestration workflows (orchestrate, promote, release) alongside your callbacks. Review the generated YAML before adopting it, as the hardening checklist advises.

  • Orchestrate on merge. A merge to trunk triggers the orchestrate workflow, which runs validate and build for the first environment and records state.
  • Promote between environments. A promote dispatch advances the recorded SHA to the next environment, re-running the relevant callbacks against it. Promotion is SHA-pinned, so what you tested is what advances.
  • Release at the terminal environment. Crossing into the last environment publishes the final semver release (from the RC), runs the breaking-change gate, and fires the publish callback to retag artifacts.

Two capabilities sit alongside the main flow. Hotfix lets you patch a released version through an integration branch without dragging unreleased trunk changes along. Rollback re-promotes a prior environment snapshot from recorded state. See Architecture for how state and promotion underpin both.

Map your current pieces onto cascade’s split of responsibility. The recurring question is: does cascade take this over, or does it stay yours behind a callback?

You have todayIn cascadeWhat changes
A deploy script or jobA deploy reusable-workflow callbackMove the script into a workflow_call workflow; cascade calls it with environment, sha, dry_run, plus build outputs.
A build/package stepA build callbackSame move; declare artifact_id (and any tags) as outputs so they chain to deploys and to publish.
Hand-rolled env-promotion logic (scripts gating dev to staging to prod)cascade’s promotion cascadecascade owns this. Delete your promotion glue; cascade orchestrates, pins SHAs, and gates the release boundary.
Manual or tool-driven version bumpingConventional-commit-driven version derivationcascade owns it and it is required. Your bump logic goes away; commit messages drive the semver.
A changelog tool (release-please, git-cliff)A changelog callback, or keep the tool and disable cascade’s changelogTwo valid paths (see below).
A release tool (goreleaser)An external release wired via release.tag, or disable cascade’s release and keep the toolTwo valid paths (see below).

The clean line: cascade takes over orchestration, promotion, state, versioning, and the release boundary. It does not take over how you build, deploy, validate, or (optionally) cut changelogs and release artifacts. Those stay yours, expressed as callbacks.

Two options, both supported by the changelog: section of the Manifest Reference:

  • Custom changelog callback. Point changelog.workflow at a reusable workflow that wraps your tool. cascade passes changelog_base_sha, head_sha, and repo; your workflow must return a changelog output. cascade uses that text when it cuts the release.
  • Disable and keep yours as-is. Set changelog.disabled: true and let your existing release-please workflow run independently on its own trigger. cascade stops generating a changelog; everything else (promotion, release) still works.

cascade has no separate “release callback” that receives build_name/old_version/new_version. Releasing is either cascade’s own job or your external tool. Two options via the release: section:

  • External release tool. Keep your goreleaser callback as a normal build/deploy callback that emits a tag output, and set release.tag: goreleaser.tag (the callback.output reference). cascade defers the tag to your tool’s output.
  • Disable and keep goreleaser standalone. Set release.disabled: true to turn off cascade’s release management and run goreleaser on your own trigger.

Omitting the release: section entirely uses cascade’s defaults: it creates releases with conventional-commit changelogs.

cascade’s version derivation, breaking-change detection, and default changelog are conventional-commit-only by design. If your history contains commits that do not follow the convention, version derivation can fail and breaking changes will be missed. Adopt the convention before (or as part of) migrating. Details in Versioning and schema compatibility.

Before a production promotion, confirm at minimum:

  • Branch protection plus CODEOWNERS on .github/workflows/** so generated and callback workflows require review.
  • Environment protection rules (required reviewers, branch/tag policy) on each deploy environment, declared inside the reusable deploy workflow.
  • Scoped tokens for cross-repo dispatch and release API calls; prefer a GitHub App or short-lived token over a broad PAT.
  • Audit and integrity: pinned actions, immutable registry tags, OIDC with a tight trust policy.

The full, ordered checklist is in Security and Hardening. Work through it there; the points above are the highlights, not the whole list.

Environment count is structural. Pick the shape that matches your project:

  • No-env (release-only) - omit environments. For libraries and CLIs that publish releases without deploying anywhere.
  • 2-env - dev + prod. Smallest promotion chain with a prerelease stage.
  • 3-env - dev + staging + prod (or pre + staging + prod). The common default.
  • 4-env - dev + staging + pre + prod. Adds a dedicated prerelease stage before production.

Worked example repositories are not published yet (examples TBD). Until then, the Getting Started walkthrough and the Callback Contract skeletons are the reference implementations.