Understanding GitHub Actions: Core Concepts and Fundamentals

Understanding GitHub Actions: Core Concepts and Fundamentals

Introduction

Hey there everyone 👋! Let’s talk about scale for a moment. GitHub Actions processes 10.54+ billion CPU minutes every year - and that’s just for public repositories. With over 518 million public and private repositories on GitHub, the true scale of automation happening behind the scenes is staggering. We’re looking at one of the largest continuous integration platforms on the planet.

I’m excited to continue our journey through modern software development. In the previous articles, we explored the fundamentals of Git (Part 1) and learned how to set up our first GitHub repository (Part 2). Today, we’re diving into GitHub Actions - but instead of just talking about it, we’re going to build something together.

Here’s what you’ll accomplish in this article:

  • Understand the core components: workflows, jobs, steps, and runners
  • Build your first “Hello World” automation
  • Watch it run in real-time on GitHub’s infrastructure
  • Learn to troubleshoot when things go wrong

By the end, you’ll have hands-on experience with the same automation platform processing those billions of minutes we talked about.

Ready to level up how you work with code? Let’s get building.

What is GitHub Actions?

GitHub Actions is a sophisticated automation platform built directly into GitHub. It’s designed to eliminate the repetitive, error-prone tasks that eat up developer time - running tests, building applications, deploying code to production servers. No more forgetting a crucial deployment step at 3 AM. No more typos in your build commands. No more “works on my machine” mysteries. GitHub Actions catches problems before they reach your users, consistently and reliably.

But here’s the thing: automation isn’t just about going faster. It’s about fundamentally shifting where your energy goes as a developer.

Every time we manually run tests or deploy code, we’re opening the door to mistakes and wasted time. GitHub Actions moves the toil - the repetitive execution of tasks - onto machines. This frees up your brainpower for the creative work: design, problem-solving, innovation. It’s an enabler that lets you focus on writing code and building cool features without getting bogged down in repetitive mechanics.

Think of it this way: would you rather spend 30 minutes manually running tests, or 30 minutes designing a clever solution to a challenging problem? That’s the real power of automation.

How GitHub Actions Works

Now let’s trace the journey of your code - from the moment you push it to GitHub all the way through testing and deployment. GitHub Actions orchestrates this journey like a well-rehearsed symphony.

GitHub Actions Workflow Pipeline Figure 1: A visual representation of a typical GitHub Actions workflow, from code commit to deployment.

Think of GitHub Actions as following a recipe - but instead of ingredients and cooking steps, you have events that trigger the recipe and jobs that define the work to be done.

Let’s follow what happens when you push code:

1. The Event (Your Alarm Bell)

First, an event occurs - in this case, you pushing code. Events are the alarm bells that wake up GitHub Actions. Other common triggers include:

  • Someone creating a pull request
  • A scheduled time (like running tests every night)
  • Manual triggers (clicking a button on GitHub)

2. GitHub Looks for Instructions

When an event happens, GitHub doesn’t randomly start running things. It looks for workflow files in a very specific folder: .github/workflows/. These files contain your automation recipes. GitHub checks: “Is there a workflow waiting for this specific event?” If yes, it springs into action.

3. Jobs: Your Mini-Missions

Inside your workflow file, you define one or more jobs. Think of these as mini-missions, each with its own set of tasks. The powerful part? These jobs can run:

  • In parallel: Multiple jobs running simultaneously (saving time)
  • Sequentially: One after another (when order matters)

4. Steps: Where the Real Work Happens

Within each job, you define steps - the individual tasks that execute actual commands:

  • Checking out your code from the repository
  • Installing dependencies (like npm packages for JavaScript)
  • Running your test suite
  • Building your application
  • Deploying to a server

5. Runners: The Workhorses

All these steps need somewhere to run - they’re not executing on your laptop! This is where runners come in. A runner is a server (that GitHub provides for free!) where your jobs execute. When you see runs-on: ubuntu-latest in a workflow, you’re saying “Hey GitHub, give me a Linux computer to run this job.”

6. Real-Time Feedback

While everything runs, you’re not flying blind. GitHub Actions provides real-time feedback:

  • Watch each step execute in the logs
  • See the output of your commands
  • Get visual cues: ✅ green checkmarks for success, ❌ red X’s for problems

