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.

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:
- Code is pushed to
main - A pull request targeting
mainis 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
mainor when a pull request targetsmain.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.