100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
CI/CD with GitHub Actions
45 minintermediate

Foundations Practice — First Workflow

What You'll Build

You will create the first GitHub Actions workflow for CricketPulse — a live cricket scores and statistics API. The workflow will be triggered on push to main and on all pull requests, run two jobs in parallel (a build-info job that echoes pipeline context and a test job that runs a trivial validation script), and produce visible output in the GitHub Actions UI. This exercise is deliberately simple — the focus is entirely on workflow structure, YAML syntax, trigger configuration, and reading the Actions interface. By the end, you will be able to read a workflow run log, understand the job timeline, and know where status checks appear on pull requests. These navigation skills are the foundation for every subsequent debugging exercise in the course.

Analogy🏏Cricket
🏏 Think of it like cricket: This exercise is building the complete match management infrastructure: the pitch is prepared (dev), the warm-up match is played (staging approval), and only after the selectors sign off does the Test match begin (production). You'll experience being both the deployer (pushing the commit that initiates the pipeline) and the approver (clicking the approval button in GitHub's reviewer interface) — understanding both sides of the gate is essential for designing gates that are genuinely useful rather than bureaucratic obstacles.

Prerequisites

  • A GitHub account with the ability to create public repositories — the Actions free tier provides 2,000 minutes per month for public repos and 500 MB storage.
  • Git installed locally and basic Git knowledge: `git clone`, `git add`, `git commit`, `git push`.
  • A text editor with YAML support (VS Code with the GitHub Actions extension is recommended — it provides schema validation and autocomplete for workflow files).
  • Familiarity with GitHub Actions concepts from Lessons 1–3: workflows, jobs, steps, triggers, and the `github` context.
  • A GitHub repository to work in — either create a new one named `cricketpulse` or fork the course starter repository.

Setup & Project Structure

Start by creating the repository structure and the required directory for GitHub Actions workflows. The `.github/workflows/` directory is a GitHub convention — Actions only recognises workflow files in this exact path. You will also create a minimal Node.js project with a `package.json` and a simple test script so the workflow has something meaningful to execute. The entire setup takes under two minutes and produces a repository structure that mirrors what you will use throughout the rest of the course.

Analogy🏏Cricket
🏏 Think of it like cricket: configuring the three environments and their protection rules in the GitHub UI before writing any workflow is like agreeing the match's playing conditions with the officials before the toss — not mid-innings. Just as environment protection rules live entirely in settings and there is no YAML for them, the tournament's rules on reviews, powerplays, and appeals are fixed by the governing body off the field, not written into a batsman's technique. Just as the workflow simply names an environment and GitHub enforces whatever rules are configured, a player simply takes the field and the umpires enforce the pre-agreed conditions — the player doesn't carry the rulebook, the officials do. The payoff: settle the playing conditions once, in the right place, and every delivery afterwards is automatically governed by them without cluttering the play itself.
bash
# Terminal commands — run these to set up the project

# Create and enter the project directory
mkdir cricketpulse && cd cricketpulse
git init
git remote add origin https://github.com/YOUR_USERNAME/cricketpulse.git

# Create the workflows directory
mkdir -p .github/workflows

# Create a minimal package.json
cat > package.json << 'EOF'
{
  "name": "cricketpulse",
  "version": "1.0.0",
  "description": "Live cricket scores and statistics API",
  "scripts": {
    "test": "node scripts/validate.js",
    "build": "echo 'CricketPulse build complete'"
  }
}
EOF

# Create a scripts directory and a simple validation script
mkdir scripts
cat > scripts/validate.js << 'EOF'
// Trivial validation — confirms Node.js is running and exits cleanly
const data = {
  matchId: 'IPL-2024-CSK-MI-042',
  venue: 'Wankhede Stadium, Mumbai',
  battingTeam: 'Mumbai Indians',
  score: { runs: 187, wickets: 4, overs: 20 },
  topScorer: { name: 'Rohit Sharma', runs: 68, balls: 42 }
};

console.log('CricketPulse data validation starting...');
console.log(`Match: ${data.matchId}`);
console.log(`Score: ${data.score.runs}/${data.score.wickets} in ${data.score.overs} overs`);
console.log(`Top scorer: ${data.topScorer.name} — ${data.topScorer.runs}(${data.topScorer.balls})`);

// Validate required fields
const required = ['matchId', 'venue', 'battingTeam', 'score', 'topScorer'];
required.forEach(field => {
  if (!data[field]) throw new Error(`Missing required field: ${field}`);
});

console.log('\n✅ All validations passed. CricketPulse data structure is correct.');
process.exit(0);
EOF

echo "Project structure created successfully"

Step 1 — Foundation

Create the workflow file. This step introduces the core YAML structure: the `name:` field for the workflow display name, the `on:` trigger block with both `push` and `pull_request` triggers, and the `jobs:` block. The first job — `pipeline-info` — will run a set of `echo` commands that read from the `github` context, producing visible output in the Actions log that demonstrates how context values are resolved at runtime. This job has no external dependencies: no `npm install`, no test frameworks. It simply proves that the runner started, the workflow YAML parsed correctly, and context values are accessible.