The Magic: Consistency and Reliability

Once set up, this entire process runs the same way, every single time that event occurs. No more:

  • Forgetting to run tests before deploying
  • Manual deployment typos at 3 AM
  • “But it worked on my machine!” mysteries

It’s automation that brings reliability. Every push triggers the same checks, every deployment follows the same steps, every time.

Now let’s create your first workflow and watch these concepts come to life!

Your First GitHub Actions Workflow

Now that you understand how GitHub Actions works, let’s shift gears. The best way to really get this is to build something yourself. Nothing beats hands-on experience!

We’re going to create a simple “Hello World” workflow. It won’t deploy any applications or run complex tests - but it will show you exactly how all these pieces fit together. Ready to see that green checkmark?

Setting Up

Before we dive in, let’s make sure you have everything ready:

Prerequisites:

  • A GitHub repository (you can use my-first-project from Part 2, or any repo you have)
  • A text editor like VS Code
  • About 5 minutes of your time

If you’re using the repository from Part 2:

git clone https://github.com/ahmedmuhi/my-first-project.git
cd my-first-project

Open the project in VS Code by selecting File → Open Folder and choosing your project directory.

Creating the Workflow File

GitHub Actions workflows live in a very specific place. Remember when we said GitHub looks for workflow files in a special folder? Let’s create that structure:

  1. In VS Code, right-click in the Explorer panel (the left sidebar)
  2. Select “New Folder”
  3. Name it .github - the dot at the beginning is crucial!
  4. Right-click on the .github folder and create another folder inside called workflows - note it’s plural! So, workflows, NOT workflow
  5. Right-click on workflows and select “New File”
  6. Name your file main.yml - the .yml extension is required

Your full path should be: .github/workflows/main.yml

This precise structure tells GitHub exactly where to look for automation recipes.

Writing Your First Workflow

Now for the fun part! Copy and paste this YAML code into your main.yml file:

name: Hello World

on: [push]

jobs:
  say-hello:
    runs-on: ubuntu-latest
    
    steps:
      - name: Say hello
        run: echo "Hello, GitHub Actions!"
        
      - name: Tell us the time
        run: date

VS Code with workflow Figure 2: Your first workflow file in VS Code

Understanding the YAML Structure

Before we run this, let’s decode what we just wrote. YAML might look simple, but it’s very particular about one thing: indentation.

⚠️ Critical YAML Rules:

  • Use spaces, not tabs (this catches many people!)
  • Use exactly 2 spaces per indentation level
  • The hierarchy matters - indentation shows relationships

Here’s how our code translates to the concepts we learned:

name: Hello World          # Names your workflow
on: [push]                # The trigger (our alarm bell!)
jobs:                     # Container for all jobs
  say-hello:             # Our job name (2 spaces indent)
    runs-on: ubuntu-latest    # The runner (4 spaces)
    steps:               # Container for steps (4 spaces)
      - name: Say hello  # First step (6 spaces)
        run: echo "..."  # The command (8 spaces)

The indentation creates a hierarchy:

  • Steps belong to say-hello job
  • The say-hello job belongs to jobs
  • Everything belongs to the workflow

💡 VS Code Tip: If your indentation gets messy, select all your YAML and press Shift+Alt+F to auto-format!

Pushing Your Workflow to GitHub

Time to make it go! Let’s get this workflow to GitHub where it can run.

First, stage your new workflow file:

  1. Click the Source Control icon in VS Code’s sidebar (branching tree icon)
  2. You’ll see main.yml listed under “Changes”
  3. Click the + icon to stage it

Stage workflow Figure 3: Staging your workflow file

Now commit with a descriptive message:

  1. Type “Add Hello World workflow” in the message box
  2. Click the Commit button (checkmark icon)

Commit workflow Figure 4: Committing your workflow

Finally, push to GitHub:

  1. Click “Sync Changes” (the button that replaced Commit)
  2. Click “OK” when VS Code asks for confirmation

Sync to GitHub Figure 5: Pushing to GitHub

The Moment of Truth

Head over to your GitHub repository and click the Actions tab. You should see your workflow - it might already be running or just finished!

Actions tab Figure 6: Your workflow in the Actions tab

🎉 See that green checkmark? That’s what success looks like!

Click on “Add Hello World workflow” to see the details:

Workflow summary Figure 7: Workflow summary - everything you need to know about this run

What you’re seeing:

  • Trigger: The push event that started everything
  • Status: Success! (hopefully with a green checkmark)
  • Duration: How long it took (probably around 10 seconds)
  • Jobs: Our say-hello job

Diving Into the Details

Click on the say-hello job name to see what really happened:

Job steps Figure 8: Wait, we only defined two steps - why are there four?

GitHub automatically adds two extra steps:

  • Set up job: Prepares the runner environment, downloads necessary tools
  • Complete job: Cleans up afterward, uploads logs

These bookend your custom steps, ensuring everything runs smoothly.

Click on each step to expand it:

Expanded steps Figure 9: The actual commands and their output

Look closely at your custom steps:

  • Say hello: Shows both the command (echo "Hello, GitHub Actions!") and its output
  • Tell us the time: Displays the exact timestamp when the runner executed this step

You Did It! 🎉

Take a moment to appreciate what just happened:

  1. You wrote automation code in YAML
  2. GitHub detected your push and read your workflow
  3. A computer in the cloud spun up just for you
  4. Your commands executed successfully
  5. Everything was logged for you to see

That green checkmark isn’t just a symbol - it’s proof that automation just happened. You’ve officially joined the ranks of developers using GitHub Actions!

In the next section, we’ll peek under the hood to understand what that mysterious ubuntu-latest runner actually is and how GitHub made all this magic happen.

Understanding What Just Happened

Seeing that green checkmark is great, but let’s dig deeper into what really happened behind the scenes. That runs-on: ubuntu-latest line? It orchestrated something remarkable that’s worth understanding.

Demystifying Runners

A runner is simply a server whose job is to execute your workflow steps. But here’s what makes it special: it’s a temporary computer that exists just for your job.

When your workflow triggered, here’s what GitHub did in mere seconds:

  1. Saw your runs-on: ubuntu-latest instruction
  2. Created a brand new virtual machine running Ubuntu Linux
  3. Executed all your workflow steps on this fresh machine
  4. Destroyed the entire virtual machine - wiped clean

This isn’t just efficient - it’s crucial for consistency. Every workflow run gets a perfectly clean environment. No leftover files from previous runs, no conflicting software versions, no “but it worked last time” mysteries.

Why “ubuntu-latest”?

Let’s decode what you’re actually asking for with this line:

Ubuntu = A specific flavor of Linux, one of the most popular and widely-used distributions Latest = The most recent stable version GitHub supports

But what makes this so useful? When GitHub spins up an Ubuntu runner, it comes preloaded with a treasure trove of development tools:

  • Git itself (obviously!)
  • Programming runtimes: Node.js, Python, Ruby, Java, and more
  • Package managers: npm, pip, apt-get
  • Build tools: make, gcc, maven
  • Common utilities: curl, wget, jq, zip

It’s like ordering a computer that arrives with everything a developer might need already installed. No setup required!

GitHub-hosted vs Self-hosted

What we used is called a GitHub-hosted runner. GitHub manages everything:

  • The infrastructure
  • The virtual machines
  • All the pre-installed software
  • The cleanup afterward

It’s free for public repositories and includes generous limits for private ones. Perfect for most needs!

The alternative? Self-hosted runners - where you provide your own computer. Teams use these when they need:

  • Specific hardware (like GPUs for machine learning)
  • To run behind a company firewall
  • An operating system GitHub doesn’t offer
  • To avoid usage limits

But for learning and most projects? GitHub-hosted is the way to go.

The Hidden Steps: Setup and Cleanup

Remember seeing four steps when you only defined two? Let’s explore what GitHub does automatically:

Set up job (The Preparation)

When you expand this step in the logs, you’re seeing GitHub prepare your runner. It’s like watching a chef set up their kitchen before cooking:

Current runner version: 2.317.0
Operating System: Ubuntu 24.04.2 LTS
Runner Image: ubuntu-24.04
Runner Image Provisioner: 20250711.363

This shows you:

  • The exact operating system version
  • Pre-installed tool versions
  • The runner software itself
  • Environment variables available

This transparency helps when debugging - you know exactly what environment your code ran in.

Complete job (The Cleanup)

