GitHub Actions Tutorial — Jobs & Runners Exercises
Practice Exercises — Jobs & Runners
Test your understanding of job configuration, dependencies, outputs, and containers with these hands-on exercises.
Exercise 1 — Diamond Pipeline
Task: Create a workflow with this execution pattern:
┌──────┐
┌───►│ lint │───┐
┌───────┐│ └──────┘ │ ┌────────┐
│ build │─┤ ├─►│ deploy │
└───────┘│ ┌──────┐ │ └────────┘
└───►│ test │───┘
└──────┘
buildruns firstlintandtestrun in parallel afterbuilddeployruns after bothlintandtestsucceed
Show Solution
name: Diamond Pipeline
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "Building..."
lint:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "Linting..."
test:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "Testing..."
deploy:
needs: [lint, test]
runs-on: ubuntu-latest
steps:
- run: echo "Deploying..."
Exercise 2 — Pass Version Between Jobs
Task: Create a workflow where:
- Job
preparegenerates a version string in the formatYYYY.MM.DD-<short-sha>(e.g.,2025.03.28-abc1234) and sets it as a job output - Job
buildreads the version fromprepareand prints “Building version X” - Job
tagreads the version fromprepareand prints “Tagging version X”
Show Solution
name: Version Pipeline
on: push
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.ver.outputs.version }}
steps:
- name: Generate version
id: ver
run: |
VERSION="$(date +%Y.%m.%d)-${GITHUB_SHA::7}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Generated version: $VERSION"
build:
needs: prepare
runs-on: ubuntu-latest
steps:
- run: echo "Building version ${{ needs.prepare.outputs.version }}"
tag:
needs: prepare
runs-on: ubuntu-latest
steps:
- run: echo "Tagging version ${{ needs.prepare.outputs.version }}"
Exercise 3 — Integration Tests with Services
Task: Create a workflow that:
- Runs inside a
node:20container - Starts a PostgreSQL 16 service container
- Waits for PostgreSQL to be healthy
- Runs a command that verifies the database connection
Show Solution
name: Integration Tests
on: push
jobs:
test:
runs-on: ubuntu-latest
container:
image: node:20
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Install PostgreSQL client
run: |
apt-get update
apt-get install -y postgresql-client
- name: Test database connection
run: |
psql postgresql://test:test@postgres:5432/testdb -c "SELECT 1 AS connected;"
Inside a container job, you connect to the service using the service name (postgres) as the hostname.
Exercise 4 — Cleanup on Failure
Task: Create a workflow with three jobs:
setup— creates resources (just echo for now)test— runs tests that fail (exit 1)cleanup— always runs aftertest, even if it fails, and prints “Cleaning up resources”
Show Solution
name: Cleanup on Failure
on: push
jobs:
setup:
runs-on: ubuntu-latest
steps:
- run: echo "Setting up resources..."
test:
needs: setup
runs-on: ubuntu-latest
steps:
- run: |
echo "Running tests..."
exit 1
cleanup:
needs: test
if: always()
runs-on: ubuntu-latest
steps:
- name: Check test result
run: |
echo "Test result: ${{ needs.test.result }}"
echo "Cleaning up resources..."
The if: always() on the cleanup job ensures it runs regardless of whether the test job succeeded or failed.
Exercise 5 — Cross-Platform Testing
Task: Create a workflow that runs echo "Testing on $RUNNER_OS" on three different runners in parallel:
ubuntu-latestwindows-latestmacos-latest
Don’t use matrix strategy (we’ll learn that in Chapter 9). Create three separate jobs.
Show Solution
name: Cross-Platform Tests
on: push
jobs:
test-linux:
runs-on: ubuntu-latest
steps:
- name: Test
run: echo "Testing on $RUNNER_OS"
test-windows:
runs-on: windows-latest
steps:
- name: Test
run: echo "Testing on $env:RUNNER_OS"
shell: pwsh
test-macos:
runs-on: macos-latest
steps:
- name: Test
run: echo "Testing on $RUNNER_OS"
Note that Windows uses PowerShell by default, so the environment variable syntax is $env:RUNNER_OS. All three jobs run in parallel.
← Back to: Chapter 4: Jobs & Runners
Next Chapter: Chapter 5: Steps & Actions →