Arc 4 Quest 16

The Kingdom's Actions

GitHub Actions, YAML workflows and automation

The Master Archivist guides you into the depths of the Great Forge. Around you, immense machines turn without rest, powered by streams of blue magic. Scrolls descend on conveyor belts, pass under automatic stamps, then are sorted and stored in luminous alcoves. Not a single blacksmith in sight - everything runs on its own.

"In your last quest, you discovered the Eternal Forges and understood the principles of continuous integration and deployment. Today, you will learn to program the Forges yourself. Every Forge obeys Actions - enchanted instruction scrolls that tell it exactly what to do when new chronicles arrive. You are going to write your first Action."

What is a GitHub Action?

A GitHub Action is an automated workflow that runs directly on GitHub's servers. You don't need to install anything, configure anything on your machine - GitHub takes care of everything.

The principle is simple: you describe in a file what you want to happen and when you want it to happen. GitHub executes these instructions automatically.

For example:

  • On every push, run the tests
  • On every pull request, check the code style
  • Every night at midnight, generate a report
  • When a version is tagged, deploy the application

"Imagine a tireless scribe waiting in the Forge, day and night. As soon as a new scroll arrives, he reads the instructions you wrote and executes them to the letter. That is exactly what Actions do."

Key concepts

Before writing your first Action, you need to understand the vocabulary. GitHub Actions uses five fundamental concepts:

Workflow - the instruction scroll

A workflow is a YAML file describing a complete automated process. It is the scroll you write for the Forge. A repository can contain multiple workflows.

Job - the task

A job is a set of steps that run on the same machine. A workflow can contain multiple jobs. By default, jobs run in parallel (at the same time).

Step - the individual action

A step is an individual action inside a job. Each step is either a shell command (run:) or a reusable action (uses:).

Action - the reusable component

An action (with a lowercase "a") is a prefabricated component you can use in your steps. Thousands exist on the GitHub marketplace. The most common is actions/checkout@v4 which retrieves your code.

Runner - the execution machine

A runner is the virtual machine on which your job runs. GitHub offers free runners on Ubuntu, Windows and macOS.

Concept Forge analogy Description
Workflow The instruction scroll YAML file describing the complete process
Job A task in the Forge Set of steps on the same machine
Step A blacksmith's gesture An individual action (command or action)
Action A prefabricated tool Reusable component from the marketplace
Runner The Forge's anvil Virtual machine that runs the job

"An instruction scroll (workflow) contains one or more tasks (jobs). Each task is a series of gestures (steps). Some gestures use prefabricated tools (actions) forged by other Masters. And the whole thing runs on a magical anvil (runner) that GitHub lends you for free."

Where to place workflows?

GitHub looks for workflows in a very specific folder:

your-repo/
  .github/
    workflows/
      my-workflow.yml
      another-workflow.yml

The path is always .github/workflows/. If you place your file elsewhere, GitHub will ignore it.

Important: The .github folder starts with a dot - it's a hidden folder. On Linux/macOS, use ls -a to see it. On Windows, enable hidden file display.

Files must have the .yml or .yaml extension.

YAML basics

