GitHub Actions Workflow for the Capstone
What This Concept Is
One workflow file, three jobs, real YAML. The capstone pipeline should:
- run on every push and every pull request to
main - run build, lint, and tests
- deploy automatically to the environment mapped to the trigger (preview or staging on PR, prod on
main) - run a smoke test against the deployed environment and fail the run if it fails
If any one of those is missing or aspirational, your capstone does not have a pipeline -- it has a file that occasionally runs tests.
The file lives at .github/workflows/deploy.yml. GitLab CI, Buildkite, and CircleCI equivalents are acceptable substitutes; this concept uses GitHub Actions because the capstone default repo is on GitHub.
Why It Matters Here (In the Capstone)
Every capstone concept downstream assumes a pipeline exists. Rollback runbooks refer to "re-running the previous deploy." Smoke tests run in "the post-deploy step." Release notes are generated from "the commits since last deploy." Preview environments are "stood up by the workflow on PR open." None of those phrases have a referent without a real pipeline.
A pipeline is also evidence of discipline in a way a README claim is not. When a reviewer opens the Actions tab and sees 40 green runs over 3 months, with occasional red runs that were fixed (not ignored, not disabled), they know you have been shipping. When they see three runs total, the last 6 weeks ago, they know something else.
Concrete Example(s)
A minimal, self-contained deploy.yml for a Node + Terraform capstone on Cloud Run:
name: deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
id-token: write # required for OIDC to cloud
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false # do not cancel a running deploy
jobs:
build-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
- run: npm ci
- run: npm run lint
- run: npm test -- --ci
- run: npm run build
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: build
path: dist/
deploy:
needs: build-test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: prod
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with: { name: build, path: dist/ }
- uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ secrets.GCP_WIF_PROVIDER }}
service_account: ${{ secrets.GCP_DEPLOY_SA }}
- uses: hashicorp/setup-terraform@v3
- run: terraform init
working-directory: terraform
- run: terraform apply -auto-approve -var image=$IMAGE
working-directory: terraform
env:
IMAGE: gcr.io/${{ vars.GCP_PROJECT }}/api:${{ github.sha }}
- name: Smoke test
run: ./scripts/smoke.sh ${{ vars.PROD_URL }}
This YAML is the whole capstone pipeline. The GitLab CI shape is similar, for reference:
# .gitlab-ci.yml
stages: [build, deploy]
build-test:
stage: build
script: [npm ci, npm test, npm run build]
deploy-prod:
stage: deploy
only: [main]
environment: prod
id_tokens:
GCP_ID_TOKEN: { aud: https://iam.googleapis.com/... }
script: [terraform apply -auto-approve, ./scripts/smoke.sh]
Both run on every push, gate deploy on tests, use OIDC for cloud access, and fail the run if smoke fails.
Common Confusion / Misconceptions
- "My workflow has 14 jobs and 9 reusable composite actions." For a solo capstone, that is theater. Reviewers can read a 40-line YAML and evaluate it. They will not read a 400-line YAML and trust it more. Reusable actions are for teams shipping many pipelines; one pipeline does not need them.
- "I'll put
terraform applyinpull_requestso PRs get their own infra." No -- PRs runterraform plan(read-only on state). Only pushes tomainapply. Apply-on-PR is how repositories full of unmerged experiments accumulate orphan infrastructure. - "Cache everything to go fast." Caching
node_modulesor.terraform/pluginsis fine. Caching the build output across branches is a source of "works on main, broken on PR" bugs. Cache by cache key that includes lockfile hash, not by branch alone. - "One big job is simpler than split jobs." Split
build-testanddeployso a test-fail does not leave the workflow in an unclear state and sodeploycan be re-run against a greenbuild-testwithout re-running tests. - "Concurrency doesn't matter for solo." It does -- if you push twice in a row, two deploy jobs can race against the same cloud, and the loser can roll back the winner.
concurrency:withcancel-in-progress: falseprotects you.
How To Use It (In Your Capstone)
- Start with exactly one workflow file. Name it
deploy.yml. - Make the
build-testjob idempotent and cacheable. This is the job that will run 200 times this semester. - Gate
deployonneeds: build-testand on branch or event. - Use the
environment:key to get approval gates, protected secrets, and the "Environments" dashboard for free. - Add a
concurrency:block scoped to the ref; pickcancel-in-progress: falsefor deploy jobs. - Keep the file under 150 lines. Every added job must justify itself.
- Validate YAML with
gh workflow view deployor VS Code's YAML extension before committing; rejected YAML at push is a self-inflicted 5-minute loss.
See also (integrative)
- S9 M04 Cluster 2: Pipeline-as-code -- why the pipeline lives in the repo, not a UI
- S9 M04 Cluster 2: Build once, promote everywhere -- the image built in
build-testis the same artifact deployed to prod - S9 M04 Cluster 1: Small, frequent, reversible changes -- the pipeline exists to make small changes cheap
- S9 M04 Cluster 5: Pipeline security -- secrets, OIDC, least privilege -- pairs with concept 08 in this module
- S8 M05 Cluster 4: Writing an executive summary -- the Actions tab and run titles are an executive summary your reviewer sees
- GitHub Actions documentation -- authoritative reference for triggers, permissions, runners, artifacts
- GitHub Actions: Using workflows -- the whole-workflow mental model
- GitHub Actions: Using environments for deployment -- the
environment:key and its protection rules - GitLab CI/CD documentation -- equivalent model for the GitLab capstone
Check Yourself
- What is the exact event that triggers a
proddeploy? - What is the single line that prevents a PR from deploying to prod?
- How long does the full workflow take today, and which step dominates?
- What does
concurrency:protect against in your workflow? - If a deploy fails halfway, what is the state of prod and what is the state of the workflow?
- Where is the build artifact produced in
build-test, and how doesdeployreceive it?
Mini Drill or Application (Capstone-scoped)
- Minimum viable
deploy.yml(30 min). Commit a file that at least runsbuild-testsuccessfully on every push. Worry about the deploy job after OIDC is set up (concept 08). - Time budget. Measure total workflow time on a typical PR. If it is over 8 minutes, identify the dominant step and file an issue with a fix (cache, split, or parallelize).
- Red-on-purpose test. Push a branch with a deliberately broken test. Verify the workflow fails at
build-testand does not reachdeploy. This is the most important invariant; confirm it once, early.
Source Backbone
Capstone deployment applies cloud, delivery, and operations material. These books are the source backbone for the delivery decisions.
- Building Secure and Reliable Systems - secure/reliable deployment posture.
- GitHub Actions in Action - workflow automation support.
- Pro Git - release history, tags, and branch discipline.
- The Linux Command Line - shell and deployment automation support.