Skip to main content

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 apply in pull_request so PRs get their own infra." No -- PRs run terraform plan (read-only on state). Only pushes to main apply. Apply-on-PR is how repositories full of unmerged experiments accumulate orphan infrastructure.
  • "Cache everything to go fast." Caching node_modules or .terraform/plugins is 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-test and deploy so a test-fail does not leave the workflow in an unclear state and so deploy can be re-run against a green build-test without 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: with cancel-in-progress: false protects you.

How To Use It (In Your Capstone)

  1. Start with exactly one workflow file. Name it deploy.yml.
  2. Make the build-test job idempotent and cacheable. This is the job that will run 200 times this semester.
  3. Gate deploy on needs: build-test and on branch or event.
  4. Use the environment: key to get approval gates, protected secrets, and the "Environments" dashboard for free.
  5. Add a concurrency: block scoped to the ref; pick cancel-in-progress: false for deploy jobs.
  6. Keep the file under 150 lines. Every added job must justify itself.
  7. Validate YAML with gh workflow view deploy or VS Code's YAML extension before committing; rejected YAML at push is a self-inflicted 5-minute loss.

See also (integrative)

Check Yourself

  1. What is the exact event that triggers a prod deploy?
  2. What is the single line that prevents a PR from deploying to prod?
  3. How long does the full workflow take today, and which step dominates?
  4. What does concurrency: protect against in your workflow?
  5. If a deploy fails halfway, what is the state of prod and what is the state of the workflow?
  6. Where is the build artifact produced in build-test, and how does deploy receive it?

Mini Drill or Application (Capstone-scoped)

  1. Minimum viable deploy.yml (30 min). Commit a file that at least runs build-test successfully on every push. Worry about the deploy job after OIDC is set up (concept 08).
  2. 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).
  3. Red-on-purpose test. Push a branch with a deliberately broken test. Verify the workflow fails at build-test and does not reach deploy. 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.