DevOpsGitHub

GitHub Mastery: The Final Project and Assessment

TT
TopicTrick Team
GitHub Mastery: The Final Project and Assessment

GitHub Mastery: The Final Project and Assessment

This capstone project brings together every major concept from the GitHub Mastery course: branch protection, CI/CD automation, matrix builds, security scanning, deployment environments, OIDC authentication, and release automation. Completing this project produces a portfolio-quality repository that demonstrates production-grade DevOps skills to any engineering team.

Work through each section in order. Each section builds on the previous one.


Project Overview

You will build and configure a full CI/CD pipeline for a real application with the following capabilities:

RequirementSkill Demonstrated
Multi-OS test matrix (Ubuntu + Windows)Matrix builds, cross-platform CI
Automated dependency security scanningDependabot, CodeQL
Environment-gated production deploymentDeployment environments, approval gates
OIDC-based AWS authenticationKeyless secrets, OIDC
Semantic release automationGit tags, Conventional Commits
Canary deployment strategyControlled rollout
Reusable workflow for deploy stepWorkflow modularity

Part 1: Repository Setup and Branch Protection

Step 1.1: Initialise the project

bash
# Create a Node.js project (or use an existing repo)
mkdir github-mastery-capstone && cd github-mastery-capstone
git init
npm init -y
npm install --save-dev typescript jest @types/jest ts-jest

# Create a basic TypeScript project
mkdir src
cat > src/calculator.ts << 'EOF'
export function add(a: number, b: number): number {
  return a + b;
}

export function divide(a: number, b: number): number {
  if (b === 0) throw new Error('Division by zero');
  return a / b;
}
EOF

cat > src/calculator.test.ts << 'EOF'
import { add, divide } from './calculator';

describe('add', () => {
  it('adds two numbers', () => expect(add(2, 3)).toBe(5));
});

describe('divide', () => {
  it('divides two numbers', () => expect(divide(10, 2)).toBe(5));
  it('throws on division by zero', () => expect(() => divide(1, 0)).toThrow());
});
EOF

git add . && git commit -m "feat: initial project setup"
git branch -M main
git remote add origin https://github.com/YOUR_USERNAME/github-mastery-capstone.git
git push -u origin main

Step 1.2: Configure branch protection

In the GitHub UI: Settings → Branches → Add branch protection rule

text
Branch name pattern: main

☑ Require a pull request before merging
  ☑ Require approvals: 1
  ☑ Dismiss stale pull request approvals when new commits are pushed

☑ Require status checks to pass before merging
  ☑ Require branches to be up to date before merging
  Search and add: ci / lint, ci / test (ubuntu-latest), ci / test (windows-latest)

☑ Require signed commits
☑ Do not allow bypassing the above settings

Step 1.3: Create a CODEOWNERS file

text
# .github/CODEOWNERS
# All changes require review from the platform team
* @YOUR_USERNAME

# Infrastructure changes require an additional review
/.github/workflows/ @YOUR_USERNAME
/infrastructure/ @YOUR_USERNAME

Part 2: CI Pipeline with Matrix Builds

Create the core CI workflow that runs on every pull request:

yaml
# .github/workflows/ci.yml
name: CI

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

jobs:
  lint:
    name: ci / lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci
      - run: npx tsc --noEmit          # TypeScript type checking
      - run: npx eslint src/           # Linting

  test:
    name: ci / test
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
        node: ['18', '20']
      fail-fast: false    # Don't cancel other matrix jobs on failure
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: 'npm'

      - run: npm ci
      - run: npm test -- --coverage

      - name: Upload coverage
        uses: actions/upload-artifact@v4
        if: matrix.os == 'ubuntu-latest' && matrix.node == '20'
        with:
          name: coverage-report
          path: coverage/

  build:
    name: ci / build
    needs: [lint, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci && npm run build

      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: dist-${{ github.sha }}
          path: dist/
          retention-days: 7

Part 3: Security Scanning

Step 3.1: Enable Dependabot

yaml
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: npm
    directory: /
    schedule:
      interval: weekly
      day: monday
      time: "09:00"
    open-pull-requests-limit: 5
    labels:
      - dependencies
    commit-message:
      prefix: "chore"

  - package-ecosystem: github-actions
    directory: /
    schedule:
      interval: weekly
    commit-message:
      prefix: "chore(ci)"

Step 3.2: Enable CodeQL

yaml
# .github/workflows/codeql.yml
name: CodeQL Security Analysis

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 6 * * 1'    # Weekly on Monday at 6am UTC

jobs:
  analyze:
    name: Analyze (${{ matrix.language }})
    runs-on: ubuntu-latest
    permissions:
      security-events: write
      contents: read

    strategy:
      matrix:
        language: [javascript-typescript]

    steps:
      - uses: actions/checkout@v4

      - uses: github/codeql-action/init@v3
        with:
          languages: ${{ matrix.language }}
          queries: security-extended    # More thorough than default

      - uses: github/codeql-action/autobuild@v3

      - uses: github/codeql-action/analyze@v3
        with:
          category: /language:${{ matrix.language }}

Enable Secret scanning and Push protection in: Settings → Security → Code security and analysis → Enable all.


Part 4: Deployment Environments and OIDC

Step 4.1: Create deployment environments

Settings → Environments → New environment

Create two environments:

  1. staging: No required reviewers, all branches allowed
  2. production: Required reviewers (add yourself), main branch only, wait timer 5 minutes

Step 4.2: Configure AWS OIDC (one-time setup)

bash
# Create the OIDC identity provider in AWS
aws iam create-open-id-connect-provider \
  --url https://token.actions.githubusercontent.com \
  --client-id-list sts.amazonaws.com \
  --thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1

# Create IAM role for staging deployments
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

aws iam create-role \
  --role-name github-actions-staging \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::'"$ACCOUNT_ID"':oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        },
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:YOUR_USERNAME/github-mastery-capstone:environment:staging"
        }
      }
    }]
  }'

