Skip to content
Français
CI/CD & Automatisation medium

What is a GitHub Actions workflow?

35 min de lecture

Read this page in French

A workflow is an automated process you define inside your repository. It tells GitHub: "when X happens, do Y". It is the heart of GitHub Actions.

What is a workflow?

A workflow is a text file (YAML) that describes:

  1. When to run (on every push? every pull request? every Monday?)
  2. What to do (run tests? build an image? deploy?)
  3. Where to do it (on which machine?)

When the condition is met, GitHub runs the described actions automatically. You have nothing to do: everything happens in the cloud, on machines managed by GitHub.

Where do you put a workflow?

Workflows must live in a specific folder of your source code:

my-project/
├── src/
│ └── ...
├── package.json
└── .github/
└── workflows/
├── ci.yml ← One workflow
├── deploy.yml ← Another workflow
└── tests.yml ← And another one

The rules to remember:

  • The folder must be named exactly .github/workflows/ (with the leading dot)
  • Files must carry the .yml or .yaml extension
  • You can have as many workflows as you want
  • The file name carries no technical meaning (but pick explicit names)

The essentials of workflow syntax

Workflows are written in YAML, a readable configuration format. Here are the essential rules that keep you out of trouble.

Indentation is critical

In YAML, indentation defines the structure. No braces, no parentheses: what counts is the number of spaces at the start of the line.

# Level 0 (no indentation)
jobs:
# Level 1 (2 spaces)
build:
# Level 2 (4 spaces)
runs-on: ubuntu-24.04
steps:
# Level 3 (6 spaces)
- name: Checkout
# Level 4 (8 spaces)
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

The classic mistake:

# ❌ WRONG: steps is not indented under build
jobs:
build:
runs-on: ubuntu-24.04
steps: # ← Wrong level!
- run: echo "Hello"
# ✅ CORRECT: steps sits at the right level (4 spaces)
jobs:
build:
runs-on: ubuntu-24.04
steps:
- run: echo "Hello"

Multi-line commands

To run several commands, use the | (pipe) character:

- name: Build and test
run: |
echo "Installing..."
npm ci
npm test
npm run build

Every line after the | runs as a separate command.

When do you need quotes?

