Exercises and projects
Table of contents
- Basic exercises
- Intermediate exercises
- Project 1: Node.js pipeline
- Project 2: Automatic release
- Project 3: Monorepo
- Validation checklist
1 - Basic exercises
Exercise 1.1: Hello World
Create a workflow that displays "Hello World" on every push.
Solution
name: Hello World
on: push
jobs:
hello:
runs-on: ubuntu-latest
steps:
- run: echo "Hello World!"
Exercise 1.2: Multi-event
Create a workflow triggered by push AND pull_request.
Solution
name: Multi-Event
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Triggered by ${{ github.event_name }}"
Exercise 1.3: Environment variables
Create a workflow with a global variable and a step variable.
Solution
name: Variables
on: push
env:
GLOBAL_VAR: "I am global"
jobs:
vars:
runs-on: ubuntu-latest
steps:
- name: Use global
run: echo "$GLOBAL_VAR"
- name: Use local
env:
LOCAL_VAR: "I am local"
run: echo "$LOCAL_VAR"
Exercise 1.4: Checkout and list
Create a workflow that checks out the code and lists the files.
Solution
name: Checkout
on: push
jobs:
list:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ls -la
- run: cat README.md || echo "No README"
🔝 Back to table of contents
2 - Intermediate exercises
Exercise 2.1: npm cache
Create a workflow with npm cache using setup-node.
Solution
name: Build with Cache
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npm run build
Exercise 2.2: Multi-version matrix
Test on Node 16, 18 and 20.
Solution
name: Matrix Test
on: push
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node: [16, 18, 20]
name: Node ${{ matrix.node }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: node --version
Exercise 2.3: Dependent jobs
Create 3 jobs: build → test → deploy (sequential).
Solution
name: Pipeline
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Building..."
test:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "Testing..."
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- run: echo "Deploying..."
Exercise 2.4: Artifacts
Create a file in one job and download it in another.
Solution
name: Artifacts
on: push
jobs:
create:
runs-on: ubuntu-latest
steps:
- run: echo "Hello" > message.txt
- uses: actions/upload-artifact@v4
with:
name: message
path: message.txt
read:
needs: create
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: message
- run: cat message.txt
Exercise 2.5: Conditions
Run a job only on the main branch.
Solution
name: Conditional
on: push
jobs:
always:
runs-on: ubuntu-latest
steps:
- run: echo "Always runs"
main-only:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- run: echo "Only on main"
🔝 Back to table of contents
3 - Project 1: Complete Node.js pipeline
Goal
Create a complete CI/CD pipeline for a Node.js application:
- Lint with ESLint
- Tests with Jest
- Build
- Conditional deployment
Solution
name: Node.js CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: '18'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- run: npm run lint
test:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- run: npm test -- --coverage
- uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: build
path: dist/
deploy-staging:
needs: build
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
environment: staging
steps:
- uses: actions/download-artifact@v4
with:
name: build
path: ./dist
- run: echo "Deploying to staging..."
deploy-production:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/download-artifact@v4
with:
name: build
path: ./dist
- run: echo "Deploying to production..."
🔝 Back to table of contents
4 - Project 2: Automatic release
Goal
Create a workflow that:
- Triggers on
v*tags - Builds the application
- Creates a GitHub release with the assets
Solution
name: Release
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npm run build
- name: Create archive
run: tar -czf dist.tar.gz dist/
- uses: actions/upload-artifact@v4
with:
name: release-archive
path: dist.tar.gz
release:
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
name: release-archive
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: dist.tar.gz
generate_release_notes: true
🔝 Back to table of contents
5 - Project 3: Monorepo
Goal
Create a pipeline for a monorepo with:
- Frontend (React)
- Backend (Node.js)
- Conditional deployment based on the modified files
Solution
name: Monorepo CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
changes:
runs-on: ubuntu-latest
outputs:
frontend: ${{ steps.filter.outputs.frontend }}
backend: ${{ steps.filter.outputs.backend }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
frontend:
- 'frontend/**'
backend:
- 'backend/**'
frontend:
needs: changes
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- run: npm ci
- run: npm test
- run: npm run build
backend:
needs: changes
if: needs.changes.outputs.backend == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
cache-dependency-path: backend/package-lock.json
- run: npm ci
- run: npm test
deploy:
needs: [frontend, backend]
if: always() && github.ref == 'refs/heads/main' && (needs.frontend.result == 'success' || needs.backend.result == 'success')
runs-on: ubuntu-latest
steps:
- run: echo "Deploying changed components..."
🔝 Back to table of contents
6 - Validation checklist
Skills to validate
| Skill | Checked |
|---|---|
| Create a basic workflow | ☐ |
| Use checkout and setup-node | ☐ |
| Configure the cache | ☐ |
| Use variables and secrets | ☐ |
| Create dependent jobs | ☐ |
| Use artifacts | ☐ |
| Configure a matrix | ☐ |
| Add conditions | ☐ |
| Create a reusable workflow | ☐ |
| Configure environments | ☐ |
| Deploy automatically | ☐ |
Self-assessment
- Can you create a lint → test → build → deploy pipeline?
- Do you know how to use the cache to speed up your builds?
- Can you test on several Node versions?
- Do you know how to share data between jobs?
- Can you deploy conditionally based on the branch?
Congratulations!
You have completed the GitHub Actions course! 🎉
You now know how to:
- ✅ Create CI/CD workflows
- ✅ Use Marketplace actions
- ✅ Manage secrets and variables
- ✅ Optimize with cache and artifacts
- ✅ Test across environments with matrix builds
- ✅ Build reusable workflows
- ✅ Create complete deployment pipelines
Next steps
- Apply this knowledge to your projects
- Explore the Marketplace to discover new actions
- Create your own reusable actions
- Share your workflows with your team