Store the role ARN as a Variable (not a secret — it is not sensitive) in the staging environment: AWS_ROLE_ARN.

Step 4.3: Deployment workflow

yaml
# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy-staging:
    name: Deploy to Staging
    runs-on: ubuntu-latest
    environment: staging
    permissions:
      id-token: write
      contents: read
    needs: []    # No dependency — deploys immediately after push to main

    steps:
      - uses: actions/checkout@v4

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ vars.AWS_ROLE_ARN }}
          aws-region: us-east-1

      - name: Deploy to staging
        run: |
          echo "Deploying commit ${{ github.sha }} to staging..."
          # aws s3 sync ./dist s3://your-staging-bucket/
          echo "Deployed successfully"

  deploy-production:
    name: Deploy to Production
    runs-on: ubuntu-latest
    environment: production       # Requires manual approval
    permissions:
      id-token: write
      contents: read
    needs: [deploy-staging]

    steps:
      - uses: actions/checkout@v4

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ vars.AWS_ROLE_ARN }}
          aws-region: us-east-1

      - name: Deploy to production
        run: |
          echo "Deploying ${{ github.sha }} to production..."
          echo "Deployed successfully"

After pushing to main, the staging job runs automatically. The production job pauses and sends a notification to you (as a required reviewer). Only after you approve does production deployment proceed.


Part 5: Release Automation

Step 5.1: Configure semantic-release

bash
npm install --save-dev semantic-release @semantic-release/changelog @semantic-release/git
json
// .releaserc.json
{
  "branches": ["main"],
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/changelog",
    ["@semantic-release/npm", { "npmPublish": false }],
    "@semantic-release/github",
    ["@semantic-release/git", {
      "assets": ["CHANGELOG.md", "package.json"],
      "message": "chore(release): ${nextRelease.version} [skip ci]"
    }]
  ]
}
yaml
# .github/workflows/release.yml
name: Release

on:
  push:
    branches: [main]

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      issues: write
      pull-requests: write

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          persist-credentials: false

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci

      - name: Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: npx semantic-release

Test it by making commits with Conventional Commit messages:

bash
git commit -m "feat: add multiply function"
git commit -m "fix: handle NaN inputs in divide"
git commit -m "feat!: change API to return Result type

BREAKING CHANGE: divide now returns {value, error} instead of throwing"

Part 6: Assessment Checklist

Before submitting, verify every item:

Repository Configuration

  • Branch protection rules on main with required status checks
  • CODEOWNERS file routing workflow changes to your account
  • Dependabot enabled for npm and GitHub Actions
  • Secret scanning with push protection enabled

CI Pipeline

  • Matrix builds run on Ubuntu and Windows
  • Matrix builds run on Node 18 and Node 20
  • Test coverage artifact uploaded
  • Build artifact uploaded with SHA in the name
  • All status checks are required before merging to main

Security

  • CodeQL runs on every PR and weekly
  • At least one Dependabot PR merged
  • No hardcoded credentials anywhere in the repository

Deployment

  • Staging environment with no required reviewers
  • Production environment with required reviewers and wait timer
  • OIDC configured — no AWS access keys stored as secrets
  • Production deployment requires manual approval

Release

  • semantic-release configured
  • CHANGELOG.md generated automatically
  • GitHub Release created with release notes
  • At least one versioned tag exists (v1.0.0 or similar)

Frequently Asked Questions

Q: My CI workflow fails because status checks are not registered yet. How do I fix this?

Status check names only appear in the branch protection dropdown after they have run at least once. Push a commit to a branch, open a pull request, let the workflow run, then add the status check names in branch protection settings. Alternatively, add them manually by typing the exact name (e.g., ci / test (ubuntu-latest)).

Q: The production deployment requires approval but nobody is approving it. How do I test it?

Add yourself as a required reviewer for the production environment. When the deployment is pending, go to the Actions tab, open the workflow run, and click Review deployments → Approve and deploy. In a real team, this would be a second person.

Q: semantic-release says "There are no relevant changes, so no new version is released." What does that mean?

semantic-release only creates a release when it finds commits matching its rules: feat: triggers a minor version, fix: triggers a patch version, and BREAKING CHANGE: triggers a major version. Commits like chore:, docs:, and test: do not trigger releases. Make a feat: commit to test the release.

Q: Can I put this project on my CV?

Yes. A repository with a working CI/CD pipeline, OIDC authentication, multi-OS matrix builds, CodeQL security scanning, and automated semantic releases demonstrates senior-level DevOps skills. Link to the repository and describe the architecture in your portfolio. Engineers reviewing your application can see the full commit history, workflow runs, and configuration.


Key Takeaway

You have assembled a production-grade GitHub repository from the ground up: branch protection enforces code review, matrix builds verify cross-platform compatibility, CodeQL and Dependabot handle security continuously, deployment environments enforce approval gates, OIDC eliminates credential storage, and semantic-release automates the release process end-to-end. These are the capabilities that distinguish a software team that ships with confidence from one that ships with anxiety. The repository you built here is the template — apply this pattern to every production project you work on.


You have completed the GitHub Mastery Course — masters of the pipeline.