Quotes are optional except when your text contains special characters (: # { } & * ?):

# ❌ The colon breaks the parsing
message: Note: important
# ✅ Quotes are required
message: "Note: important"

The three main parts of a workflow

Every workflow is structured in three main parts.

1. The name (name)

name: Unit tests

This is the name displayed in the GitHub interface, under the "Actions" tab. Pick something descriptive so you can find your way once you have several workflows.

2. The trigger (on)

on:
push:
branches: [main]

The trigger defines when the workflow runs. It is the event that wakes your workflow up. Without a trigger, the workflow never runs.

3. The jobs (jobs)

jobs:
test:
runs-on: ubuntu-24.04
steps:
- run: npm test

Jobs define what the workflow does. A job is a set of tasks (steps) running on the same machine.

Triggers: when the workflow runs

The on: block defines the events that trigger your workflow. Here are the most used ones:

TriggerWhen it firesTypical use case
pushWhen code is pushedTests, lint, build
pull_requestWhen a PR is opened or updatedValidation before merge
workflow_dispatchManually (a button in the interface)On-demand deployment
scheduleAt fixed times (cron syntax)Nightly security scans
releaseWhen a release is publishedPublishing packages

You can combine several triggers:

on:
push:
branches: [main] # On every push to main
pull_request:
branches: [main] # On every PR targeting main
workflow_dispatch: # And manually when needed

Jobs: the units of work

A job is an independent unit of work. Each job:

  • runs on its own virtual machine (called a runner);
  • can contain several steps;
  • can depend on other jobs or run in parallel.
jobs:
build:
runs-on: ubuntu-24.04
steps:
- run: echo "I build"
test:
runs-on: ubuntu-24.04
steps:
- run: echo "I test"

By default, jobs run in parallel. In the example above, build and test start at the same time.

Running jobs in a specific order

When a job must wait for another one to finish, use needs:

jobs:
build:
runs-on: ubuntu-24.04
steps:
- run: echo "Build"
test:
runs-on: ubuntu-24.04
needs: build # Waits for "build" to finish
steps:
- run: echo "Test"
deploy:
runs-on: ubuntu-24.04
needs: [build, test] # Waits for both to finish
steps:
- run: echo "Deploy"

Runners: where the code runs

The runner is the machine that executes your job. GitHub offers hosted runners, free within certain limits:

RunnerSystemUse case
ubuntu-24.04Linux Ubuntu 24.04The most common, fast and cheap
windows-2025Windows Server 2025.NET applications, Windows tests
macos-15macOS 15 (Sequoia)iOS and macOS applications
jobs:
test-linux:
runs-on: ubuntu-24.04
test-windows:
runs-on: windows-2025
test-mac:
runs-on: macos-15

Steps: the individual tasks

Steps are the tasks to run inside a job. They run sequentially, one after the other, on the same machine.

There are two kinds of steps.

1. Running a shell command (run)

steps:
- name: Print a message
run: echo "Hello!"
- name: Several commands
run: |
echo "First line"
echo "Second line"
npm install
npm test

The | lets you write several commands across several lines.

2. Using an action (uses)

steps:
- name: Check out the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 20

An action is a reusable block of code. Rather than rewriting the logic to "check out the repository code" or "install Node.js", you use an existing action.

Jobs versus steps: understanding the difference

This is a frequent source of confusion. Here is how to tell them apart:

Workflow
├── Job 1 (machine A)
│ ├── Step 1: check out the code
│ ├── Step 2: install the dependencies
│ └── Step 3: run the tests
└── Job 2 (machine B)
├── Step 1: check out the code
└── Step 2: deploy
ConceptDefinitionEnvironment
JobA group of tasksEach job gets its own machine
StepAn individual taskEvery step of a job shares the same machine

An important consequence:

  • Files created by a step are available to the following steps of the same job (same machine).
  • Files created inside a job are not available in another job (different machines). You need artifacts to share files between jobs.

Your first workflow

  1. Create the folder .github/workflows/ at the root of your project

  2. Create a file named hello.yml in that folder, with this content:

    name: Hello World
    on:
    push:
    branches: [main]
    workflow_dispatch:
    jobs:
    hello:
    runs-on: ubuntu-24.04
    steps:
    - name: Say hello
    run: echo "Hello, GitHub Actions!"
    - name: Print some context information
    run: |
    echo "Repository: \${{ github.repository }}"
    echo "Branch: \${{ github.ref_name }}"
    echo "Commit author: \${{ github.actor }}"
  3. Commit and push to the main branch

  4. Open the Actions tab of your repository on GitHub

  5. Watch your workflow run

Where do you see the results?

Once your workflow has been triggered:

  1. Go to your GitHub repository
  2. Click the Actions tab
  3. You see the list of every run (workflow runs)
  4. Click a run to see its jobs
  5. Click a job to see the logs of each step

GitHub Actions interface showing workflow runs and job details

The interface shows you:

  • the steps that succeeded (in green);
  • the steps that failed (in red);
  • the steps currently running;
  • the detailed logs of every command.

The lifecycle of a workflow

When an event triggers a workflow, here is what happens:

  1. Event: someone pushes code, opens a PR, and so on
  2. Detection: GitHub detects the event and looks for matching workflows
  3. Queueing: the workflow is added to the execution queue
  4. Runner assignment: GitHub assigns a virtual machine
  5. Execution: the jobs and steps run
  6. Cleanup: the machine is destroyed, the results are kept

Every run is ephemeral: the machine is created for the occasion and destroyed afterwards. That is why you always start by checking out the code (actions/checkout).

Context variables

In the previous example you saw \${{ github.repository }}. That is a context variable: GitHub exposes information about the current run.

A few useful variables:

VariableContent
github.repositoryThe repository name (owner/repo)
github.ref_nameThe branch or tag name
github.actorThe user who triggered the workflow
github.event_nameThe event type (push, pull_request, and so on)
github.shaThe commit SHA
github.run_idThe unique ID of this run

Next steps

You now know what a workflow is and how it runs. Before going deeper into the syntax, it is essential to understand the security stakes.

Is this site useful to you?

Fewer than 1% of readers support this site.

I maintain more than 700 free guides, with no ads and no tracking. Any support, even a symbolic one, helps cover hosting and keeps these resources free. Thank you for the help.

The form does not show? Open Ko-fi in a new tab.

Subscribe and follow my DevSecOps work on LinkedIn