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:
| Requirement | Skill Demonstrated |
|---|---|
| Multi-OS test matrix (Ubuntu + Windows) | Matrix builds, cross-platform CI |
| Automated dependency security scanning | Dependabot, CodeQL |
| Environment-gated production deployment | Deployment environments, approval gates |
| OIDC-based AWS authentication | Keyless secrets, OIDC |
| Semantic release automation | Git tags, Conventional Commits |
| Canary deployment strategy | Controlled rollout |
| Reusable workflow for deploy step | Workflow modularity |
Part 1: Repository Setup and Branch Protection
Step 1.1: Initialise the project
# 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 mainStep 1.2: Configure branch protection
In the GitHub UI: Settings → Branches → Add branch protection rule
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 settingsStep 1.3: Create a CODEOWNERS file
# .github/CODEOWNERS
# All changes require review from the platform team
* @YOUR_USERNAME
# Infrastructure changes require an additional review
/.github/workflows/ @YOUR_USERNAME
/infrastructure/ @YOUR_USERNAMEPart 2: CI Pipeline with Matrix Builds
Create the core CI workflow that runs on every pull request:
# .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: 7Part 3: Security Scanning
Step 3.1: Enable Dependabot
# .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
# .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:
- staging: No required reviewers, all branches allowed
- production: Required reviewers (add yourself), main branch only, wait timer 5 minutes
Step 4.2: Configure AWS OIDC (one-time setup)
# 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
# .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
npm install --save-dev semantic-release @semantic-release/changelog @semantic-release/git// .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]"
}]
]
}# .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-releaseTest it by making commits with Conventional Commit messages:
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
mainwith 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.
