Week 10 Lecture 1 CICD
CI/CD with GitHub Actions¶
Automating the path from code to deployment¶
“If it’s not automated, it’s a liability.”
The Problem We’re Solving¶
Every team eventually hits this wall¶
You’ve been here (or you will be):
- “It works on my machine” but breaks on the server
- Forgetting to run tests before pushing
- Manual deploys that only one person knows how to do
- A bug slips to production because the review was rushed
- Merge conflicts that break everything and nobody knows why
CI/CD is the systematic answer to all of these.
What Is CI/CD?¶
Two ideas, often combined¶
| Term | Stands For | What It Means |
|---|---|---|
| CI | Continuous Integration | Automatically build and test every code change |
| CD | Continuous Delivery / Deployment | Automatically deliver tested code to an environment |
The key word is automatically.
CI/CD replaces “someone remembered to do it” with “the system always does it.”
The CI/CD Pipeline “assembly line”¶
Developer pushes code
│
▼
┌─────────────┐
│ Trigger │ ← something happened (push, PR, etc.)
└──────┬──────┘
│
▼
┌─────────────┐
│ Build │ ← install deps, compile, lint
└──────┬──────┘
│
▼
┌─────────────┐
│ Test │ ← unit tests, integration tests
└──────┬──────┘
│
▼
┌─────────────┐
│ Deploy │ ← push to server, cloud, container
└─────────────┘
Each stage is a gate. Failure stops the line.
Why CI/CD Changes Team Dynamics¶
It’s not just automation it’s trust¶
Without CI/CD:
- “Did you test this?” is a social question
- One person holds deployment knowledge
- Broken main branch = everyone is blocked
With CI/CD:
- Tests run on every push, automatically
- Any team member can deploy safely
- Broken builds are caught before they affect others
- Code review is about logic, not “did you remember to…”
CI/CD turns good intentions into enforced process.
GitHub Actions: The Basics¶
GitHub’s built-in CI/CD platform¶
GitHub Actions is:
- Free for public repos; generous free tier for private
- Deeply integrated with your repo (PRs, branches, releases)
- YAML-based your pipeline lives in your repo as code
- Extensible thousands of community “Actions” to reuse
Key vocabulary:
| Term | Meaning |
|---|---|
| Workflow | A YAML file that defines your automation |
| Trigger (on:) | What event starts the workflow |
| Job | A group of steps that run on one machine |
| Step | A single command or Action |
| Runner | The virtual machine that executes the job |
Anatomy of a Workflow File¶
Everything lives in .github/workflows/¶
name: CI # shown in GitHub UI
on: # TRIGGER
push:
branches: [main]
pull_request:
branches: [main]
jobs: # JOBS
test: # job name (you pick it)
runs-on: ubuntu-latest # runner OS
steps: # STEPS (run in order)
- uses: actions/checkout@v4 # checkout your code
- uses: actions/setup-node@v4 # install Node.js
with:
node-version: '20'
- run: npm ci # install deps
- run: npm test # run your tests
This file is version-controlled just like your code.
Triggers: When Does a Workflow Run?¶
# On push to specific branches
on:
push:
branches: [main, develop]
# On pull requests targeting main
on:
pull_request:
branches: [main]
# On a schedule (cron syntax)
on:
schedule:
- cron: '0 9 * * 1' # Every Monday at 9am UTC
# Manually triggered from GitHub UI
on:
workflow_dispatch:
# Multiple triggers together
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
Jobs can run in parallel or in sequence¶
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint
test:
runs-on: ubuntu-latest
needs: lint # ← waits for lint to pass first
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm test
deploy:
runs-on: ubuntu-latest
needs: [lint, test] # ← waits for BOTH
if: github.ref == 'refs/heads/main'
steps:
- run: echo "Deploy would happen here"
The uses vs run Distinction¶
run: executes a shell command directly
uses: runs a pre-built Action from the marketplace
- uses: actions/checkout@v4 # checks out your repo
- uses: actions/setup-node@v4 # configures Node.js
with:
node-version: '20'
- uses: actions/upload-artifact@v4 # saves files for later
with:
name: test-results
path: ./coverage
Actions are reusable building blocks. Think of them as npm packages for your pipeline.
Secrets and Environment Variables¶
Never hardcode credentials¶
Secrets stored encrypted in GitHub, injected at runtime:
# In GitHub: Settings → Secrets and variables → Actions
# Add: DEPLOY_KEY, DATABASE_URL, API_TOKEN, etc.
steps:
- run: ./deploy.sh
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
Environment variables for non-sensitive config:
env:
NODE_ENV: test
PORT: 3000
jobs:
test:
env:
DATABASE_URL: postgres://localhost/testdb # job-level
steps:
- run: npm test
env:
LOG_LEVEL: debug # step-level
Rule: if it’s a secret, it goes in Secrets. Never in your YAML file.
Pull Requests as Quality Gates¶
PRs + CI = enforced standards¶
Without CI on PRs:
With CI on PRs:
developer → pushes branch → CI runs automatically
→ tests pass? reviewer sees green checkmark
→ tests fail? PR is blocked until fixed
→ reviewer focuses on logic, not mechanics
You can require CI to pass before merging:
- Repo Settings → Branches → Branch protection rules
- ✅ Require status checks to pass before merging
- Select your workflow job name
This turns CI from “nice to have” into “enforced team standard.”
name: Node.js CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x] # test on multiple versions
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm' # cache node_modules
- run: npm ci
- run: npm run build --if-present
- run: npm test
Deployment from GitHub Actions¶
For your projects (deploying to a cloud VM), a typical pattern:
deploy:
runs-on: ubuntu-latest
needs: build-and-test
if: github.ref == 'refs/heads/main' # only deploy from main
steps:
- uses: actions/checkout@v4
- name: Deploy to VM via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.VM_HOST }}
username: ${{ secrets.VM_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/myapp
git pull origin main
npm ci --production
pm2 restart myapp
Your server pulls the latest code. GitHub Actions is the trigger.
Viewing Results in GitHub¶
Where to find CI/CD feedback¶
- Actions tab → all workflow runs, full logs per step
- Pull Request page → inline pass/fail status with links
- Commit list → green ✅ or red ❌ next to each commit
- Email notifications → if a workflow you triggered fails
Reading logs:
- Each step is collapsible
- Failed steps show the exact command and output
- Exit codes matter:
0= success, anything else = failure - Timestamps help diagnose slow steps
Common Mistakes and How to Avoid Them¶
Things that trip people up¶
| Mistake | Fix |
|---|---|
| Workflow never runs | Check the on: trigger matches your branch name exactly |
npm test exits with no tests |
Add a test script to package.json |
| Secrets not available | Confirm they’re added under repo Settings, not org-level |
| Deploy runs on every PR | Add if: github.ref == 'refs/heads/main' |
| Works locally, fails in CI | CI uses a clean env check you committed all config files |
| Slow workflows | Use cache: 'npm' in setup-node, split into parallel jobs |
CI/CD Best Practices¶
Rules to build good habits¶
- Keep workflows fast slow CI gets ignored or bypassed; aim for under 5 minutes
- Fail fast run cheap checks (lint) before expensive ones (integration tests)
- Pin Action versions use
@v4not@mainto avoid surprise breakage - One workflow per concern separate CI from CD for clarity
- Test your workflow changes in a branch don’t debug on main
- Make the green checkmark meaningful a CI that always passes teaches nothing
What’s Coming in the Lab¶
Demo 1 (60 min): Hands-on GitHub Actions¶
You will:
- Add a
ci.ymlworkflow to your project repo - Write a test and watch it run automatically on push
- Add a lint step using ESLint
- Set up branch protection so PRs require green CI
- Intentionally break a test observe the PR block in action
- (Stretch) Add a deploy step that SSHes into a VM
Come with: your project repo on GitHub, Node.js project with at least one test
Summary¶
What we covered¶
- CI = automatically build and test every change
- CD = automatically deliver passing code to an environment
- GitHub Actions = YAML workflows that live in your repo
- Triggers control when workflows run (push, PR, schedule)
- Jobs group steps; jobs can depend on each other
- Secrets keep credentials out of your code
- Branch protection turns CI from optional to enforced
Next up: Lecture 2 Docker for Collaborative Development
Before Lab 1: make sure your repo has npm test working locally.
Speaker Notes¶
(Pipeline Mental Model)¶
Draw this on the board first, then show the slide. Ask: “Where does this break down in your current workflow?” Get 2-3 answers before moving on.
(Jobs and Steps)¶
Emphasize needs: this is the dependency graph. A deploy job that doesn’t needs: test will deploy broken code. This is a real bug teams make.
(Pull Requests as Quality Gates)¶
This is the cultural shift, not just the technical one. CI enforces team norms. Ask: “Have you ever merged something that broke main? What would have stopped that?”
(Deployment)¶
Walk through the SSH pattern slowly. Students deploying to VMs will use exactly this. Point out that the server is pulling the VM needs to have the repo cloned already.
(Common Mistakes)¶
Leave 5 minutes here. These are the bugs they’ll hit in Lab 1. Inoculate them now.
Created : July 6, 2026