{"id":60,"date":"2025-12-16T12:00:00","date_gmt":"2025-12-16T12:00:00","guid":{"rendered":"https:\/\/rezachegini.com\/?p=60"},"modified":"2026-10-30T06:45:06","modified_gmt":"2026-10-30T06:45:06","slug":"github-actions-fundamentals-from-zero-to-your-first-pipeline","status":"publish","type":"post","link":"https:\/\/rezachegini.com\/?p=60","title":{"rendered":"GitHub Actions Fundamentals: From Zero to Your First Pipeline"},"content":{"rendered":"<p class=\"wp-block-paragraph\">After working with GitHub Actions in my home lab, I want to share what I learned about the basics. This is the stuff I wish someone explained to me when I started.<\/p>\n<h3 class=\"wp-block-heading\">What is GitHub Actions?<\/h3>\n<p class=\"wp-block-paragraph\">GitHub Actions is a CI\/CD tool built into GitHub. You write YAML files that tell GitHub what to do when something happens in your repository like when you push code, open a PR, or click a button.<\/p>\n<h3 class=\"wp-block-heading\">The Building Blocks<\/h3>\n<p class=\"wp-block-paragraph\">Every GitHub Actions workflow has 3 main parts:<\/p>\n<h3 class=\"wp-block-heading\">1. Workflows<\/h3>\n<p class=\"wp-block-paragraph\">A workflow is a YAML file stored in .github\/workflows\/ in your repository. Here&#8217;s my first workflow:<\/p>\n<pre class=\"wp-block-code\"><code>name: 01 - Building Blocks\non: push\n\njobs:\n  echo-hello:\n    runs-on: ubuntu-latest\n    steps:\n      - name: say hello\n        run: echo \"Hello World!\"<\/code><\/pre>\n<p class=\"wp-block-paragraph\">This workflow runs every time I push code to any branch.<\/p>\n<h3 class=\"wp-block-heading\">2. Jobs<\/h3>\n<p class=\"wp-block-paragraph\">Jobs are groups of steps that run on the same runner. By default, jobs run in parallel, but you can make them run one after another.<\/p>\n<p class=\"wp-block-paragraph\">Here&#8217;s something I learned the hard way if one step fails, the rest of the steps in that job won&#8217;t run:<\/p>\n<pre class=\"wp-block-code\"><code>jobs:\n  echo-goodbye:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Failed Step\n        run: |\n          echo \"I will fail\"\n          exit 1\n      - name: say goodbye\n        run: echo \"Good bye\"  # This never runs!<\/code><\/pre>\n<p class=\"wp-block-paragraph\">The &#8220;say goodbye&#8221; step never executes because the previous step exits with code 1.<\/p>\n<h3 class=\"wp-block-heading\">3. Steps<\/h3>\n<p class=\"wp-block-paragraph\">Steps are individual tasks in a job. They can run commands or use pre-built actions from the marketplace.<\/p>\n<pre class=\"wp-block-code\"><code>steps:\n  - name: Say hello\n    run: echo \"Hello, world!\"<\/code><\/pre>\n<h3 class=\"wp-block-heading\">Workflow Events: When Should Things Run?<\/h3>\n<p class=\"wp-block-paragraph\">The on: field tells GitHub when to trigger your workflow. I use three main types:<\/p>\n<h3 class=\"wp-block-heading\">Push Events<\/h3>\n<p class=\"wp-block-paragraph\">Runs when you push code:<\/p>\n<pre class=\"wp-block-code\"><code>on: push<\/code><\/pre>\n<h3 class=\"wp-block-heading\">Pull Request Events<\/h3>\n<p class=\"wp-block-paragraph\">Runs when someone opens or updates a PR:<\/p>\n<pre class=\"wp-block-code\"><code>on: pull_request<\/code><\/pre>\n<h3 class=\"wp-block-heading\">Manual Trigger<\/h3>\n<p class=\"wp-block-paragraph\">This is super useful for testing. You get a &#8220;Run workflow&#8221; button in GitHub UI:<\/p>\n<pre class=\"wp-block-code\"><code>on: workflow_dispatch<\/code><\/pre>\n<h3 class=\"wp-block-heading\">Runners: Where Does Code Run?<\/h3>\n<p class=\"wp-block-paragraph\">Runners are the machines that execute your jobs. GitHub provides three types:<\/p>\n<pre class=\"wp-block-code\"><code>jobs:\n  ubuntu-echo:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Show OS\n        run: |\n          echo \"This job is running on an Ubuntu runner.\"\n          echo \"Runner OS: $RUNNER_OS\"\n  windows-echo:\n    runs-on: windows-latest\n    steps:\n      - name: Show OS\n        shell: bash  # Important for Windows!\n        run: |\n          echo \"This job is running on a Windows runner.\"\n          echo \"Runner OS: $RUNNER_OS\"\n  mac-echo:\n    runs-on: macos-latest\n    steps:\n      - name: Show OS\n        run: |\n          echo \"This job is running on a MacOS runner.\"\n          echo \"Runner OS: $RUNNER_OS\"<\/code><\/pre>\n<p class=\"wp-block-paragraph\"><strong>Important:<\/strong> On Windows runners, you need to specify shell: bash if you want to use bash commands. Otherwise it uses PowerShell by default.<\/p>\n<h3 class=\"wp-block-heading\">Using Pre-built Actions<\/h3>\n<p class=\"wp-block-paragraph\">You don&#8217;t have to write everything from scratch. GitHub Marketplace has thousands of actions. Here&#8217;s a real example from my React app workflow:<\/p>\n<pre class=\"wp-block-code\"><code>jobs:\n  build:\n    runs-on: ubuntu-latest\n    defaults:\n      run:\n        working-directory: 04-using-actions\/react-app\n    steps:\n      - name: Checkout Code\n        uses: actions\/checkout@v4\n      - name: Setup Node\n        uses: actions\/setup-node@v4\n        with:\n          node-version: '20.x'\n      - name: Install Dependencies\n        run: npm ci\n      - name: Run Unit Tests\n        run: npm run test<\/code><\/pre>\n<p class=\"wp-block-paragraph\">The uses: keyword tells GitHub to run an action from the marketplace. The with: section passes parameters to that action.<\/p>\n<h3 class=\"wp-block-heading\">Context: Getting Information About Your Workflow<\/h3>\n<p class=\"wp-block-paragraph\">GitHub gives you a lot of information through the github context. You can access it using ${{ }} syntax:<\/p>\n<pre class=\"wp-block-code\"><code>steps:\n  - name: Display information\n    run: |\n      echo \"Event Name: ${{ github.event_name }}\"\n      echo \"Ref: ${{ github.ref }}\"\n      echo \"SHA: ${{ github.sha }}\"\n      echo \"Actor: ${{ github.actor }}\"\n      echo \"Workflow: ${{ github.workflow }}\"\n      echo \"Run ID: ${{ github.run_id }}\"\n      echo \"Run number: ${{ github.run_number }}\"<\/code><\/pre>\n<p class=\"wp-block-paragraph\">This is useful for debugging or creating unique identifiers for your builds.<\/p>\n<h3 class=\"wp-block-heading\">What I Learned<\/h3>\n<ol class=\"wp-block-list\"><li><strong>Start small:<\/strong> My first workflow was just echo commands. Then I added real steps one by one.<\/li><li><strong>Use workflow_dispatch:<\/strong> Adding manual triggers makes testing so much easier. You don&#8217;t have to push code just to test your workflow.<\/li><li><strong>Check the logs:<\/strong> When something fails, GitHub shows you exactly which step failed and why. The logs are your best friend.<\/li><li><strong>Runners cost money:<\/strong> GitHub gives you free minutes, but they run out. Ubuntu runners are cheapest. Windows and macOS cost more.<\/li><li><strong>YAML is sensitive:<\/strong> Indentation matters. A lot. One extra space and your workflow won&#8217;t work.<\/li><\/ol>\n<h3 class=\"wp-block-heading\">My Setup<\/h3>\n<p class=\"wp-block-paragraph\">I&#8217;m running these workflows in my home lab where I practice DevOps concepts. All my code is on GitHub, and I use these workflows to automate testing and deployment.<\/p>","protected":false},"excerpt":{"rendered":"<p>After working with GitHub Actions in my home lab, I want to share what I learned about the basics. This is the&hellip;<\/p>\n","protected":false},"author":2,"featured_media":62,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_rezaops_part":"1","_rezaops_linkedin_url":"https:\/\/www.linkedin.com\/pulse\/github-actions-fundamentals-from-zero-your-first-pipeline-chegini-pmf7c\/","footnotes":""},"categories":[27,10],"tags":[30,8,29,31,6],"series":[28],"class_list":["post-60","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ci-cd","category-devops","tag-ci-cd","tag-devops","tag-github-actions","tag-workflows","tag-yaml","series-github-actions"],"jetpack_featured_media_url":"https:\/\/rezachegini.com\/wp-content\/uploads\/2026\/10\/github-actions-fundamentals-cover.png","_links":{"self":[{"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/posts\/60","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/rezachegini.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=60"}],"version-history":[{"count":1,"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/posts\/60\/revisions"}],"predecessor-version":[{"id":61,"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/posts\/60\/revisions\/61"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/media\/62"}],"wp:attachment":[{"href":"https:\/\/rezachegini.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=60"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rezachegini.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=60"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rezachegini.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=60"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/rezachegini.com\/index.php?rest_route=%2Fwp%2Fv2%2Fseries&post=60"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}