After your steps finish, GitHub:

  • Uploads all the logs you’re reading
  • Cleans up temporary files
  • Reports success/failure back to GitHub
  • Tears down the virtual machine

This cleanup is why you can run thousands of workflows without worrying about disk space or leftover files.

The Bigger Picture

What seems like a simple two-step workflow actually involves sophisticated orchestration:

  1. Request: Your push triggers a workflow
  2. Provision: GitHub finds available compute resources
  3. Initialize: A fresh Ubuntu VM spins up in seconds
  4. Prepare: Development tools are verified and ready
  5. Execute: Your actual steps run
  6. Report: Results stream back in real-time
  7. Cleanup: Everything is torn down, logs preserved

This same pattern scales from your “Hello World” to deployments serving millions of users. The infrastructure that just ran your echo command is the same one processing those 10.54 billion minutes of computation we talked about.

Next, we’ll look at common issues you might encounter. But first, appreciate what you’ve learned: you now understand the infrastructure powering millions of workflows worldwide!

When Things Go Wrong (And They Will!)

We’ve been enjoying those green checkmarks, but let’s be real - eventually, you’ll see a red X. It happens to everyone! The good news? GitHub Actions makes failures almost as informative as successes.

What Failure Looks Like

When a step fails, you’ll see:

  • Red X (❌) instead of a green checkmark
  • The workflow stops at that step (by default)
  • Subsequent steps in that job won’t even run
  • Clear error messages in the logs

This “fail fast” behavior is intentional - why run tests if the build failed? Why deploy if tests failed?

The logs become your best friend here. They show:

  • The exact error message from the failed command
  • Whether it’s “command not found”, “permission denied”, or test output
  • Which line failed in multi-line scripts
  • The precise moment things went wrong

No more guessing games - it’s all laid out clearly.

The Power of Infrastructure as Code

Before we dive into specific issues, let’s appreciate what we’ve actually built. That YAML file isn’t just a script - it’s Infrastructure as Code (IaC):

name: Hello World        # Defines the process
on: [push]              # Defines the trigger
runs-on: ubuntu-latest  # Defines the environment
steps:                  # Defines the work

This means:

  • Your workflow is version-controlled alongside your code
  • It’s perfectly reproducible - anyone can run the exact same process
  • No more “worked on my machine” mysteries
  • The environment is created fresh from this definition every time

This same pattern - defining infrastructure through code - scales from our simple “Hello World” to complex multi-stage deployment pipelines serving millions.

Your Troubleshooting First Aid Kit

Now, let’s tackle the most common issues you’ll encounter:

Issue 1: “My Workflow Didn’t Even Run!”

You push code, check the Actions tab… nothing. Frustrating!

Almost always the culprit: file path or name

Must be exactly: .github/workflows/main.yml

Common mistakes:

  • Missing dot: github/workflows/main.yml
  • Missing ‘s’: .github/workflow/main.yml
  • Wrong extension: .github/workflows/main.yaml (though .yaml works too)

First check: Is your file in exactly the right place with exactly the right name?

Second check: Branch triggers

on:
  push:
    branches: [main]  # Only triggers on main branch

If you have branch filters, ensure you’re pushing to the right branch!

Issue 2: The Dreaded YAML Indentation Error

YAML is notoriously picky about indentation. It uses spaces to understand structure - steps belong to jobs, jobs belong to workflows.

Common mistakes:

# ❌ Mixing tabs and spaces (invisible but deadly!)
jobs:
  my-job:
    runs-on: ubuntu-latest
	steps:  # This line has a tab!

# ❌ Inconsistent indentation
jobs:
  my-job:
    runs-on: ubuntu-latest
   steps:  # Only 3 spaces instead of 4

The fix:

  • Always use spaces, never tabs
  • Exactly 2 spaces per indent level
  • Configure your editor to show whitespace
  • In VS Code: Shift+Alt+F to auto-format

Issue 3: Permission Denied on Scripts

Trying to run a script from your repo?

- name: Run my script
  run: ./myscript.sh  # Permission denied!

Linux runners need execute permissions. Fix it locally first:

chmod +x myscript.sh
git add myscript.sh
git commit -m "Make script executable"
git push

Issue 4: “Resource Not Accessible by Integration”

Your workflow tries to do something outside standard permissions (like modifying repository settings). This is a deeper topic for later, but know it exists!