Analogy🏏Cricket
🏏 Think of it like cricket: The warm-up session runs on the hotel practice ground with no spectators, no formal protocols — players take the field and start immediately. The dev environment is the hotel practice ground: automated, immediate, no gatekeeping. Confirming the dev pipeline runs automatically before adding gated environments is like confirming the practice session proceeds smoothly before worrying about the match-day protocols.
yaml
# File: .github/workflows/cricketpulse-ci.yml
# Step 1: Workflow skeleton with pipeline-info job

name: CricketPulse CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  pipeline-info:
    name: Pipeline Context Information
    runs-on: ubuntu-latest
    steps:
      - name: Print workflow context
        run: |
          echo "=== CricketPulse Pipeline Context ==="
          echo "Workflow:     ${{ github.workflow }}"
          echo "Run number:   ${{ github.run_number }}"
          echo "Triggered by: ${{ github.event_name }}"
          echo "Actor:        ${{ github.actor }}"
          echo "Branch:       ${{ github.ref_name }}"
          echo "Commit SHA:   ${{ github.sha }}"
          echo "Repository:   ${{ github.repository }}"
          echo "Runner OS:    ${{ runner.os }}"
          echo "======================================"

Step 2 — Core Logic

Add the test job that runs the validation script. This job runs in parallel with `pipeline-info` — both start immediately when the workflow triggers because neither declares a `needs:` dependency. The test job uses the `actions/checkout` and `actions/setup-node` marketplace actions to prepare the environment before executing the validation script. This step demonstrates how reusable actions eliminate boilerplate and how to pass configuration to them via the `with:` block.

Analogy🏏Cricket
🏏 Think of it like cricket: The warm-up session is complete (dev deployed). Now the match-day protocols begin: the umpires inspect the pitch, the team captains arrive for the toss, the grounds team prepare the outfield. The staging approval is the toss ceremony — a formal, structured pause before the match begins, requiring the participating parties to confirm readiness before play commences.
yaml
# File: .github/workflows/cricketpulse-ci.yml
# Step 2: Add test job running in parallel with pipeline-info

name: CricketPulse CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  pipeline-info:
    name: Pipeline Context Information
    runs-on: ubuntu-latest
    steps:
      - name: Print workflow context
        run: |
          echo "=== CricketPulse Pipeline Context ==="
          echo "Workflow:     ${{ github.workflow }}"
          echo "Run number:   ${{ github.run_number }}"
          echo "Triggered by: ${{ github.event_name }}"
          echo "Actor:        ${{ github.actor }}"
          echo "Branch:       ${{ github.ref_name }}"
          echo "Commit SHA:   ${{ github.sha }}"
          echo "Repository:   ${{ github.repository }}"
          echo "Runner OS:    ${{ runner.os }}"
          echo "======================================"

  test:
    name: Validate CricketPulse Data
    runs-on: ubuntu-latest
    # No 'needs:' — runs in parallel with pipeline-info
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js 20
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Run CricketPulse validation script
        run: node scripts/validate.js

      - name: Confirm successful validation
        run: |
          echo "✅ CricketPulse validation passed on commit ${{ github.sha }}"
          echo "   Branch: ${{ github.ref_name }}"
          echo "   Run:    #${{ github.run_number }}"

Step 3 — Integration & Enhancement

Add a third job — `summary` — that depends on both previous jobs and writes a structured summary to the GitHub Actions job summary. Job summaries are a GitHub Actions feature that allows workflows to post formatted Markdown visible in the workflow run summary page. This step demonstrates `needs:` for sequential execution, the `always()` status check function for running even when upstream jobs fail, and writing to `$GITHUB_STEP_SUMMARY` — a useful pattern for surfacing important information without burying it in verbose log output.

Analogy🏏Cricket
🏏 Think of it like cricket: After the warm-up (dev) and the pitch inspection ceremony (staging), the Test match finally begins with all the formal protocols: national anthems, player presentations, the coin toss, a final equipment check. Each is a structured delay ensuring everything is ready before the ball is bowled. The 2-minute production wait timer is the Test match's opening ceremony — a deliberate pause giving everyone a final opportunity to abort if something has gone wrong.
yaml
# File: .github/workflows/cricketpulse-ci.yml
# Step 3: Final complete workflow with summary job

name: CricketPulse CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  pipeline-info:
    name: Pipeline Context Information
    runs-on: ubuntu-latest
    steps:
      - name: Print workflow context
        run: |
          echo "Workflow: ${{ github.workflow }} | Run: #${{ github.run_number }}"
          echo "Triggered by: ${{ github.event_name }} on ${{ github.ref_name }}"
          echo "Actor: ${{ github.actor }} | SHA: ${{ github.sha }}"

  test:
    name: Validate CricketPulse Data
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
      - name: Setup Node.js 20
        uses: actions/setup-node@v4
        with: { node-version: '20' }
      - name: Run CricketPulse validation script
        run: node scripts/validate.js

  summary:
    name: Workflow Summary
    runs-on: ubuntu-latest
    needs: [pipeline-info, test]   # Runs after BOTH jobs complete
    if: always()                    # Runs even if pipeline-info or test failed
    steps:
      - name: Write job summary
        run: |
          cat >> $GITHUB_STEP_SUMMARY << 'SUMMARY'
          ## 🏏 CricketPulse CI Summary

          | Property | Value |
          |----------|-------|
          | **Workflow** | ${{ github.workflow }} |
          | **Run number** | #${{ github.run_number }} |
          | **Trigger** | ${{ github.event_name }} |
          | **Branch** | ${{ github.ref_name }} |
          | **Actor** | ${{ github.actor }} |
          | **Commit** | `${{ github.sha }}` |

          ### Job Results
          - Pipeline Info: ${{ needs.pipeline-info.result }}
          - Validation Tests: ${{ needs.test.result }}
          SUMMARY

