Week 10 Demo 1 CICD
Week 10 Demo 1: GitHub Actions¶
Duration: 60 minutes
Format: Instructor live-codes; students follow along on their own repos
Prerequisite: Lecture 1 (CI/CD), project repo on GitHub with at least one test
Instructor Overview¶
This lab is structured as a guided build, not a lecture. You code, students code. Move at a pace where most of the room is with you — use check-ins frequently. The stretch goal (deploy step) is for fast finishers; don’t rush the main flow to reach it.
End state: Every student has a working GitHub Actions workflow that:
- Runs lint + tests on every push and pull request
- Blocks PR merges if CI fails
- (Stretch) SSHes into a VM to deploy
Setup Checklist (Before Lab Starts)¶
Students should have:
- A Node.js project repo on GitHub (can be their course project)
-
npm testrunning locally and passing - ESLint installed OR willing to install it during lab
- GitHub repo open in browser
- Code editor open
Instructor should have:
- A demo repo ready (separate from students’ repos)
- Terminal and editor visible on projector
- GitHub Actions tab open in browser
Part 1 — Warm-Up and Repo Audit (5 min)¶
Instructor Script¶
“Before we write a single line of YAML, let’s make sure our repos are ready. CI is only as useful as the tests and scripts it runs.”
Ask students to verify — do it yourself live:
# 1. Confirm npm test works
npm test
# 2. Confirm what scripts exist
cat package.json | grep '"scripts"' -A 10
What to look for in package.json:
{
"scripts": {
"test": "jest", // or mocha, vitest, etc.
"lint": "eslint .", // we'll add this if missing
"start": "node server.js"
}
}
If a student has no test script:
“A CI pipeline that has nothing to run is useless. The test script is the contract CI enforces.”
Part 2 — Add ESLint (8 min)¶
Why lint in CI?¶
“Lint is a fast, cheap check that catches real bugs and enforces style. It costs about 10 seconds in CI and catches things code review misses.”
Live code: Install and configure ESLint¶
Follow the prompts (Node.js, CommonJS or ESM, JSON config). Then:
Introduce a deliberate error:
Fix it, confirm it passes:
“This is what CI will run. If it passes here, it passes there.”
Commit and push:
Part 3 — Write the CI Workflow (20 min)¶
Create the workflow file¶
Build it step by step — type this live, explain each line¶
“This is the name shown in the GitHub Actions tab.”
“This runs on every push to main, and on every PR targeting main. These are the two moments when we most need confidence.”
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
“Notice
npm cinotnpm install.ciis stricter — it fails ifpackage-lock.jsonis out of sync. That’s what you want in CI.”
test:
runs-on: ubuntu-latest
needs: lint
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
“The
needs: lintline means tests only run if lint passes. No point running 2 minutes of tests if the code has lint errors. Fail fast.”
Full file for reference:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
test:
runs-on: ubuntu-latest
needs: lint
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
Commit and push:
Check it running¶
“Go to your repo on GitHub. Click the Actions tab. You should see the workflow running right now.”
Walk through the UI:
- Workflow run page
- Expanding individual jobs
- Expanding individual steps
- The green checkmark when it passes
Check-in: Raise your hand when you see green. (Wait for ~80% before moving on.)
Part 4 — Make CI Fail on Purpose (7 min)¶
Why this matters¶
“CI that you’ve never seen fail is CI you don’t trust. Let’s break it intentionally.”
Break the lint step¶
Go to GitHub → Actions. Watch the lint job fail. Notice:
testjob never starts (becauseneeds: lint)- Red X on the commit in the commit list
Fix it and push again:
“You just experienced the full CI feedback loop. This is exactly what your teammates will see when they push broken code.”
Part 5 — Branch Protection (10 min)¶
Turning CI from optional to required¶
“Right now CI runs, but nothing stops you from merging a PR with a failed CI. Let’s fix that.”
Live walkthrough in GitHub UI:
- Go to your repo → Settings → Branches
- Click Add branch protection rule
- Branch name pattern:
main - Check: ✅ Require status checks to pass before merging
- In the search box, type your job names:
lint,test - Check: ✅ Require branches to be up to date before merging
- Click Save changes
Demonstrate it working¶
Go to GitHub → create a Pull Request from this branch to main.
“Look at the PR. The merge button is greyed out with a message: ‘Some checks were not successful.’ The team is protected.”
Fix the test, push again, watch the PR unblock.
Part 6 — Stretch Goal: Deploy Step (10 min, if time permits)¶
“For those who are ahead: let’s add a deploy step that SSHes into your VM.”
Add a deploy job¶
deploy:
runs-on: ubuntu-latest
needs: [lint, test]
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to VM
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.VM_HOST }}
username: ${{ secrets.VM_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd ~/myapp
git pull origin main
npm ci --omit=dev
pm2 restart myapp || pm2 start server.js --name myapp
Add secrets to the repo¶
GitHub → Settings → Secrets and variables → Actions → New repository secret:
VM_HOST— your VM’s IP address or hostnameVM_USER— SSH username (e.g.,ubuntu,ec2-user)SSH_PRIVATE_KEY— contents of your~/.ssh/id_rsaprivate key
“Your private key goes in GitHub Secrets. It’s encrypted at rest and never shown in logs. GitHub replaces any accidental secret output with
***.”
Wrap-Up and Debrief (5 min)¶
What you built today¶
- ✅ ESLint configured and running locally
- ✅ GitHub Actions workflow with lint + test jobs
- ✅ Workflow runs automatically on push and PR
- ✅ Branch protection: PRs require green CI to merge
- ✅ (Stretch) Automated deploy to VM
Questions to prompt discussion¶
- “What would you add to this pipeline for your project?”
- “Where in your current workflow would this have caught a bug?”
- “What’s the cost of a slow CI pipeline? How would you speed this one up?”
Before Lab 2¶
- Install Docker Desktop: docs.docker.com/get-docker
- Run
docker run hello-worldto confirm it works - Think about what services your app needs (database? cache?)
Troubleshooting Guide¶
“My workflow never runs”¶
- Check the
on:trigger — does the branch name match exactly? (mainvsmaster) - Check the file is in
.github/workflows/(two levels deep) - Check the YAML is valid — use yaml.org/start.html or VS Code’s YAML extension
“npm ci fails but npm install works”¶
npm cirequires a committedpackage-lock.json. Runnpm installlocally, commit the lock file.
“Lint passes locally but fails in CI”¶
- CI uses a clean environment. Check that
.eslintrcis committed. - Check
node_modules/.bin/eslintis not being relied on in a way that differs fromnpm run lint.
“Test job runs even though lint failed”¶
- Make sure
needs: lintis indented correctly under thetestjob, not understeps.
“Secrets not available in the deploy step”¶
- Secrets are scoped to the repo where they’re added. Confirm under Settings → Secrets.
- Forks do not get access to the parent repo’s secrets (security feature).
“Branch protection isn’t blocking the merge”¶
- You need at least one workflow run to have completed before GitHub recognizes the check names. Push once, then set up protection.
Created : July 6, 2026