GitHub Actions Getting Started: YAML Workflow Basics and Trigger Configuration
It’s 3 AM, and that red error message on your screen makes you want to smash your keyboard.
Your code runs perfectly locally, but the moment you push to GitHub, it fails. I edited that YAML file six times—every single time, it was an indentation issue. I remember thinking: why is this harder than writing actual code?
The truth is, GitHub Actions itself isn’t complicated. What’s difficult is the documentation—you’re immediately hit with hundreds of pages of configuration details that leave you dizzy. In this article, I want to walk you through the core structure of YAML workflows in the simplest way possible.
You’ll learn:
- The four core fields of YAML files and what each one does
- 8 common trigger configuration methods and their use cases
- A complete workflow template you can copy and use immediately
- The pitfalls I’ve encountered and how to avoid them
Ready? Let’s dive in.
YAML Workflow Files: Four Core Fields
Honestly, when I first started with GitHub Actions, seeing those YAML files in the .github/workflows directory felt like deciphering alien script. Indentation everywhere, colons everywhere—miss one space and everything breaks.
Later I discovered it really boils down to four core parts. Master these four, and everything else is just icing on the cake.
name: Give Your Workflow a Name
This field is the simplest, yet many people (myself included) overlook it at first.
name: CI for Node.js App
name is what your workflow displays in the GitHub Actions tab. After you push code, when you go to the Actions page in your repository, that’s the text you see.
Here’s a naming tip: use project name + function description. Like MyApp CI or Backend Deploy. This way, when you have multiple workflows later, you can immediately spot the one you’re looking for.
This field is technically optional. If you omit it, GitHub will use the filename instead. But I don’t recommend skipping it—filenames are usually abbreviated, which isn’t as intuitive.
on: When to Trigger
on is the “switch” for your entire workflow. You’re telling GitHub: under what circumstances should this workflow run.
The simplest form:
on: push
This means: trigger whenever there’s any code push.
But in real projects, you typically need more granular control. Like only running when pushing to the main branch:
on:
push:
branches: [main]
Or you might want to trigger when creating a Pull Request too:
on:
push:
branches: [main]
pull_request:
branches: [main]
Triggers are the essence of GitHub Actions. I’ll dedicate a whole section later to cover 8 common scenarios. For now, just remember: on determines the “trigger timing” of your workflow.
jobs: Define What to Do
jobs is the body of your workflow, defining “what specific tasks to execute.”
A workflow can contain multiple jobs, which run in parallel by default. Each job needs to specify its runtime environment using the runs-on field:
jobs:
build:
runs-on: ubuntu-latest
steps:
# ... steps list
This code defines a job called build that will run on the latest Ubuntu version provided by GitHub.
If your workflow has multiple jobs, you can use the needs field to define dependencies:
jobs:
test:
runs-on: ubuntu-latest
# ... test steps
deploy:
needs: test # Wait for test to complete before executing
runs-on: ubuntu-latest
# ... deploy steps
This way, deploy will wait for test to finish before starting. If test fails, deploy won’t run.
steps: Specific Execution Steps
steps are the smallest execution units within a job—commands or actions executed in sequence.
Each step has two ways to write it:
1. Use run to execute commands:
steps:
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
After run is the command you want to execute in the terminal, just like typing commands locally.
2. Use uses to call Actions:
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
uses is GitHub Actions’ killer feature. Someone else has already written an Action, and you just use it directly. For example, actions/checkout@v4 pulls your code, and actions/setup-node@v4 sets up your Node.js environment.
with passes parameters to the Action. Like node-version: '20' above tells setup-node: I want to use Node.js version 20.
That covers all four fields. Simpler than you imagined, right?
Trigger Deep Dive: 8 Common Scenarios
Earlier I mentioned the on field determines trigger timing. GitHub Actions supports dozens of triggers, but honestly, only a handful are commonly used.
I’ve put together a table to help you quickly understand each trigger’s use case:
| Trigger | Typical Scenario | Configuration Example |
|---|---|---|
push | Code pushed to branch | on: push: branches: [main] |
pull_request | PR created or updated | on: pull_request: types: [opened, synchronize] |
schedule | Scheduled tasks (Cron) | on: schedule: - cron: '0 0 * * *' |
workflow_dispatch | Manual trigger | on: workflow_dispatch: inputs: env: ... |
workflow_call | Reusable workflow | `on: workflow_call: inputs: …’ |
release | Release events | on: release: types: [published] |
issues | Issue events | on: issues: types: [opened, labeled] |
repository_dispatch | External events | on: repository_dispatch: types: [deploy] |
Let me elaborate on a few of the most commonly used ones.
push: The Most Basic Trigger
push is the first trigger you’ll encounter. It fires when code is pushed to a branch.
Simple configuration:
on: push
But there’s a problem with this configuration: pushes to any branch will trigger it. If your repository has 20 branches, every push from anyone will run the workflow, and you’ll burn through your free quota quickly.
A more sensible approach is to limit branches:
on:
push:
branches: [main, develop]
Or use wildcards:
on:
push:
branches:
- 'main'
- 'release/**' # Matches release/v1.0, release/v2.0, etc.
pull_request: Guardian Before Code Merge
pull_request triggers when a PR is created or updated, typically used for running tests and checking code style.
on:
pull_request:
branches: [main]
You can use the types field for more granular control:
on:
pull_request:
types: [opened, synchronize, reopened]
opened: PR just createdsynchronize: PR has new commitsreopened: PR reopened
With this configuration, the workflow only runs under these three circumstances, avoiding wasted resources.
schedule: Scheduled Tasks
schedule uses Cron expressions to define scheduled triggers. Like running tests once every day at midnight:
on:
schedule:
- cron: '0 0 * * *' # Every day at UTC 0:00
Cron expressions have 5 fields: minute, hour, day, month, day of week.
Some common timings:
0 0 * * *: Every day at UTC 0:00 (8 AM Beijing time)0 */6 * * *: Every 6 hours30 2 * * 1: Every Monday at UTC 2:30
One pitfall to watch out for: GitHub uses UTC time. If you want to run a task at 9 AM Beijing time, you need to set it to UTC 1:00 (0 1 * * *).
workflow_dispatch: Manual Trigger
Sometimes you don’t want automatic triggering; you want to click a button to run it manually. That’s what workflow_dispatch is for.
on:
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
default: 'staging'
type: choice
options:
- staging
- production
After configuration, a “Run workflow” button appears on the Actions page. Click it to select environment parameters before triggering.
This trigger is particularly useful in deployment scenarios: automated testing, manual deployment.
workflow_call: Reusable Workflows
If your workflow logic is complex, or multiple repositories need the same process, you can use workflow_call to turn your workflow into a reusable component.
Define a reusable workflow:
# .github/workflows/ci.yml
on:
workflow_call:
inputs:
node-version:
required: true
type: string
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm ci && npm test
Call it from another workflow:
# .github/workflows/main.yml
on: push
jobs:
call-ci:
uses: ./.github/workflows/ci.yml
with:
node-version: '20'
This lets you reuse the same CI logic across different repositories—change it once, it takes effect everywhere.
The other triggers (release, issues, repository_dispatch) are used less frequently, so I won’t expand on them. If you’re interested, check out the official GitHub documentation.
Hands-on: Your First Workflow Template
Enough concepts—let’s get our hands dirty.
Here’s a complete Node.js project CI workflow. You can copy this directly into your own project:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
# 1. Checkout code
- name: Checkout code
uses: actions/checkout@v4
# 2. Setup Node.js environment
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
# 3. Install dependencies
- name: Install dependencies
run: npm ci
# 4. Run tests
- name: Run tests
run: npm test
How to Use It?
Step 1: Create a .github/workflows folder in your project root (if it doesn’t already exist).
Step 2: Create a ci.yml file in that folder and paste the code above.
Step 3: Commit and push to GitHub.
After pushing, go to your repository’s Actions tab, and you should see your workflow running.
Line-by-Line Explanation
name: CI: Workflow name, displays in the Actions pageon: push: branches: [main]: Triggers when code is pushed to the main branchon: pull_request: branches: [main]: Also triggers when someone opens a PR to mainjobs: build:: Defines a job called buildruns-on: ubuntu-latest: Runs on the latest Ubuntu provided by GitHubactions/checkout@v4: Official Action that pulls your code to the virtual machineactions/setup-node@v4: Official Action that sets up the Node.js environmentnpm ci: Installs dependencies (faster and cleaner thannpm install)npm test: Runs tests
If your project isn’t Node.js, just swap out the middle steps. For a Python project:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: pip install -r requirements.txt
- run: pytest
It’s basically a “pull code → setup environment → install dependencies → run tests” pattern.
Common Configuration Errors Troubleshooting
I’ve stepped in these pits so you don’t have to.
Here’s a troubleshooting checklist to reference when you encounter issues:
| Error Symptom | Possible Cause | Solution |
|---|---|---|
| Workflow doesn’t trigger | Branch filter conditions wrong | Check branches configuration, confirm branch name case is correct |
| YAML parsing fails | Using Tab for indentation | Change all to space indentation, YAML doesn’t support Tab |
| Secrets don’t work | Scope error | Verify secrets are at the correct level (job or step) |
| Job dependency fails | needs reference error | Check job-id spelling, case-sensitive |
| Workflow runs slowly | Not using cache | Add actions/cache to cache dependencies |
| Permission denied errors | GITHUB_TOKEN insufficient permissions | Add permissions configuration in job |
Error 1: Wrong Indentation
This is the most common error, bar none.
YAML is extremely sensitive to indentation. You must use spaces, not Tab. And each level of indentation must be exactly 2 spaces (or some other consistent number, but GitHub Actions defaults to 2).
# Wrong example: indentation messed up
jobs:
build:
runs-on: ubuntu-latest # This line should be indented 4 spaces
# Correct way
jobs:
build:
runs-on: ubuntu-latest # Indented 4 spaces
Most editors (VS Code, WebStorm) can be configured to “automatically insert spaces when pressing Tab”. I recommend turning this on so you don’t have to manually type spaces every time.
Error 2: Wrong Branch Name
GitHub branch names are case-sensitive. main and Main are two different branches.
# If your branch is called main
on:
push:
branches: [Main] # Wrong, won't trigger
# Correct way
on:
push:
branches: [main] # Lowercase
If you’re not sure about the branch name, check the GitHub repository page or run git branch locally.
Error 3: job-id Spelling Error
When your workflow has multiple jobs with dependencies, the needs field must accurately reference other job ids.
jobs:
test:
runs-on: ubuntu-latest
# ...
deploy:
needs: Test # Wrong, case doesn't match
runs-on: ubuntu-latest
# Correct way
jobs:
test:
runs-on: ubuntu-latest
deploy:
needs: test # Lowercase, matching the job id above
runs-on: ubuntu-latest
Error 4: Secrets Used at Wrong Level
GitHub Secrets have two scopes: repository level and environment level. Reference them with ${{ secrets.XXX }}.
# Wrong example: secrets written in wrong position
jobs:
build:
runs-on: ubuntu-latest
env:
API_KEY: secrets.MY_KEY # Wrong, missing ${{ }}
# Correct way
jobs:
build:
runs-on: ubuntu-latest
env:
API_KEY: ${{ secrets.MY_KEY }} # Wrapped with ${{ }}
Also, Secrets are encrypted—you can’t see their actual values in logs. To confirm whether a value was passed successfully, you can print a substitute:
- name: Debug
run: echo "API_KEY is set: ${{ secrets.MY_KEY != '' }}"
This won’t leak the actual Key, but lets you confirm whether it’s empty or not.
FAQ
What fields must a GitHub Actions workflow contain?
What's the difference between push and pull_request triggers?
Should I use Tab or spaces for YAML indentation?
What's the free quota? What happens if I exceed it?
GitHub Actions vs Other CI/CD Tools
You might have used Jenkins, GitLab CI, or CircleCI. How does GitHub Actions compare?
I’ve put together a comparison table:
| Comparison | GitHub Actions | GitLab CI | Jenkins | CircleCI |
|---|---|---|---|---|
| Configuration Language | YAML | YAML | Groovy | YAML |
| Hosting | Cloud-native | Cloud/Self-hosted | Self-hosted | Cloud-native |
| Integration | GitHub native | GitLab native | Requires setup | Requires setup |
| Learning Curve | Low | Low | High | Medium |
| Free Quota | 2000 min/month (private repos) | 400 min/month | Unlimited (self-hosted) | 6000 min/month |
| Public Repositories | Unlimited | Unlimited | Unlimited | Unlimited |
GitHub Actions Advantages
1. Zero-Configuration Integration
If your code is already hosted on GitHub, using GitHub Actions is the natural choice. No need to configure webhooks, no need to maintain servers, no need to install plugins. Create a YAML file, push it, and it runs.
2. Marketplace Ecosystem
GitHub Marketplace has thousands of Actions. AWS, Azure, Google Cloud all have official Actions. Whatever functionality you need, chances are someone has already written it—just uses it.
3. Developer-Friendly Free Quota
Unlimited use for public repositories, 2000 minutes per month for private repositories. For personal projects or small teams, it’s basically sufficient.
When Not to Choose GitHub Actions?
1. Your Code Isn’t on GitHub
If you use GitLab or Bitbucket, use their built-in CI/CD. While GitHub Actions can be triggered via webhooks, going through that hassle isn’t as good as using the native solution.
2. You Need Complete Control Over the Runtime Environment
GitHub Actions runner environments are fixed (Ubuntu/Windows/macOS). You can’t install your own software or configure special environments. In this case, Jenkins + self-hosted runner is more flexible.
3. You Have Extreme Security Requirements
GitHub Actions runners are GitHub-hosted virtual machines. If your code involves highly sensitive information, you might need to build your own CI system. Though GitHub also supports self-hosted runners, which can be a middle-ground solution.
My Recommendation
For most individual developers and small teams:
- Code on GitHub → Choose GitHub Actions
- Code on GitLab → Choose GitLab CI
- Need high customization → Jenkins or self-hosted runner
There’s no absolute best solution—what fits you is what’s best.
Summary
By now, you should have a solid understanding of how GitHub Actions YAML workflows work.
Key takeaways:
- Four core fields:
namefor naming,onfor triggers,jobsfor defining tasks,stepsfor execution steps - Eight common triggers: most used are
push,pull_request, andschedule - One copy-paste template: pull code → setup environment → install dependencies → run tests
- Four common pitfalls: indentation, branch names, job-id, and Secrets
Next steps:
- Create your first workflow in your project (just copy the template from this article and adapt it to your project configuration)
- Read the follow-up article in this series, “GitHub Actions Caching Strategies: Speed Up CI/CD Pipelines by 5x”, to learn how to make workflows run faster
- Browse GitHub Marketplace to see what useful Actions you can use directly
Once you taste the sweetness of automation, there’s no going back.
5 min read · Published on: Apr 10, 2026 · Modified on: Apr 11, 2026
Related Posts
Nginx Performance Tuning: gzip, Caching, and Connection Pool Configuration
Nginx Performance Tuning: gzip, Caching, and Connection Pool Configuration
GitHub Actions Getting Started: YAML Workflow Basics and Trigger Configuration
GitHub Actions Getting Started: YAML Workflow Basics and Trigger Configuration
Docker Compose Multi-Service Orchestration: One-Command Local Development Setup

Comments
Sign in with GitHub to leave a comment