← All blogs
  • GitHub Actions
  • CI/CD
  • YAML

Understanding GitHub Actions Workflow YAML: A Beginner’s Guide

Learn to read GitHub Actions workflow YAML: events, jobs, runners, steps, actions, and the structure behind CI/CD pipelines.

Originally published on Medium ↗. Read the full article below.

If you’re getting started with CI/CD and GitHub Actions, one of the first things you’ll encounter is a .yml or .yaml workflow file.

At first glance, the syntax can look a little unfamiliar. But once you understand the structure, GitHub Actions workflows become surprisingly straightforward.

The easiest way to think about a workflow is:

Workflow → Events → Jobs → Steps → Actions/Commands

Let’s break that down.

Understanding GitHub Actions Workflow YAML: A Beginner’s Guide

1. What is a GitHub Actions workflow?

A GitHub Actions workflow is a YAML file that tells GitHub:

  • When something should happen
  • What work should be performed
  • Where that work should run
  • Which commands or reusable actions should be executed

Workflow files are typically stored inside:

.github/workflows/

For example:

.github/
└── workflows/
    └── ci.yml

The filename is up to you. You might call it ci.yml, build.yml, test.yml, or something more descriptive.

2. name — Give your workflow a name

A workflow usually starts with:

name: Continuous Integration

This is simply the human-readable name of the workflow.

It helps you identify the workflow in the GitHub Actions interface.

You could use names such as:

name: Build and Test

or:

name: Application CI

There is no special naming convention you have to follow.

3. on — Define when the workflow should run

The on section defines the events that trigger your workflow.

For example:

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

This tells GitHub to run the workflow when:

  1. Code is pushed to main
  2. A pull request targeting main is created or updated

This is where GitHub Actions becomes useful for CI.

Imagine a developer submits a pull request:

Developer creates PR
        ↓
pull_request event
        ↓
GitHub Actions starts
        ↓
Build + Tests
        ↓
Pass / Fail

This allows you to automatically verify changes before they are merged.

4. GitHub Actions supports many events

push and pull_request are just two examples.

Depending on your requirements, workflows can respond to events such as:

on:
  push:
  pull_request:
  issues:
  workflow_dispatch:
  schedule:

For example:

on:
  workflow_dispatch:

allows a workflow to be manually triggered from GitHub.

You can also configure events with additional conditions, such as specific branches or activity types.

5. jobs — Define the work

Once you’ve defined when the workflow should run, you need to define what it should do.

That’s where jobs comes in.

jobs:
  build:

Here, build is the job ID.

You choose the ID yourself.

For example:

jobs:
  build:

or:

jobs:
  test:

or:

jobs:
  deploy:

A workflow can contain one job or multiple jobs.

For example:

Workflow
│
├── build
├── test
└── deploy

Jobs can also have relationships between them, allowing you to create more complex pipelines.

6. runs-on — Choose the execution environment

A job needs an environment in which to run.

For example:

jobs:
  build:
    runs-on: ubuntu-latest

This tells GitHub to execute the job on an Ubuntu runner.

Conceptually:

Workflow triggered
        ↓
Runner created
        ↓
Job executes
        ↓
Runner is cleaned up

Depending on your requirements, GitHub Actions can run jobs on different operating systems and environments.

The important idea is that you don’t necessarily need to maintain a dedicated machine just to execute your CI workflow.

7. steps — Break the job into individual operations

Inside a job, you’ll normally define a series of steps:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Build application
        run: ./gradlew build

Each item under steps represents an operation.

You can think of it as a checklist:

1. Get the source code
2. Prepare the environment
3. Run the build
4. Run tests
5. Publish results

The steps execute in sequence within the job.

8. uses — Reuse an existing GitHub Action

One of the most useful features of GitHub Actions is the ability to reuse actions created by GitHub, your organization, or the wider community.

For example:

- name: Checkout code
  uses: actions/checkout@v4

Here we’re using the checkout action.

Instead of writing our own logic to retrieve the repository’s source code, we can reuse an existing action.

The general syntax is:

uses: owner/action@version

