Time to write code that runs code. In this lesson you'll create a real workflow, push it, and watch GitHub execute it — the first tangible proof that your pipeline works.
GitHub Actions: Where Pipelines Live
In GitHub Actions, pipelines are YAML files stored in a special directory in your repo:
.yml file into .github/workflows/, GitHub automatically recognises it as a workflow. No setup, no configuration, no "enable CI" button.
The Simplest Possible Workflow
Here's the absolute minimum — a workflow that runs one command:
name: Hello CI
# WHEN to run (the trigger)
on:
push:
branches: [main]
# WHAT to do
jobs:
greet:
runs-on: ubuntu-latest
steps:
- name: Say hello
run: echo "Hello from CI! 🎉"
Let's map every line to the mental model from Lesson 01:
🏋️ Exercise: Build It, Push It, Watch It Run
Goal: Create a workflow that runs tests on every push. Follow these steps exactly:
Step 1: Create the application
cd cicd-mastery # your repo from Lesson 01 prep
# Create a simple app
mkdir -p src tests
cat > src/math.js << 'EOF'
function add(a, b) { return a + b; }
function multiply(a, b) { return a * b; }
module.exports = { add, multiply };
EOF
cat > tests/math.test.js << 'EOF'
const { add, multiply } = require('../src/math');
test('add: 2 + 3 = 5', () => {
expect(add(2, 3)).toBe(5);
});
test('multiply: 4 × 5 = 20', () => {
expect(multiply(4, 5)).toBe(20);
});
EOF
# Initialize Node project
npm init -y
npm install --save-dev jest
# Add test script to package.json
npx json -I -f package.json -e 'this.scripts.test="jest"'
Step 2: Verify locally
npm test
# Should show: 2 tests passed ✓
Step 3: Create the workflow
mkdir -p .github/workflows
cat > .github/workflows/ci.yml << 'EOF'
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout code
uses: actions/checkout@v4
- name: 📦 Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: 📚 Install dependencies
run: npm ci
- name: 🧪 Run tests
run: npm test
EOF
Step 4: Push and observe
git add .
git commit -m "feat: add CI pipeline"
git push origin main
Step 5: Watch it run
- Go to your repo on GitHub
- Click the "Actions" tab
- Click the running workflow
- Click the "test" job
- Expand each step and read the logs
🎉 You just ran your first CI pipeline.
What Just Happened (Sequence Diagram)
Understanding uses: vs run:
Steps come in two flavours:
uses: — Run a pre-built Action
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
Like calling a library function. Someone else wrote the logic; you pass parameters via with:.
run: — Execute a shell command
- run: npm ci
- run: npm test
- run: |
echo "Multi-line"
echo "commands"
Direct shell access. Runs in bash by default. Use | for multi-line.
The actions/checkout Mystery
New users often miss this: the runner starts with an EMPTY filesystem. Your code isn't there. actions/checkout is what clones your repo onto the runner.
actions/checkout, every run: step that references your code will fail with "file not found." This is the #1 beginner mistake.
🧠 Recall Check
- Where must workflow YAML files live in your repository?
- What does
runs-on: ubuntu-latestspecify? - Why is
actions/checkout@v4almost always the first step? - What's the difference between
uses:andrun:?
Reveal answers
.github/workflows/— GitHub auto-discovers any.ymlfile here.- The runner — a fresh Ubuntu VM that executes the job.
- Because the runner starts empty — no code. Checkout clones your repo onto it.
uses:runs a pre-built action (reusable logic).run:executes a shell command directly.
What You Built
main or open a PR, GitHub will automatically run your tests and report the result. You'll never merge broken code without knowing.
Next lesson: Triggers & Events — controlling exactly when your pipeline runs (and when it doesn't).