Step 4 — Testing & Verification

Push the completed workflow to GitHub and verify it triggers correctly. Then introduce a deliberate failure to observe how GitHub Actions surfaces the error in the Actions UI and on the pull request check. This failure-observation step is critical — knowing how failures look in the interface is as important as knowing how successes look, because you will spend significant time debugging pipelines in your career.

Analogy🏏Cricket
🏏 Think of it like cricket: running the three scenarios — automatic push, manual dispatch with production, and a rejected staging deploy — is like rehearsing how a match unfolds under different calls before the real fixture. Just as an automatic push flows through dev and staging but stops short of production, an ordinary passage of play proceeds freely up to the boundary rope but can't cross into a scoring appeal without review. Just as manual dispatch with production enabled runs the full three-stage pipeline, a captain formally opting to declare unlocks the complete sequence of events that follows. And just as a deliberately rejected staging deployment halts the pipeline, a third umpire ruling not-out on review sends the batsman back and the passage of play simply stops there. Testing all three teaches you how each gate behaves. The payoff: like rehearsing every umpiring outcome in advance, you learn exactly how approvals let a deploy through, how manual triggers open the full path, and how a rejection cleanly stops the release.
bash
# Terminal: push the workflow and observe it run

# Commit and push the workflow file
git add .github/workflows/cricketpulse-ci.yml scripts/validate.js package.json
git commit -m "feat: add initial CricketPulse CI workflow"
git push origin main

# GitHub Actions will trigger immediately — navigate to:
# https://github.com/YOUR_USERNAME/cricketpulse/actions
# Expected: Three jobs visible in the workflow run:
#   ✅ Pipeline Context Information
#   ✅ Validate CricketPulse Data
#   ✅ Workflow Summary

# --- DELIBERATE FAILURE TEST ---
# Edit scripts/validate.js: add this line near the end:
#   throw new Error('Simulated pipeline failure for Lesson 4 exercise');

git add scripts/validate.js
git commit -m "test: simulate pipeline failure for learning exercise"
git push origin main

# Expected output in Actions:
#   ✅ Pipeline Context Information  (unaffected — runs in parallel)
#   ❌ Validate CricketPulse Data    (fails with error message visible in log)
#   ⚠️  Workflow Summary              (runs due to 'if: always()', shows failed status)

# Navigate to the failed job in the Actions UI:
# 1. Click the red ❌ on 'Validate CricketPulse Data'
# 2. Expand the 'Run CricketPulse validation script' step
# 3. Observe the error message and exit code
# 4. Note the red X on the commit in the repository's commit list

# Restore the passing state:
# Remove the throw line and push again
git add scripts/validate.js
git commit -m "fix: remove simulated failure"
git push origin main

Warning: If your workflow shows 'No workflow runs' in the Actions tab after pushing, check two things. First, verify the file is in `.github/workflows/` (the dot prefix is easy to miss). Second, confirm the branch name matches your `on: push: branches:` list — if your repository default branch is `master` and you listed `main`, the trigger will never fire. Run `git branch` to check your current branch name and update the workflow accordingly.

Extension Challenge: Add a fourth job to the workflow that only runs when the trigger is a `pull_request` event. This job should post the PR number, the head branch name, and the target branch to the step log using `github.event.pull_request.number`, `github.head_ref`, and `github.base_ref`. Create a branch, push a commit, and open a pull request to observe this job appear exclusively on PR-triggered runs.

  • GitHub Actions workflow files must be in `.github/workflows/` with a `.yml` or `.yaml` extension — any other path is ignored by GitHub.
  • Jobs with no `needs:` declaration run in parallel immediately; the `summary` job's `needs: [pipeline-info, test]` ensures it only starts after both predecessors complete.
  • The `if: always()` status function overrides default failure-halt behaviour, allowing jobs like `summary` to run even when upstream jobs fail — essential for post-failure reporting and cleanup.
  • Context values like `${{ github.sha }}` and `${{ github.actor }}` are resolved server-side before the runner executes, making them safe and consistent across all steps.
  • Writing to `$GITHUB_STEP_SUMMARY` produces formatted Markdown visible in the workflow run's summary page — a cleaner alternative to burying important information in verbose step logs.
  • Observing how a pipeline failure looks in the Actions UI — red job badges, error step expansion, commit status indicators — is as important as understanding successful runs.
Lesson 4 of 24
0% complete