For example:

uses: actions/checkout@v4

You can think of this as:

owner      → actions
action     → checkout
version    → v4

This makes workflows much easier to maintain and reuse.

9. with — Configure an action

Actions can often accept configuration parameters.

For example:

- name: Set up Java
  uses: actions/setup-java@v4
  with:
    java-version: '17'
    distribution: 'temurin'

The with section provides inputs to the action.

Conceptually, you can think of it like calling a function with arguments:

setup Java
    Java version = 17
    Distribution = Temurin

The exact parameters depend on the action you’re using.

Always check the action’s documentation to see which inputs it supports.

10. run — Execute a shell command

Not everything needs to be a reusable Action.

Sometimes you simply want to execute a command.

That’s what run is for.

For example:

- name: Build application
  run: ./gradlew build

The command is executed directly in the job’s environment.

You could also run commands such as:

- name: Check Java version
  run: java -version

or:

- name: List files
  run: ls -la

So the distinction is important:

uses:

means:

Use an existing reusable Action.

While:

run:

means:

Execute a shell command.

11. Running multiple commands

You can execute multiple commands in one step using YAML’s multiline syntax:

- name: Prepare environment
  run: |
    chmod +x gradlew
    java -version
    ./gradlew clean

The | tells YAML that the following indented lines belong to the same multiline value.

12. Steps share the job environment

Consider this workflow:

steps:
  - name: Checkout code
    uses: actions/checkout@v4
  - name: Set up Java
    uses: actions/setup-java@v4
    with:
      java-version: '17'
      distribution: 'temurin'
  - name: Build
    run: ./gradlew build

The operations form a logical sequence:

Runner
  ↓
Checkout source code
  ↓
Configure Java
  ↓
Run Gradle
  ↓
Build application

The later steps can make use of the environment and files prepared by earlier steps within the same job.

That’s why the order of steps matters.

13. A complete example

Putting the concepts together, a simple Java CI workflow might look like this:

name: Java CI
on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Set up Java
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
      - name: Make Gradle executable
        run: chmod +x gradlew
      - name: Build application
        run: ./gradlew build

Let’s read this workflow in plain English:

Call this workflow “Java CI”.

Run it when code is pushed to main or when a pull request targets main.

Create a build job on an Ubuntu runner.

Checkout the repository.

Set up Java 17.

Make the Gradle wrapper executable.

Run the Gradle build.

That’s essentially what the YAML is communicating.

14. The hierarchy is the key to understanding YAML

One of the easiest ways to understand workflow syntax is to focus on its hierarchy:

Workflow
│
├── name
│
├── on
│    ├── push
│    └── pull_request
│
└── jobs
     │
     └── build
          ├── runs-on
          │
          └── steps
               ├── checkout
               ├── setup Java
               ├── command
               └── build

The indentation in YAML represents this hierarchy.

For example:

jobs:
  build:
    runs-on: ubuntu-latest

means:

jobs
 └── build
      └── runs-on

YAML indentation therefore isn’t just formatting — it determines the structure of the configuration.

15. The most important keywords to remember

If you’re just starting with GitHub Actions, focus on these:

KeywordPurposenameName the workflowonDefine workflow triggersjobsDefine the work to performruns-onChoose the runnerstepsDefine individual operationsusesReuse an existing ActionwithConfigure an ActionrunExecute a shell command

Once these concepts are clear, most beginner-level GitHub Actions workflows become much easier to read.

Final mental model

Don’t try to memorize an entire workflow file.

Instead, remember this:

WHEN?
  ↓
on
WHAT?
  ↓
jobs
WHERE?
  ↓
runs-on
HOW?
  ↓
steps
REUSE SOMETHING?
  ↓
uses
CONFIGURE IT?
  ↓
with
RUN A COMMAND?
  ↓
run

GitHub Actions workflows are essentially declarative automation recipes.

You describe the event that should start the workflow, define the jobs that need to happen, select an execution environment, and then specify the actions and commands that should run.

Once you start thinking in that structure, writing your own CI/CD workflows becomes much less intimidating.