GitHub Actions workflows are written in YAML (YAML Ain't Markup Language). It's a data format readable by humans. Here are the essential rules:

Indentation is crucial

In YAML, indentation defines the structure. We use spaces (never tabs!). Each nesting level adds 2 spaces.

# Correct
parent:
  child:
    grandchild: value

# INCORRECT - mixed indentation
parent:
  child:
      grandchild: value   # 6 spaces instead of 4!

Warning: Never use tabs in YAML! Only spaces. This is the most common mistake for beginners.

Key-value pairs

name: Tests
version: 1.0
active: true

Lists

List items start with a dash followed by a space:

fruits:
  - apple
  - banana
  - cherry

Multi-line text

The | character allows writing text on multiple lines:

script: |
  echo "First line"
  echo "Second line"
  echo "Third line"

"YAML is like the calligraphy of enchanted scrolls. Every space counts, every indentation has meaning. One space too many or too few, and the enchantment fails. Rigor is the key."

Your first workflow

Here is the simplest possible workflow. It runs on every push and displays a message:

name: Tests
on: push
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: echo "Tests pass!"

Let's break down each line:

Line Meaning
name: Tests The workflow name (displayed in GitHub)
on: push Trigger: runs on every push
jobs: Start of the task list
test: Job name (you choose the name)
runs-on: ubuntu-latest Ubuntu virtual machine
steps: Start of the step list
- uses: actions/checkout@v4 Step 1: retrieve the repository code
- run: echo "..." Step 2: execute a shell command

The actions/checkout@v4 action is almost always the first step. Without it, your code is not available on the runner - the job runs on a blank machine!

Triggers

The on: keyword defines when the workflow starts. Here are the most common triggers:

On every push

on: push

On every pull request

on: pull_request

On multiple events

on: [push, pull_request]

On specific branches

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

This workflow only triggers for pushes to main or develop, and for pull requests targeting main.

On a schedule (cron)

on:
  schedule:
    - cron: '0 0 * * *'   # Every day at midnight UTC

Manually

on: workflow_dispatch

This trigger adds a "Run workflow" button in the GitHub interface. Very handy for manual deployments.

"Triggers are the sentinels of the Forge. They keep watch at all times and wake the machines at the right moment. A push? Tests launch. A pull request? The automatic review starts. Midnight? The report is generated. Everything is orchestrated."

A more complete workflow

Here is a realistic workflow for a project with tests:

name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: npm install

      - name: Run tests
        run: npm test

      - name: Check style
        run: npm run lint

Each step can have a name that appears in the logs. It's optional but highly recommended for readability.

Multiple jobs and dependencies

By default, jobs run in parallel. To create dependencies between jobs, use the needs: keyword.

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm run build

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: echo "Deploying..."

In this example:

  • test runs first
  • build waits for test to finish successfully
  • deploy waits for build to finish successfully

If test fails, neither build nor deploy will run. That's exactly the desired behavior - no deployment if tests fail!

A job can also depend on multiple jobs:

  deploy:
    needs: [test, build, lint]

"In the Forge, certain operations must be done in order. You don't quench the sword before forging it, and you don't polish it before quenching. The needs keyword enforces this sacred order."

The marketplace - Tools from other Masters

GitHub has a marketplace with thousands of prefabricated actions. Instead of writing everything yourself, you can reuse the work of others.

Some popular actions:

Action Usage
actions/checkout@v4 Retrieve the repository code
actions/setup-node@v4 Install Node.js
actions/setup-python@v5 Install Python
actions/upload-artifact@v4 Save produced files
actions/cache@v4 Cache dependencies

Example with Node.js:

steps:
  - uses: actions/checkout@v4

  - uses: actions/setup-node@v4
    with:
      node-version: '20'

  - run: npm install
  - run: npm test

The with: keyword passes parameters to an action. Here, we request Node.js version 20.

"The marketplace is the great library of the Forges. Thousands of Master Blacksmiths have deposited their tools there for others to use. Why reinvent the anvil when someone else has already forged it perfectly?"

Secrets and environment variables

Some information must never appear in your code: passwords, API keys, access tokens. GitHub lets you store them as secrets.

Using a secret in a workflow

steps:
  - name: Deploy
    run: ./deploy.sh
    env:
      API_KEY: ${{ secrets.API_KEY }}
      DB_PASSWORD: ${{ secrets.DB_PASSWORD }}

Secrets are configured in the repository's Settings on GitHub, under "Secrets and variables" > "Actions".

Simple environment variables

For non-sensitive values, you can define environment variables directly:

env:
  NODE_ENV: production
  APP_NAME: my-application

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Application: $APP_NAME"

Variables defined at root level are available in all jobs. You can also define them at the job or step level.

"Secrets are like the protected formulas of the Master Blacksmiths. You never write them on an ordinary scroll - you lock them in a magic chest and reference them by name. Even if someone steals the scroll, they will only see the name, never the formula."

Checking results

When a workflow runs, you can follow its progress in the Actions tab of your GitHub repository.

Logs

Each step produces logs you can view by clicking on it. This is where you'll see error messages if something fails.

Statuses

Each run has a status:

  • Green (success): all jobs passed
  • Red (failure): at least one job failed
  • Yellow (in progress): the workflow is currently running
  • Grey (cancelled): the workflow was cancelled

Status badges

You can add a badge to your README that displays the status of the latest workflow:

![CI](https://github.com/USERNAME/REPO/actions/workflows/FILENAME.yml/badge.svg)

This badge updates automatically. Green if tests pass, red otherwise. It's a trust signal for everyone who visits your repository.

"Badges are the emblems of the Forge. They show everyone that your code is tested, verified, approved by the machines. A repository without a badge is a castle without a flag - you don't know if it's inhabited or abandoned."

Hands-on exercise - Write your first Action

Write your first instruction scroll for the Forge:

  1. Create a forge-actions repository
  2. Create the .github/workflows/ folder
  3. Create a test script test.sh
  4. Create a README.md
  5. Write a verification.yml workflow that triggers on push, retrieves the code and runs the tests
  6. Commit everything
  7. Run the verification script

Step 1 - Create the repository

mkdir forge-actions
cd forge-actions
git init -b main

Step 2 - Create the workflow structure

mkdir -p .github/workflows

This is the magic folder that GitHub watches. Any .yml file placed here will be treated as a workflow.

Step 3 - Create a test script

Before writing the workflow, create a small script the workflow can execute:

cat > test.sh << 'EOF'
#!/bin/bash
echo "=== Chronicle verification ==="
echo "Test 1: The README file exists..."
if [ -f "README.md" ]; then
    echo "  SUCCESS"
else
    echo "  FAILURE"
    exit 1
fi
echo "Test 2: The README is not empty..."
if [ -s "README.md" ]; then
    echo "  SUCCESS"
else
    echo "  FAILURE"
    exit 1
fi
echo "=== All tests pass ==="
EOF
chmod +x test.sh

Step 4 - Create the README

cat > README.md << 'EOF'
# Forge Actions

My first repository with GitHub Actions.

![Tests](https://github.com/USERNAME/REPO/actions/workflows/verification.yml/badge.svg)
EOF

Step 5 - Write the workflow

This is the crucial moment. Create the file .github/workflows/verification.yml:

name: Chronicle verification
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - name: Retrieve the code
        uses: actions/checkout@v4

      - name: Make the script executable
        run: chmod +x test.sh

      - name: Run the tests
        run: ./test.sh

Verify the file is correct:

cat .github/workflows/verification.yml

Step 6 - Commit everything

git add .
git commit -m "Add the chronicle verification workflow"

Step 7 - Run the verification

bash verifier.sh
.\verifier.ps1

Concept summary

Concept Description
Workflow YAML file describing an automated process
Job Set of steps executed on the same machine
Step Individual action within a job
Action Reusable marketplace component
Runner Virtual machine that runs the jobs
name: Workflow name
on: Workflow trigger(s)
jobs: List of tasks to execute
runs-on: Virtual machine type
steps: List of steps in a job
uses: Use a prefabricated action
run: Execute a shell command
needs: Dependency between jobs
with: Parameters passed to an action
env: Environment variables
secrets.* Access to repository secrets

The Master Archivist observes the instruction scroll you just wrote. He holds it up to the light, scans every line with his eyes, then slowly nods his head.

"Your first scroll for the Forge is impeccable. The triggers are well defined, the steps are clear, and the structure follows the rules of YAML. When you push these instructions to a remote Forge, they will execute automatically, without you needing to lift a finger."

He carefully stores the scroll in a leather case marked with the seal of the Forges.

"You now know how to program the Forges. But a single Action is not enough for a true Master. In your next quest, you will learn to create complex pipelines - chains of Actions that test, build and deploy your code in a single fluid motion. The Forges will soon hold no more secrets for you."