GitHub Actions Tutorial — Artifacts & Caching Exercises

3 minute read

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:

  1. build — checks out code, runs npm ci && npm run build, and uploads the dist/ directory as an artifact named build-output
  2. deploy — downloads the build-output artifact 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:

  1. Runs npm test
  2. If tests fail, uploads the test-results/ directory as an artifact named failure-report
  3. 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:

  1. Caches ~/.cache/pip
  2. Uses exact key: pip-<os>-<hash of requirements.txt>
  3. Falls back to pip-<os>- then pip- if exact match misses
  4. Runs pip install -r requirements.txt after 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:

  1. Runs tests on ubuntu-latest, windows-latest, and macos-latest using a matrix
  2. Each job uploads a test report artifact named report-<os>
  3. A final collect job 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:

  1. Next.js build cache — depends on dependencies and source files
  2. Gradle build — depends on Gradle version and build.gradle files
  3. 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 →

Updated: