GitHub Actions Tutorial — Artifacts & Caching Exercises
Practice Exercises — Artifacts & Caching
Test your understanding of artifact upload/download, caching, cache keys, and monorepo patterns with these hands-on exercises.
Exercise 1 — Build and Deploy with Artifacts
Task: Create a workflow with two jobs:
build— checks out code, runsnpm ci && npm run build, and uploads thedist/directory as an artifact namedbuild-outputdeploy— downloads thebuild-outputartifact and prints the directory listing
Show Solution
name: Build and Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: build-output
path: ./dist
retention-days: 7
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
name: build-output
path: ./dist
- run: ls -la ./dist
- run: echo "Deploying..."
Exercise 2 — Upload Test Results on Failure
Task: Create a workflow that:
- Runs
npm test - If tests fail, uploads the
test-results/directory as an artifact namedfailure-report - The artifact should be kept for 3 days only
Show Solution
name: Test with Failure Report
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm test
- name: Upload failure report
if: failure()
uses: actions/upload-artifact@v4
with:
name: failure-report
path: ./test-results/
retention-days: 3
if-no-files-found: warn
The if: failure() ensures the upload only happens when tests fail. retention-days: 3 minimizes storage usage.
Exercise 3 — Dependency Cache with Restore Keys
Task: Create a cache configuration for a Python project that:
- Caches
~/.cache/pip - Uses exact key:
pip-<os>-<hash of requirements.txt> - Falls back to
pip-<os>-thenpip-if exact match misses - Runs
pip install -r requirements.txtafter cache restore
Show Solution
name: Python CI
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ hashFiles('requirements.txt') }}
restore-keys: |
pip-${{ runner.os }}-
pip-
- run: pip install -r requirements.txt
- run: python -m pytest
The restore-keys cascade ensures a partial cache hit even when requirements.txt changes.
Exercise 4 — Matrix Artifacts and Merge
Task: Create a workflow that:
- Runs tests on
ubuntu-latest,windows-latest, andmacos-latestusing a matrix - Each job uploads a test report artifact named
report-<os> - A final
collectjob downloads all report artifacts and merges them into a single directory
Show Solution
name: Cross-Platform Reports
on: push
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- run: echo "Test results for ${{ matrix.os }}" > report.txt
- uses: actions/upload-artifact@v4
with:
name: report-${{ matrix.os }}
path: report.txt
collect:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
pattern: report-*
merge-multiple: true
path: ./all-reports
- run: ls -la ./all-reports
- run: cat ./all-reports/*.txt
The merge-multiple: true combines all matched artifacts into a single directory, so you get all report files in ./all-reports/.
Exercise 5 — Cache Key Design
Task: Design cache keys (just the key: and restore-keys: values) for these scenarios:
- Next.js build cache — depends on dependencies and source files
- Gradle build — depends on Gradle version and
build.gradlefiles - Docker layer cache — depends on Dockerfile and source code
Show Solution
1. Next.js build cache:
key: nextjs-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-${{ hashFiles('src/**', 'pages/**') }}
restore-keys: |
nextjs-${{ runner.os }}-${{ hashFiles('package-lock.json') }}-
nextjs-${{ runner.os }}-
Two levels of specificity: exact match on deps + source, fallback on deps only.
2. Gradle build:
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
gradle-${{ runner.os }}-
Includes all Gradle files and the wrapper version.
3. Docker layer cache:
key: docker-${{ runner.os }}-${{ hashFiles('Dockerfile', 'src/**') }}
restore-keys: |
docker-${{ runner.os }}-${{ hashFiles('Dockerfile') }}-
docker-${{ runner.os }}-
Exact match on Dockerfile + source, fallback on Dockerfile only.
← Back to: Chapter 8: Artifacts & Caching
Next Chapter: Chapter 9: Matrix Strategy →