Your Debugging Checklist

When you see that red X:

  1. Click through to the error

    • Actions tab → Failed workflow → Failed job → Failed step
    • Read the actual error message
  2. Look for keywords

    • “command not found” = missing software
    • “permission denied” = file permissions issue
    • “syntax error” = likely YAML problem
  3. Try locally first

    # If this failed in Actions:
    npm run build
    
    # Try it on your machine first
  4. Add debug output

    - name: Debug info
      run: |
        echo "Where am I? $(pwd)"
        echo "What files exist? $(ls -la)"
        echo "What's installed? $(which node)"
  5. Check raw logs

    • Click the gear icon → “View raw logs”
    • Sometimes shows details the formatted view hides

Remember: Fresh Environment Every Time

This catches many people:

  • No files from previous runs
  • No globally installed tools from your machine
  • No environment variables you didn’t explicitly set

This is a feature, not a bug! It forces your workflows to be self-contained and reproducible. If it works in Actions, it’ll work consistently - no hidden dependencies!

You’re Not Alone

Every developer has:

  • Fought with YAML indentation
  • Forgotten the dot in .github
  • Wondered why their script won’t run
  • Seen “command not found” for tools on their machine

The GitHub Actions documentation, Stack Overflow, and the GitHub Community Forum are your friends. And remember - those error messages are actually quite helpful once you know how to read them!

Ready to build something more complex? Let’s move forward!

What’s Next

Wow, what a journey! Let’s take a moment to appreciate everything you’ve accomplished in this article. You’ve gone from knowing nothing about GitHub Actions to successfully creating and running your own automated workflow. That’s huge!

What You’ve Learned

You now understand:

  • What GitHub Actions is - That tireless automation assistant processing 10.54 billion minutes of compute yearly
  • How workflows work - From triggers to runners to completed jobs
  • The key components - Workflows, events, jobs, steps, and how they fit together
  • How to create workflows - You built your first one from scratch!
  • How to read the logs - No more mystery about what’s happening behind the scenes
  • What runners are - Those magical computers in the cloud that run your code
  • How to troubleshoot - You have your debugging toolkit ready

But here’s the best part: everything you learned today scales up. The same concepts that power your “Hello World” workflow are used by teams deploying applications millions of people use every day. You’re now part of the automation revolution!

Coming Up Next: Taking Control with Triggers

In our next article, we’ll explore one of the most powerful features of GitHub Actions: triggers. Right now, your workflow runs every time you push code. Simple, but what if you want more control?

In Part 4, you’ll learn how to:

  • Run workflows only when specific files change - Why rebuild everything if only the README changed?
  • Trigger workflows on pull requests - Automated code review before merging
  • Set up different workflows for different branches - Test on feature branches, deploy from main
  • Combine multiple triggers - Push, schedule, or manual - mix and match!

Imagine having workflows that automatically run tests when someone opens a pull request, or only deploy when changes are made to your main branch. That’s the power of triggers, and that’s what we’ll build together next time!

Your Homework (Optional but Fun!)

The best part about GitHub Actions? You can’t break anything! Those runners are isolated and temporary. So experiment freely:

Try these challenges:

  1. Modify your workflow - Change the echo message to something personal
  2. Add more steps - Maybe print the current directory with pwd or list files with ls
  3. Break something on purpose - Try run: blahblahblah and watch the red X appear
  4. Create a second workflow - Name it test.yml and watch both workflows run
  5. Mess up the indentation - See what YAML errors look like firsthand

Learn by doing. Learn by breaking. GitHub Actions is the perfect playground for learning automation safely.

Final Thoughts

You’ve taken your first steps into a larger world. GitHub Actions is used by millions of developers to automate everything from simple tasks to complex deployment pipelines. And now you’re one of them!

Every expert GitHub Actions user started exactly where you are now - with a simple “Hello World” workflow and a sense of curiosity. Keep that curiosity alive, keep experimenting, and most importantly, have fun automating!

Remember: that green checkmark you saw? It represents freedom from repetitive tasks, consistency in your processes, and time saved for creative work. That’s the real power of automation.

Thank you for joining me on this journey. I can’t wait to see what you’ll automate next!

See you in Part 4: Understanding Push and Pull Request Triggers where we’ll take your GitHub Actions skills to the next level! 🚀