{"id":78744,"date":"2026-09-26T04:46:35","date_gmt":"2026-09-26T04:46:35","guid":{"rendered":"https:\/\/www.devopsschool.com\/blog\/?p=78744"},"modified":"2026-09-26T04:46:38","modified_gmt":"2026-09-26T04:46:38","slug":"github-actions-essentials-learn-ci-cd-in-2-hours","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/blog\/github-actions-essentials-learn-ci-cd-in-2-hours\/","title":{"rendered":"GitHub Actions Essentials \u2014 Learn CI\/CD in 2 Hours"},"content":{"rendered":"\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Audience: Beginners, developers, junior DevOps engineers, QA engineers, and students<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Level: Beginner to early-intermediate<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Duration: About 120 minutes<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Format: Learn, modify, run, break, and fix<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Goal: By the end of this tutorial, you should be able to read an unfamiliar GitHub Actions workflow, create a basic CI workflow, use common Actions features safely, and troubleshoot common failures.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Table of Contents<\/h1>\n\n\n\n<ol class=\"wp-block-list\">\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/Users\/rajesh\/Downloads\/GitHub-Actions-Essentials-2-Hour-Tutorial.md#1-how-to-use-this-tutorial\">How to Use This Tutorial<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/Users\/rajesh\/Downloads\/GitHub-Actions-Essentials-2-Hour-Tutorial.md#2-github-actions-in-10-minutes\">GitHub Actions in 10 Minutes<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/Users\/rajesh\/Downloads\/GitHub-Actions-Essentials-2-Hour-Tutorial.md#3-understanding-workflow-yaml\">Understanding Workflow YAML<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/Users\/rajesh\/Downloads\/GitHub-Actions-Essentials-2-Hour-Tutorial.md#4-events-and-triggers\">Events and Triggers<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/Users\/rajesh\/Downloads\/GitHub-Actions-Essentials-2-Hour-Tutorial.md#5-jobs-steps-and-runners\">Jobs, Steps, and Runners<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/Users\/rajesh\/Downloads\/GitHub-Actions-Essentials-2-Hour-Tutorial.md#6-using-actions\">Using Actions<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/Users\/rajesh\/Downloads\/GitHub-Actions-Essentials-2-Hour-Tutorial.md#7-variables-contexts-and-expressions\">Variables, Contexts, and Expressions<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/Users\/rajesh\/Downloads\/GitHub-Actions-Essentials-2-Hour-Tutorial.md#8-secrets-and-github_token\">Secrets and GITHUB_TOKEN<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/Users\/rajesh\/Downloads\/GitHub-Actions-Essentials-2-Hour-Tutorial.md#9-hands-on-lab-build-your-first-ci-pipeline\">Hands-On Lab: Build Your First CI Pipeline<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/Users\/rajesh\/Downloads\/GitHub-Actions-Essentials-2-Hour-Tutorial.md#10-matrix-builds\">Matrix Builds<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/Users\/rajesh\/Downloads\/GitHub-Actions-Essentials-2-Hour-Tutorial.md#11-caching\">Caching<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/Users\/rajesh\/Downloads\/GitHub-Actions-Essentials-2-Hour-Tutorial.md#12-artifacts\">Artifacts<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/Users\/rajesh\/Downloads\/GitHub-Actions-Essentials-2-Hour-Tutorial.md#13-from-ci-to-cd-and-environments\">From CI to CD and Environments<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/Users\/rajesh\/Downloads\/GitHub-Actions-Essentials-2-Hour-Tutorial.md#14-github-actions-security-5-rules\">GitHub Actions Security: 5 Rules<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/Users\/rajesh\/Downloads\/GitHub-Actions-Essentials-2-Hour-Tutorial.md#15-troubleshooting-the-7-step-method\">Troubleshooting: The 7-Step Method<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/Users\/rajesh\/Downloads\/GitHub-Actions-Essentials-2-Hour-Tutorial.md#16-final-hands-on-challenge\">Final Hands-On Challenge<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/Users\/rajesh\/Downloads\/GitHub-Actions-Essentials-2-Hour-Tutorial.md#17-github-actions-essential-cheat-sheet\">GitHub Actions Essential Cheat Sheet<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/Users\/rajesh\/Downloads\/GitHub-Actions-Essentials-2-Hour-Tutorial.md#18-15-question-knowledge-check\">15-Question Knowledge Check<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/file+.vscode-resource.vscode-cdn.net\/Users\/rajesh\/Downloads\/GitHub-Actions-Essentials-2-Hour-Tutorial.md#19-what-to-learn-next\">What to Learn Next<\/a><\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">1. How to Use This Tutorial<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">This tutorial is intentionally small enough to complete in about two hours.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The objective is not to memorize every GitHub Actions feature. The objective is to build a strong mental model and become productive with the features that appear in most day-to-day CI workflows.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You will repeatedly modify one workflow instead of studying many unrelated examples.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The learning sequence is:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">Understand\n   |\n   v\nWrite\n   |\n   v\nRun\n   |\n   v\nInspect\n   |\n   v\n<span class=\"hljs-keyword\">Break<\/span>\n   |\n   v\nFix\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">1.1 Suggested timing<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-right\" data-align=\"right\">Time<\/th><th class=\"has-text-align-left\" data-align=\"left\">Module<\/th><th class=\"has-text-align-left\" data-align=\"left\">Main outcome<\/th><\/tr><\/thead><tbody><tr><td class=\"has-text-align-right\" data-align=\"right\">0-10 min<\/td><td>GitHub Actions mental model<\/td><td>Understand the platform<\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">10-25 min<\/td><td>Workflow YAML<\/td><td>Read and write workflow structure<\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">25-38 min<\/td><td>Triggers, jobs, and steps<\/td><td>Control when and how work runs<\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">38-48 min<\/td><td>Runners and Actions<\/td><td>Understand where work runs and how Actions are reused<\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">48-60 min<\/td><td>Variables, contexts, expressions, secrets<\/td><td>Use runtime data safely<\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">60-78 min<\/td><td>Main CI lab<\/td><td>Build a real CI pipeline<\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">78-90 min<\/td><td>Matrix, cache, artifacts<\/td><td>Add important CI capabilities<\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">90-101 min<\/td><td>CI to CD<\/td><td>Understand deployment flow<\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">101-110 min<\/td><td>Security<\/td><td>Learn the five rules that matter most<\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">110-117 min<\/td><td>Troubleshooting<\/td><td>Debug failures systematically<\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">117-120 min<\/td><td>Challenge<\/td><td>Apply what you learned<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">1.2 Prerequisites<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You should have:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>a GitHub account;<\/li>\n\n\n\n<li>access to a repository where you can create branches and commits;<\/li>\n\n\n\n<li>basic Git knowledge;<\/li>\n\n\n\n<li>basic command-line knowledge;<\/li>\n\n\n\n<li>optional Node.js knowledge for the hands-on lab.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">You do not need prior GitHub Actions experience.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">1.3 What this tutorial intentionally does not cover<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The following topics are important, but they belong in a longer course:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>custom JavaScript actions;<\/li>\n\n\n\n<li>custom Docker actions;<\/li>\n\n\n\n<li>advanced reusable workflow architecture;<\/li>\n\n\n\n<li>OpenID Connect cloud federation implementation;<\/li>\n\n\n\n<li>Kubernetes deployment;<\/li>\n\n\n\n<li>Terraform and infrastructure-as-code workflows;<\/li>\n\n\n\n<li>Actions Runner Controller;<\/li>\n\n\n\n<li>enterprise governance;<\/li>\n\n\n\n<li>monorepo optimization;<\/li>\n\n\n\n<li>artifact attestations;<\/li>\n\n\n\n<li>advanced release engineering;<\/li>\n\n\n\n<li>organization-wide workflow policy.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">2. GitHub Actions in 10 Minutes<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">2.1 What is GitHub Actions?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">GitHub Actions is GitHub&#8217;s automation platform.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It listens for events such as:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>code being pushed;<\/li>\n\n\n\n<li>a pull request being opened;<\/li>\n\n\n\n<li>a pull request being updated;<\/li>\n\n\n\n<li>a release being created;<\/li>\n\n\n\n<li>a user manually starting a workflow;<\/li>\n\n\n\n<li>a schedule being reached.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">When an event matches a workflow definition, GitHub starts a workflow run.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The most common use is CI\/CD.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Typical CI tasks include:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>checkout source code;<\/li>\n\n\n\n<li>install dependencies;<\/li>\n\n\n\n<li>lint code;<\/li>\n\n\n\n<li>run unit tests;<\/li>\n\n\n\n<li>run integration tests;<\/li>\n\n\n\n<li>compile or build software;<\/li>\n\n\n\n<li>create packages;<\/li>\n\n\n\n<li>upload build artifacts.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Typical CD tasks include:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>publish a package;<\/li>\n\n\n\n<li>push a container image;<\/li>\n\n\n\n<li>deploy to staging;<\/li>\n\n\n\n<li>deploy to production;<\/li>\n\n\n\n<li>run smoke tests;<\/li>\n\n\n\n<li>roll back a deployment.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">2.2 The core mental model<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Remember this sequence:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Event\n  |\n  v\nWorkflow\n  |\n  v\nJob\n  |\n  v\nRunner\n  |\n  v\nSteps\n  |\n  v\nActions \/ Commands\n  |\n  v\nResult\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">That single flow explains most of GitHub Actions.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2.3 Core terms<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Term<\/th><th class=\"has-text-align-left\" data-align=\"left\">Simple meaning<\/th><\/tr><\/thead><tbody><tr><td>Event<\/td><td>Something happened in or around GitHub<\/td><\/tr><tr><td>Workflow<\/td><td>Automation defined in YAML<\/td><\/tr><tr><td>Workflow run<\/td><td>One execution of a workflow<\/td><\/tr><tr><td>Job<\/td><td>A unit of work scheduled to a runner<\/td><\/tr><tr><td>Runner<\/td><td>The machine that executes a job<\/td><\/tr><tr><td>Step<\/td><td>A task inside a job<\/td><\/tr><tr><td>Action<\/td><td>Reusable automation executed by a step<\/td><\/tr><tr><td>Command<\/td><td>A shell command executed by a step<\/td><\/tr><tr><td>Artifact<\/td><td>A file kept from a workflow run<\/td><\/tr><tr><td>Cache<\/td><td>Reusable data intended to make future runs faster<\/td><\/tr><tr><td>Secret<\/td><td>Protected sensitive configuration<\/td><\/tr><tr><td>Variable<\/td><td>Non-sensitive configuration<\/td><\/tr><tr><td>Context<\/td><td>Structured runtime information from GitHub<\/td><\/tr><tr><td>Expression<\/td><td>GitHub&#8217;s&nbsp;<code>${{ }}<\/code>&nbsp;evaluation syntax<\/td><\/tr><tr><td>Environment<\/td><td>A named deployment target such as staging or production<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">2.4 CI, continuous delivery, and continuous deployment<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">These terms are related but different.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Practice<\/th><th class=\"has-text-align-left\" data-align=\"left\">Question it answers<\/th><th class=\"has-text-align-left\" data-align=\"left\">Example<\/th><\/tr><\/thead><tbody><tr><td>Continuous Integration<\/td><td>Is this code safe to merge?<\/td><td>Build, lint, test<\/td><\/tr><tr><td>Continuous Delivery<\/td><td>Is this release ready to deploy?<\/td><td>Build, package, stage, wait for approval<\/td><\/tr><tr><td>Continuous Deployment<\/td><td>Can validated changes deploy automatically?<\/td><td>CI plus automatic production deployment<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">A beginner should first learn CI. Deployment should come after you understand workflow execution and security.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2.5 Your first workflow<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Create this file:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">.github\/workflows\/hello.yml\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Add:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">name: Hello GitHub Actions\n\non:\n  push:\n\njobs:\n  hello:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Say hello\n        run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Hello from GitHub Actions\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Commit and push it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then open:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Repository -&gt; Actions -&gt; Hello GitHub Actions\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">You should see a workflow run.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2.6 What happened?<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">You pushed a commit\n      |\n      v\nGitHub detected push\n      |\n      v\nhello.yml matched the event\n      |\n      v\nWorkflow run was created\n      |\n      v\nJob <span class=\"hljs-string\">\"hello\"<\/span> was scheduled\n      |\n      v\nUbuntu runner started\n      |\n      v\nStep executed <span class=\"hljs-keyword\">echo<\/span> command\n      |\n      v\nWorkflow completed\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">That is GitHub Actions in its simplest useful form.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">3. Understanding Workflow YAML<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">GitHub Actions workflows are YAML files stored under:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">.github\/workflows\/\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Common filenames:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">ci<\/span><span class=\"hljs-selector-class\">.yml<\/span>\n<span class=\"hljs-selector-tag\">pull-request-ci<\/span><span class=\"hljs-selector-class\">.yml<\/span>\n<span class=\"hljs-selector-tag\">release<\/span><span class=\"hljs-selector-class\">.yml<\/span>\n<span class=\"hljs-selector-tag\">deploy-production<\/span><span class=\"hljs-selector-class\">.yml<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Both&nbsp;<code>.yml<\/code>&nbsp;and&nbsp;<code>.yaml<\/code>&nbsp;work.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3.1 YAML indentation matters<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Correct:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">jobs:\n  test:\n    runs-on: ubuntu-latest\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Incorrect:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">jobs:\ntest:\n  runs-on: ubuntu-latest\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">YAML uses indentation to describe structure.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3.2 The basic workflow structure<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Study this example carefully:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">name: CI\n\non:\n  push:\n    branches: &#91;main]\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions\/checkout@v6\n\n      - name: Run a command\n        run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"CI is running\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The important keywords are:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">name\non\npermissions\njobs\nruns-on\nsteps\nuses\nrun\n<span class=\"hljs-keyword\">with<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">3.3 Workflow hierarchy<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Workflow\n|\n+-- name\n+-- on\n+-- permissions\n+-- jobs\n    |\n    +-- test\n        |\n        +-- runs-on\n        +-- steps\n            |\n            +-- checkout\n            +-- command\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">3.4&nbsp;<code>name<\/code><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The workflow display name:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http\"><span class=\"hljs-attribute\">name<\/span>: Pull Request CI\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This appears in the Actions UI.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3.5&nbsp;<code>on<\/code><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The trigger:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">on:\n  push:\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Or multiple triggers:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">on:\n  push:\n  pull_request:\n  workflow_dispatch:\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">3.6&nbsp;<code>jobs<\/code><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A workflow contains one or more jobs:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">jobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - run: npm test\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Here&nbsp;<code>test<\/code>&nbsp;is the job ID.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3.7&nbsp;<code>runs-on<\/code><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This selects the runner:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http\"><span class=\"hljs-attribute\">runs-on<\/span>: ubuntu-latest\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">3.8&nbsp;<code>steps<\/code><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Steps run in order inside one job:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">steps:\n  - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Step 1\"<\/span>\n  - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Step 2\"<\/span>\n  - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Step 3\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">3.9&nbsp;<code>uses<\/code><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Use a reusable Action:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- uses: actions\/checkout@v6\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">3.10&nbsp;<code>run<\/code><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Run a shell command:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- run: npm test\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Multiline commands:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- name: Build\n  run: |\n    npm ci\n    npm test\n    npm run build\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">3.11&nbsp;<code>with<\/code><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Pass inputs to an Action:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">- uses: actions\/setup-node@v7\n  <span class=\"hljs-attr\">with<\/span>:\n    node-version: <span class=\"hljs-string\">\"24\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">3.12 Micro-practice<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Modify your first workflow:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">name: My First CI Workflow\n\non:\n  push:\n\njobs:\n  hello:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: <span class=\"hljs-keyword\">Print<\/span> repository information\n        run: |\n          <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Repository: $GITHUB_REPOSITORY\"<\/span>\n          <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Commit: $GITHUB_SHA\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Push the change and inspect the log.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">4. Events and Triggers<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A workflow does nothing until an event matches its&nbsp;<code>on<\/code>&nbsp;configuration.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For a two-hour course, focus on three triggers:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">push\npull_request\nworkflow_dispatch\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">4.1 Push<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Run whenever code is pushed:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">on:\n  push:\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Run only when&nbsp;<code>main<\/code>&nbsp;changes:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">on:\n  push:\n    branches:\n      - main\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Run for multiple branches:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">on:\n  push:\n    branches:\n      - main\n      - develop\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">4.2 Pull request<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Run when a pull request is opened or updated:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">on:\n  pull_request:\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This is one of the most important CI triggers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Typical PR workflow:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Developer creates PR\n        |\n        v\nGitHub starts CI\n        |\n        v\nLint\n        |\n        v\nTest\n        |\n        v\nBuild\n        |\n        v\nStatus check passes or fails\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">4.3 Manual workflow<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Use&nbsp;<code>workflow_dispatch<\/code>&nbsp;when a person should start the workflow manually:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">on:\n  workflow_dispatch:\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">After pushing the workflow to the default branch, GitHub can show a &#8220;Run workflow&#8221; button in the Actions UI.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A simple manual input:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">on:\n  workflow_dispatch:\n    inputs:\n      environment:\n        description: Deployment environment\n        <span class=\"hljs-attr\">required<\/span>: <span class=\"hljs-literal\">true<\/span>\n        <span class=\"hljs-attr\">type<\/span>: choice\n        <span class=\"hljs-attr\">options<\/span>:\n          - staging\n          - production\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Read the selected value with:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">${{ inputs.environment }}\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">4.4 Multiple triggers<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A common CI workflow:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">on<\/span>:\n  <span class=\"hljs-selector-tag\">push<\/span>:\n    <span class=\"hljs-selector-tag\">branches<\/span>: <span class=\"hljs-selector-attr\">&#91;main]<\/span>\n  <span class=\"hljs-selector-tag\">pull_request<\/span>:\n  <span class=\"hljs-selector-tag\">workflow_dispatch<\/span>:\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Meaning:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Push to main --------+\n                     |\nPull request --------+--&gt; Run workflow\n                     |\nManual execution ----+\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">4.5 Path filters<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you only want CI when application code changes:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">on:\n  pull_request:\n    paths:\n      - <span class=\"hljs-string\">\"src\/**\"<\/span>\n      - <span class=\"hljs-string\">\"package.json\"<\/span>\n      - <span class=\"hljs-string\">\"package-lock.json\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Use path filters carefully. A workflow skipped because of a path filter can interact with required checks in ways that need deliberate repository design.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4.6 Trigger practice<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Change your workflow so that it runs:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>on pull requests;<\/li>\n\n\n\n<li>on pushes to\u00a0<code>main<\/code>;<\/li>\n\n\n\n<li>manually.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Solution:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">on<\/span>:\n  <span class=\"hljs-selector-tag\">push<\/span>:\n    <span class=\"hljs-selector-tag\">branches<\/span>: <span class=\"hljs-selector-attr\">&#91;main]<\/span>\n  <span class=\"hljs-selector-tag\">pull_request<\/span>:\n  <span class=\"hljs-selector-tag\">workflow_dispatch<\/span>:\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">5. Jobs, Steps, and Runners<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">This is the most important execution concept after triggers.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">5.1 Jobs<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A job is an independently scheduled unit of work.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">jobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Linting\"<\/span>\n\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Testing\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">By default, these jobs can run in parallel.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"> <code>            +--&gt; Job: lint --&gt; Runner A\nWorkflow ----+\n             +--&gt; Job: test --&gt; Runner B\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">5.2 Steps<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Steps inside a job run sequentially.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">steps:\n  - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"1\"<\/span>\n  - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"2\"<\/span>\n  - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"3\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Execution:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Step 1\n  |\n  v\nStep 2\n  |\n  v\nStep 3\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">5.3 Jobs do not automatically share files<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Each job normally has its own runner environment.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is important:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Job A filesystem != Job B filesystem\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">If one job creates a file and another job needs it, use an artifact or another explicit data-transfer mechanism.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">5.4 Sequential jobs with&nbsp;<code>needs<\/code><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">jobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Build\"<\/span>\n\n  deploy:\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Deploy\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Flow:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Build\n  |\n  v\nDeploy\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Without&nbsp;<code>needs<\/code>, jobs may run in parallel.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">5.5 Fan-out pattern<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">jobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Build\"<\/span>\n\n  unit:\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Unit tests\"<\/span>\n\n  integration:\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Integration tests\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Flow:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"> <code>           +--&gt; Unit\n            |\nBuild ------+\n            |\n            +--&gt; Integration\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">5.6 Runners<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A runner is the compute environment that executes a job.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Common options:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Runner<\/th><th class=\"has-text-align-left\" data-align=\"left\">Meaning<\/th><\/tr><\/thead><tbody><tr><td><code>ubuntu-latest<\/code><\/td><td>GitHub-hosted Linux runner<\/td><\/tr><tr><td><code>windows-latest<\/code><\/td><td>GitHub-hosted Windows runner<\/td><\/tr><tr><td><code>macos-latest<\/code><\/td><td>GitHub-hosted macOS runner<\/td><\/tr><tr><td><code>self-hosted<\/code><\/td><td>Runner managed by you<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">For this course, use:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http\"><span class=\"hljs-attribute\">runs-on<\/span>: ubuntu-latest\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">5.7 GitHub-hosted runners<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Advantages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>no server maintenance;<\/li>\n\n\n\n<li>fresh environment per job;<\/li>\n\n\n\n<li>common tools preinstalled;<\/li>\n\n\n\n<li>easy scaling;<\/li>\n\n\n\n<li>simple for most CI use cases.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Important consequence:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The environment is temporary. Do not expect files to remain after the job ends unless you upload them somewhere.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">5.8 Self-hosted runners<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A self-hosted runner is a machine you operate.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Examples:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>EC2 instance;<\/li>\n\n\n\n<li>VM;<\/li>\n\n\n\n<li>bare-metal server;<\/li>\n\n\n\n<li>on-premises server;<\/li>\n\n\n\n<li>Kubernetes runner pod.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Use self-hosted runners when you need special networking, hardware, tooling, or internal access.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For beginners, the most important security lesson is:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Do not run untrusted code on a powerful persistent self-hosted runner that can access production systems.<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">5.9 Practice<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Create two jobs:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">jobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Lint\"<\/span>\n\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Test\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Run the workflow and inspect whether the jobs overlap in time.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then add:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http\"><span class=\"hljs-attribute\">needs<\/span>: lint\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">to the&nbsp;<code>test<\/code>&nbsp;job and run it again.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Observe the difference.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">6. Using Actions<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">An Action is reusable automation used inside a step.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">6.1 Action vs command<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Action:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- uses: actions\/checkout@v6\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Command:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- run: npm test\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The difference is simple:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">uses = reuse an Action\nrun  = execute a shell command\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">6.2 Checkout Action<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Most workflows need the repository contents:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- name: Checkout\n  uses: actions\/checkout@v6\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Without checkout, your runner does not automatically have the repository files available for your commands.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">6.3 Setup Actions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Example for Node.js:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">- name: Setup Node\n  <span class=\"hljs-attr\">uses<\/span>: actions\/setup-node@v7\n  <span class=\"hljs-attr\">with<\/span>:\n    node-version: <span class=\"hljs-string\">\"24\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Example for Python:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">- name: Setup Python\n  <span class=\"hljs-attr\">uses<\/span>: actions\/setup-python@v5\n  <span class=\"hljs-attr\">with<\/span>:\n    python-version: <span class=\"hljs-string\">\"3.13\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">6.4 Action inputs<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Actions can accept inputs through&nbsp;<code>with<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">- uses: actions\/setup-node@v7\n  <span class=\"hljs-attr\">with<\/span>:\n    node-version: <span class=\"hljs-string\">\"24\"<\/span>\n    <span class=\"hljs-attr\">cache<\/span>: npm\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">6.5 Action versions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Examples commonly use a major release tag:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-26\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http\"><span class=\"hljs-attribute\">uses<\/span>: actions\/checkout@v6\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-26\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Production security-sensitive third-party Actions should be reviewed carefully and often pinned to a full commit SHA according to your organization&#8217;s supply-chain policy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The beginner rule is:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>prefer official or well-reviewed Actions;<\/li>\n\n\n\n<li>understand what permissions they receive;<\/li>\n\n\n\n<li>do not blindly copy random Marketplace Actions into privileged workflows.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">7. Variables, Contexts, and Expressions<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">These concepts often confuse beginners because they look similar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Use this table first:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Type<\/th><th class=\"has-text-align-left\" data-align=\"left\">Example<\/th><th class=\"has-text-align-left\" data-align=\"left\">Purpose<\/th><\/tr><\/thead><tbody><tr><td>Environment variable<\/td><td><code>$NODE_ENV<\/code><\/td><td>Runtime process configuration<\/td><\/tr><tr><td>Configuration variable<\/td><td><code>${{ vars.API_URL }}<\/code><\/td><td>Non-secret GitHub configuration<\/td><\/tr><tr><td>Context<\/td><td><code>${{ github.ref }}<\/code><\/td><td>Runtime information from GitHub<\/td><\/tr><tr><td>Expression<\/td><td><code>${{ ... }}<\/code><\/td><td>GitHub-side evaluation and logic<\/td><\/tr><tr><td>Secret<\/td><td><code>${{ secrets.API_TOKEN }}<\/code><\/td><td>Sensitive configuration<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">7.1 Environment variables<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Workflow-level:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">env:\n  NODE_ENV: test\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Job-level:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">jobs:\n  test:\n    env:\n      MODE: ci\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Step-level:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-27\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">- name: Run test\n  <span class=\"hljs-attr\">env<\/span>:\n    API_URL: https:<span class=\"hljs-comment\">\/\/example.test<\/span>\n  run: npm test\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-27\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">A more specific scope overrides a broader scope.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">7.2 Default GitHub environment variables<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">GitHub automatically provides many useful values.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Examples:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">GITHUB_REPOSITORY\nGITHUB_SHA\nGITHUB_REF\nGITHUB_REF_NAME\nGITHUB_RUN_ID\nGITHUB_RUN_NUMBER\nGITHUB_WORKFLOW\nGITHUB_WORKSPACE\nRUNNER_OS\nRUNNER_ARCH\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Use them in shell commands:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-28\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">- name: <span class=\"hljs-keyword\">Print<\/span> metadata\n  run: |\n    <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Repository: $GITHUB_REPOSITORY\"<\/span>\n    <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Commit: $GITHUB_SHA\"<\/span>\n    <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Ref: $GITHUB_REF\"<\/span>\n    <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Runner: $RUNNER_OS\/$RUNNER_ARCH\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">7.3 Contexts<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Contexts are structured objects evaluated by GitHub.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Important contexts:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Context<\/th><th class=\"has-text-align-left\" data-align=\"left\">Example data<\/th><\/tr><\/thead><tbody><tr><td><code>github<\/code><\/td><td>repository, ref, SHA, actor, event<\/td><\/tr><tr><td><code>env<\/code><\/td><td>workflow\/job\/step environment values<\/td><\/tr><tr><td><code>vars<\/code><\/td><td>configuration variables<\/td><\/tr><tr><td><code>secrets<\/code><\/td><td>available secrets<\/td><\/tr><tr><td><code>steps<\/code><\/td><td>step outputs and outcomes<\/td><\/tr><tr><td><code>needs<\/code><\/td><td>dependency job results and outputs<\/td><\/tr><tr><td><code>matrix<\/code><\/td><td>current matrix values<\/td><\/tr><tr><td><code>runner<\/code><\/td><td>runner details<\/td><\/tr><tr><td><code>inputs<\/code><\/td><td>manual or reusable workflow inputs<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-29\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">- run: |\n    <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Repository: ${{ github.repository }}\"<\/span>\n    <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Branch: ${{ github.ref_name }}\"<\/span>\n    <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Commit: ${{ github.sha }}\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-29\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">7.4 Expressions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">GitHub expression syntax:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">${{ expression }}\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-30\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">if<\/span>: ${{ github.ref == <span class=\"hljs-string\">'refs\/heads\/main'<\/span> }}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-30\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">In many&nbsp;<code>if:<\/code>&nbsp;conditions, the outer&nbsp;<code>${{ }}<\/code>&nbsp;is optional:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-31\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">if<\/span>: github.ref == <span class=\"hljs-string\">'refs\/heads\/main'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-31\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">7.5 Useful operators<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">==\n!=\n&amp;&amp;\n||\n!\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-32\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">if<\/span>: github.ref == <span class=\"hljs-string\">'refs\/heads\/main'<\/span> &amp;&amp; github.event_name == <span class=\"hljs-string\">'push'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-32\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">7.6 Useful functions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Examples:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-33\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">if<\/span>: startsWith(github.ref, <span class=\"hljs-string\">'refs\/tags\/v'<\/span>)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-33\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-34\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">if<\/span>: contains(github.event.pull_request.labels.*.name, <span class=\"hljs-string\">'run-full-ci'<\/span>)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-34\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-35\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">key: npm-${{ runner.os }}-${{ hashFiles(<span class=\"hljs-string\">'**\/package-lock.json'<\/span>) }}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-35\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">You do not need to memorize all expression functions. Learn how to recognize and look them up.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">7.7 Conditions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A common conditional step:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-36\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">- name: Main branch only\n  <span class=\"hljs-keyword\">if<\/span>: github.ref == <span class=\"hljs-string\">'refs\/heads\/main'<\/span>\n  run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Running on main\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-36\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Common status functions:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-37\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http\"><span class=\"hljs-attribute\">if<\/span>: success()\n<span class=\"hljs-attribute\">if<\/span>: failure()\n<span class=\"hljs-attribute\">if<\/span>: cancelled()\n<span class=\"hljs-attribute\">if<\/span>: always()\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-37\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Example cleanup:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-38\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">- name: Collect diagnostics\n  <span class=\"hljs-keyword\">if<\/span>: failure()\n  run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Tests failed - collecting diagnostics\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-38\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">8. Secrets and GITHUB_TOKEN<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Secrets and permissions are where beginner workflows can accidentally become unsafe.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">8.1 What is a secret?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A secret is sensitive information stored in GitHub instead of being written directly into the workflow file.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Examples:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>API tokens;<\/li>\n\n\n\n<li>passwords;<\/li>\n\n\n\n<li>package registry tokens;<\/li>\n\n\n\n<li>signing credentials;<\/li>\n\n\n\n<li>webhook tokens.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Do not do this:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">env:\n  API_TOKEN: abc123-real-secret\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Instead, create a GitHub secret and reference it:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">env:\n  API_TOKEN: ${{ secrets.API_TOKEN }}\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">8.2 Secret scopes<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Secrets can be configured at different scopes, including:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>repository;<\/li>\n\n\n\n<li>organization;<\/li>\n\n\n\n<li>environment.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">For beginners, repository secrets are the easiest to understand.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For deployments, environment secrets are often better because production and staging can use different credentials.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">8.3 Using a secret safely<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Prefer passing a secret as environment data:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- name: Call API\n  env:\n    API_TOKEN: ${{ secrets.API_TOKEN }}\n  run: .\/call-api.sh\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Avoid printing secrets:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-39\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"$API_TOKEN\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-39\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">GitHub masks many known secret values in logs, but masking should not be treated as a complete data-loss prevention system.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">8.4 Fork pull requests<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Untrusted pull requests do not automatically receive powerful repository secrets.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That is intentional.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The safe mental model is:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Untrusted pull request\n        |\n        v\nLow-privilege CI\n        |\n        v\nTest \/ validate\n        |\n        v\nTrusted workflow handles privileged work\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Do not design ordinary PR testing so that arbitrary code requires production credentials.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">8.5 What is&nbsp;<code>GITHUB_TOKEN<\/code>?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">GitHub automatically creates a short-lived token for workflow jobs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You usually do not need to manually create or rotate it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A workflow can access it as:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">${{ secrets.GITHUB_TOKEN }}\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">or:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">${{ github.token }}\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">8.6 Permissions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Always start with the minimum permissions required.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A safe CI baseline:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">permissions:\n  contents: read\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">A job that needs to create or modify repository content may need more:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">permissions:\n  contents: write\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Do not grant write permission to every job just because one job needs it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">8.7 Workflow-level vs job-level permissions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-40\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">permissions:\n  contents: read\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Read-only CI\"<\/span>\n\n  publish:\n    permissions:\n      contents: write\n    runs-on: ubuntu-latest\n    steps:\n      - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Publishing needs additional permission\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-40\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This keeps the test job less privileged.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">8.8 Beginner security rule<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Remember:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-41\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">Secrets protect sensitive values.\nPermissions control what the workflow identity can <span class=\"hljs-keyword\">do<\/span>.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-41\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">They solve different problems.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">9. Hands-On Lab: Build Your First CI Pipeline<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">This is the main practical exercise.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You will build one CI workflow step by step.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The example assumes a Node.js project with commands similar to:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">npm ci\nnpm run lint\nnpm test\nnpm run build\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">If your project uses different commands, adapt them.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">9.1 Goal<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">By the end, the workflow will look like this:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Push \/ Pull Request\n        |\n        v\nCheckout\n        |\n        v\nSetup Node.js\n        |\n        v\nInstall dependencies\n        |\n        v\nLint\n        |\n        v\nTest\n        |\n        v\nBuild\n        |\n        v\nUpload build artifact\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">9.2 Step 1 &#8211; Create the workflow<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Create:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">.github\/workflows\/ci.yml\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Start with:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-42\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">name: CI\n\non:\n  push:\n    branches: &#91;main]\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  ci:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Say hello\n        run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Starting CI\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-42\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Commit and push.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Confirm that the workflow starts.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">9.3 Step 2 &#8211; Checkout the repository<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Replace the hello-only workflow with:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-43\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http\"><span class=\"hljs-attribute\">name<\/span>: CI\n\non:\n  push:\n    branches: &#91;main]\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  ci:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions\/checkout@v6\n\n      - name: List repository files\n        run: ls -la\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-43\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Run it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The&nbsp;<code>ls -la<\/code>&nbsp;command should now show repository files.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">9.4 Step 3 &#8211; Setup Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Add:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-44\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">- name: Setup Node\n  <span class=\"hljs-attr\">uses<\/span>: actions\/setup-node@v7\n  <span class=\"hljs-attr\">with<\/span>:\n    node-version: <span class=\"hljs-string\">\"24\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-44\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Then verify:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- name: Check Node version\n  run: node --version\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Your steps now include:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-45\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">steps:\n  - name: Checkout\n    <span class=\"hljs-attr\">uses<\/span>: actions\/checkout@v6\n\n  - name: Setup Node\n    <span class=\"hljs-attr\">uses<\/span>: actions\/setup-node@v7\n    <span class=\"hljs-attr\">with<\/span>:\n      node-version: <span class=\"hljs-string\">\"24\"<\/span>\n\n  - name: Check Node version\n    <span class=\"hljs-attr\">run<\/span>: node --version\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-45\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">9.5 Step 4 &#8211; Install dependencies<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Add:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- name: Install dependencies\n  run: npm ci\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Why&nbsp;<code>npm ci<\/code>&nbsp;instead of&nbsp;<code>npm install<\/code>&nbsp;in CI?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>npm ci<\/code>&nbsp;is designed for clean, repeatable installation based on the lock file.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">9.6 Step 5 &#8211; Lint<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Add:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- name: Lint\n  run: npm run lint\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">If linting fails, the job normally stops and the workflow fails.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That is desirable in CI when lint is a required quality gate.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">9.7 Step 6 &#8211; Test<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Add:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- name: Test\n  run: npm test\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Now the pipeline validates behavior, not only syntax\/style.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">9.8 Step 7 &#8211; Build<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Add:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- name: Build\n  run: npm run build\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">A successful run now proves:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Repository can be checked out\nDependencies can be installed\nCode passes lint\nTests pass\nApplication can build\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">9.9 Step 8 &#8211; Add a timeout<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Jobs should not run forever.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Add:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-46\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http\"><span class=\"hljs-attribute\">timeout-minutes<\/span>: 15\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-46\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">jobs:\n  ci:\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">9.10 Step 9 &#8211; Final baseline CI workflow<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-47\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http\"><span class=\"hljs-attribute\">name<\/span>: CI\n\non:\n  push:\n    branches: &#91;main]\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  ci:\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n\n    steps:\n      - name: Checkout\n        uses: actions\/checkout@v6\n\n      - name: Setup Node\n        uses: actions\/setup-node@v7\n        with:\n          node-version: \"24\"\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Lint\n        run: npm run lint\n\n      - name: Test\n        run: npm test\n\n      - name: Build\n        run: npm run build\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-47\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">9.11 What happens when&nbsp;<code>npm test<\/code>&nbsp;fails?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Execution normally looks like:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Checkout ........ PASS\nSetup Node ...... PASS\nInstall ......... PASS\nLint ............ PASS\nTest ............ FAIL\nBuild ........... NOT RUN\nWorkflow ........ FAIL\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">That is standard fail-fast behavior for sequential steps.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">9.12 Add diagnostics on failure<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Add:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-48\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">- name: Failure diagnostics\n  <span class=\"hljs-keyword\">if<\/span>: failure()\n  run: |\n    <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"A previous step failed\"<\/span>\n    <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Commit: $GITHUB_SHA\"<\/span>\n    <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Runner: $RUNNER_OS\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-48\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This introduces conditional steps.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">9.13 Add concurrency for pull request CI<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When developers push several commits quickly, old CI runs can become wasteful.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Add at workflow level:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-49\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">concurrency:\n  group: ci-${{ github.workflow }}-${{ github.ref }}\n  cancel-<span class=\"hljs-keyword\">in<\/span>-progress: <span class=\"hljs-literal\">true<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-49\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Complete baseline:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-50\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">name: CI\n\non:\n  push:\n    branches: &#91;main]\n  pull_request:\n\npermissions:\n  contents: read\n\nconcurrency:\n  group: ci-${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: <span class=\"hljs-keyword\">true<\/span>\n\njobs:\n  ci:\n    runs-on: ubuntu-latest\n    timeout-minutes: <span class=\"hljs-number\">15<\/span>\n\n    steps:\n      - name: Checkout\n        uses: actions\/checkout@v6\n\n      - name: Setup Node\n        uses: actions\/setup-node@v7\n        with:\n          node-version: <span class=\"hljs-string\">\"24\"<\/span>\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Lint\n        run: npm run lint\n\n      - name: Test\n        run: npm test\n\n      - name: Build\n        run: npm run build\n\n      - name: Failure diagnostics\n        <span class=\"hljs-keyword\">if<\/span>: failure()\n        run: |\n          <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"CI failed\"<\/span>\n          <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Commit: $GITHUB_SHA\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-50\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">You now have a useful real-world CI workflow.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">10. Matrix Builds<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A matrix allows one job definition to run multiple combinations.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">10.1 Why use a matrix?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Suppose your application supports Node.js 22 and 24.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Without a matrix, you could create two nearly identical jobs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That duplicates YAML.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With a matrix:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-51\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">strategy<\/span>:\n  <span class=\"hljs-selector-tag\">matrix<\/span>:\n    <span class=\"hljs-selector-tag\">node<\/span>: <span class=\"hljs-selector-attr\">&#91;22, 24]<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-51\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">GitHub expands the job.<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Test job\n   |\n   +--&gt; Node 22\n   |\n   +--&gt; Node 24\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">10.2 Basic matrix workflow<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-52\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http\"><span class=\"hljs-attribute\">name<\/span>: Matrix CI\n\non:\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node: &#91;22, 24]\n\n    steps:\n      - uses: actions\/checkout@v6\n\n      - uses: actions\/setup-node@v7\n        with:\n          node-version: ${{ matrix.node }}\n\n      - run: npm ci\n      - run: npm test\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-52\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">GitHub creates separate jobs for the matrix values.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">10.3 Multi-dimensional matrices<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">strategy:\n  matrix:\n    os: &#91;ubuntu-latest, windows-latest]\n    node: &#91;22, 24]\n\nruns-on: ${{ matrix.os }}\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This expands to four combinations:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">OS<\/th><th class=\"has-text-align-right\" data-align=\"right\">Node<\/th><\/tr><\/thead><tbody><tr><td>Ubuntu<\/td><td class=\"has-text-align-right\" data-align=\"right\">22<\/td><\/tr><tr><td>Ubuntu<\/td><td class=\"has-text-align-right\" data-align=\"right\">24<\/td><\/tr><tr><td>Windows<\/td><td class=\"has-text-align-right\" data-align=\"right\">22<\/td><\/tr><tr><td>Windows<\/td><td class=\"has-text-align-right\" data-align=\"right\">24<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Do not create large matrices just because you can. Every combination costs time and compute.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">10.4&nbsp;<code>fail-fast<\/code><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If one matrix job fails, GitHub can cancel other in-progress matrix jobs according to strategy behavior.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you want all compatibility results:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-53\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">strategy:\n  fail-fast: <span class=\"hljs-literal\">false<\/span>\n  <span class=\"hljs-attr\">matrix<\/span>:\n    node: &#91;<span class=\"hljs-number\">22<\/span>, <span class=\"hljs-number\">24<\/span>]\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-53\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">10.5 Beginner rule<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Use a matrix when you are testing the same logic against multiple supported environments.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Do not use a matrix merely to make YAML look advanced.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">11. Caching<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Caching speeds up workflows by reusing data that is expensive to download or compute.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">11.1 Mental model<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Without cache:\nDownload dependencies -&gt; run build\n\nWith cache hit:\nRestore dependency cache -&gt; run build\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">A cache is a performance optimization.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your build should still be correct if the cache disappears.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">11.2 Node.js dependency caching<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The simplest approach uses&nbsp;<code>setup-node<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-54\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">- name: Setup Node\n  <span class=\"hljs-attr\">uses<\/span>: actions\/setup-node@v7\n  <span class=\"hljs-attr\">with<\/span>:\n    node-version: <span class=\"hljs-string\">\"24\"<\/span>\n    <span class=\"hljs-attr\">cache<\/span>: npm\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-54\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Then:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- run: npm ci\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The cache reduces repeated dependency download work.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">11.3 Cache keys<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For manual caching, keys usually include information that determines whether cached content is valid.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example concept:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">OS + runtime + dependency lock hash\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-55\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">key: npm-${{ runner.os }}-${{ hashFiles(<span class=\"hljs-string\">'**\/package-lock.json'<\/span>) }}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-55\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">11.4 Cache vs artifact<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Do not confuse them.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Cache<\/th><th class=\"has-text-align-left\" data-align=\"left\">Artifact<\/th><\/tr><\/thead><tbody><tr><td>Speeds up future work<\/td><td>Preserves workflow output<\/td><\/tr><tr><td>Usually dependencies\/build cache<\/td><td>Usually packages\/reports\/build output<\/td><\/tr><tr><td>Correctness should not depend on it<\/td><td>Can be part of the delivery pipeline<\/td><\/tr><tr><td>Looked up by cache key<\/td><td>Uploaded\/downloaded by artifact name or ID<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">11.5 Cache security<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Cached content can become a supply-chain risk if untrusted workflows can poison caches later consumed by privileged jobs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Beginner rule:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Treat cache as untrusted performance data, not as proof that software is safe.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">12. Artifacts<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Artifacts are files produced by a workflow and stored by GitHub.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Examples:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>compiled application;<\/li>\n\n\n\n<li>coverage report;<\/li>\n\n\n\n<li>test report;<\/li>\n\n\n\n<li>generated documentation;<\/li>\n\n\n\n<li>packaged release candidate;<\/li>\n\n\n\n<li>diagnostic logs.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">12.1 Upload an artifact<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If your build creates&nbsp;<code>dist\/<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- name: Upload build artifact\n  uses: actions\/upload-artifact@v4\n  with:\n    name: application\n    path: dist\/\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">12.2 Add artifact upload to the lab<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Update your CI workflow:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- name: Build\n  run: npm run build\n\n- name: Upload build artifact\n  uses: actions\/upload-artifact@v4\n  with:\n    name: application-${{ github.sha }}\n    path: dist\/\n    retention-days: 7\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Now the flow becomes:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Checkout\n   |\nSetup\n   |\nInstall\n   |\nLint\n   |\nTest\n   |\nBuild\n   |\nUpload artifact\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">12.3 Download in another job<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A separate job can download the artifact:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">deploy:\n  needs: ci\n  runs-on: ubuntu-latest\n\n  steps:\n    - name: Download build\n      uses: actions\/download-artifact@v5\n      with:\n        name: application-${{ github.sha }}\n        path: dist\/\n\n    - name: Inspect\n      run: ls -la dist\/\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">12.4 Build once, deploy many<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A mature CI\/CD design often creates one immutable artifact and promotes that exact artifact.<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Commit\n  |\n  v\nBuild once\n  |\n  v\nArtifact\n  |\n  +--&gt; Staging\n  |\n  +--&gt; UAT\n  |\n  +--&gt; Production\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This is better than rebuilding different binaries independently for each environment when consistency matters.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">13. From CI to CD and Environments<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Once CI proves the code is acceptable, CD moves a release toward an environment.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">13.1 Basic lifecycle<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Developer\n   |\n   v\nPush \/ Pull Request\n   |\n   v\nCI\n   |\n   +--&gt; Lint\n   +--&gt; Test\n   +--&gt; Build\n   |\n   v\nArtifact\n   |\n   v\nStaging\n   |\n   v\nApproval \/ Protection\n   |\n   v\nProduction\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">13.2 GitHub environments<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A GitHub environment can represent a deployment target such as:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">development\nstaging\nuat\nproduction\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Environments can be used with:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>environment secrets;<\/li>\n\n\n\n<li>environment variables;<\/li>\n\n\n\n<li>deployment approvals;<\/li>\n\n\n\n<li>branch restrictions;<\/li>\n\n\n\n<li>deployment protection rules;<\/li>\n\n\n\n<li>deployment history.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">13.3 Reference an environment<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">deploy:\n  needs: build\n  runs-on: ubuntu-latest\n  environment: production\n\n  steps:\n    - run: .\/deploy.sh\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">13.4 A safer deployment shape<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">jobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v6\n      - run: .\/build.sh\n      - uses: actions\/upload-artifact@v4\n        with:\n          name: release\n          path: dist\/\n\n  deploy:\n    needs: build\n    runs-on: ubuntu-latest\n    environment: production\n\n    steps:\n      - uses: actions\/download-artifact@v5\n        with:\n          name: release\n          path: dist\/\n\n      - run: .\/deploy.sh dist\/\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The key design idea is:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Build first\nVerify first\nThen deploy\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">13.5 Approvals<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A production environment can require reviewers before a deployment job receives protected environment access.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Conceptually:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Deployment requested\n       |\n       v\nEnvironment gate\n       |\n       +--&gt; Rejected --&gt; Stop\n       |\n       +--&gt; Approved --&gt; Deploy\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">For a two-hour course, understand the concept. Detailed environment governance belongs in the advanced course.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">14. GitHub Actions Security: 5 Rules<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">If students remember only five security rules, use these.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Rule 1 &#8211; Use least privilege<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Start with:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">permissions:\n  contents: read\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Add write permissions only when a specific job needs them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Bad beginner habit:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-56\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http\"><span class=\"hljs-attribute\">permissions<\/span>: write-all\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-56\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Avoid broad permissions unless there is a strong, reviewed reason.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Rule 2 &#8211; Never hard-code secrets<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Bad:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">env:\n  PASSWORD: my-password-123\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Good:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">env:\n  PASSWORD: ${{ secrets.PASSWORD }}\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Rule 3 &#8211; Do not print secrets<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Bad:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-57\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"$PASSWORD\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-57\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Also avoid writing secrets into artifacts or caches.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Rule 4 &#8211; Treat Actions as code dependencies<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">An Action can execute code in your job.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That means an Action may be able to access:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>repository files;<\/li>\n\n\n\n<li>available secrets;<\/li>\n\n\n\n<li>job token permissions;<\/li>\n\n\n\n<li>the network;<\/li>\n\n\n\n<li>workflow outputs.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Prefer trusted Actions and review third-party Actions before allowing them into privileged workflows.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Rule 5 &#8211; Treat pull request input as untrusted<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This can be dangerous:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-58\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">- run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"${{ github.event.pull_request.title }}\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-58\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Why?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A pull request title is user-controlled data. Interpolating untrusted values directly into shell syntax can create command-injection risks.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Safer:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-59\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">- name: <span class=\"hljs-keyword\">Print<\/span> title safely\n  env:\n    PR_TITLE: ${{ github.event.pull_request.title }}\n  run: printf <span class=\"hljs-string\">'%s\\n'<\/span> <span class=\"hljs-string\">\"$PR_TITLE\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-59\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The general rule is:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-60\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">Untrusted input should be treated <span class=\"hljs-keyword\">as<\/span> data,\nnot <span class=\"hljs-keyword\">as<\/span> executable shell syntax.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-60\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">14.1 One more critical PR rule<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>pull_request<\/code>&nbsp;and&nbsp;<code>pull_request_target<\/code>&nbsp;are not interchangeable.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For beginner CI that executes pull request code, prefer a low-privilege&nbsp;<code>pull_request<\/code>&nbsp;workflow.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Do not combine a privileged base-repository context with execution of untrusted pull request code unless you fully understand the security model.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">15. Troubleshooting: The 7-Step Method<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Do not debug GitHub Actions by randomly editing YAML.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Use layers.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">15.1 Debugging flow<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Problem\n  |\n  v\n1. Did workflow start?\n  |\n  +-- No --&gt; Trigger \/ branch \/ path \/ YAML\n  |\n  +-- Yes\n        |\n        v\n2. Did job start?\n        |\n        +-- No --&gt; needs \/ if \/ runner \/ environment\n        |\n        +-- Yes\n              |\n              v\n3. Which step failed?\n              |\n              v\n4. Read first useful error\n              |\n              v\n5. Classify error\n              |\n      +-------+-------+-------+-------+\n      |       |       |       |       |\n    YAML   Command  Auth   Network  Runtime\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">15.2 Step 1 &#8211; Did the workflow start?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If no workflow run appears, check:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Is the file under\u00a0<code>.github\/workflows\/<\/code>?<\/li>\n\n\n\n<li>Is the YAML valid?<\/li>\n\n\n\n<li>Did the expected event happen?<\/li>\n\n\n\n<li>Does\u00a0<code>on<\/code>\u00a0match the event?<\/li>\n\n\n\n<li>Does the branch filter match?<\/li>\n\n\n\n<li>Does the path filter match?<\/li>\n\n\n\n<li>Is the workflow enabled?<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">15.3 Step 2 &#8211; Did the job start?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If the workflow exists but a job is skipped or queued, check:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>if<\/code>\u00a0condition;<\/li>\n\n\n\n<li><code>needs<\/code>\u00a0dependency;<\/li>\n\n\n\n<li>runner label;<\/li>\n\n\n\n<li>environment approval;<\/li>\n\n\n\n<li>runner availability.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">15.4 Step 3 &#8211; Which step failed?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Open the failing job and locate the first failed step.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Avoid focusing only on the last red line. The useful error may appear earlier.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">15.5 Step 4 &#8211; Is it YAML or application logic?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Example workflow error:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Invalid workflow file\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Usually means syntax, indentation, expression, or unsupported configuration.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example application error:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-61\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">npm test exited <span class=\"hljs-keyword\">with<\/span> code <span class=\"hljs-number\">1<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-61\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">That may mean the workflow works correctly but the test failed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This distinction matters.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">15.6 Step 5 &#8211; Check authentication and permissions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Useful mental model:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">401 = authentication problem is likely\n403 = identity exists but permission\/policy may deny\n404 = resource may not exist or may be hidden by authorization\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Check:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>permissions<\/code>;<\/li>\n\n\n\n<li>secret availability;<\/li>\n\n\n\n<li>token scope;<\/li>\n\n\n\n<li>environment approval;<\/li>\n\n\n\n<li>fork restrictions.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">15.7 Step 6 &#8211; Check the runner environment<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Print selected values:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-62\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">- name: Runner diagnostics\n  run: |\n    <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"OS=$RUNNER_OS\"<\/span>\n    <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"ARCH=$RUNNER_ARCH\"<\/span>\n    <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"WORKSPACE=$GITHUB_WORKSPACE\"<\/span>\n    pwd\n    df -h\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-62\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">For self-hosted runners also check:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>runner online status;<\/li>\n\n\n\n<li>labels;<\/li>\n\n\n\n<li>disk space;<\/li>\n\n\n\n<li>network access;<\/li>\n\n\n\n<li>service health;<\/li>\n\n\n\n<li>file permissions.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">15.8 Step 7 &#8211; Reproduce the failing command<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If this fails in Actions:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- run: npm test\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">try to reproduce the same command with the same runtime and dependencies locally or in a controlled container.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The workflow may simply be exposing a real application issue.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">15.9 Common mistakes<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Symptom<\/th><th class=\"has-text-align-left\" data-align=\"left\">Likely cause<\/th><\/tr><\/thead><tbody><tr><td>Workflow does not run<\/td><td>Trigger\/filter\/path\/YAML issue<\/td><\/tr><tr><td>Job is skipped<\/td><td><code>if<\/code>&nbsp;or failed&nbsp;<code>needs<\/code>&nbsp;dependency<\/td><\/tr><tr><td>Job stays queued<\/td><td>No matching runner or capacity<\/td><\/tr><tr><td><code>command not found<\/code><\/td><td>Tool missing or PATH issue<\/td><\/tr><tr><td>File missing in second job<\/td><td>Jobs do not share filesystem<\/td><\/tr><tr><td>Secret empty<\/td><td>Secret unavailable in that context\/scope<\/td><\/tr><tr><td>API returns 403<\/td><td>Token lacks permission<\/td><\/tr><tr><td>Cache always misses<\/td><td>Bad key or changing inputs<\/td><\/tr><tr><td>Artifact missing<\/td><td>Wrong path or build did not create file<\/td><\/tr><tr><td>Works locally, fails in CI<\/td><td>Environment\/runtime\/dependency difference<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">16. Final Hands-On Challenge<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Start with this incomplete workflow:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-63\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">name: CI\n\non:\n  push:\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions\/checkout@v6\n\n      - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"TODO\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-63\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Complete as many tasks as possible without copying the final solution.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Challenge tasks<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Add the\u00a0<code>pull_request<\/code>\u00a0trigger.<\/li>\n\n\n\n<li>Make\u00a0<code>push<\/code>\u00a0run only for\u00a0<code>main<\/code>.<\/li>\n\n\n\n<li>Add\u00a0<code>workflow_dispatch<\/code>.<\/li>\n\n\n\n<li>Add\u00a0<code>permissions: contents: read<\/code>.<\/li>\n\n\n\n<li>Add a 15-minute job timeout.<\/li>\n\n\n\n<li>Setup Node.js 24.<\/li>\n\n\n\n<li>Enable npm caching.<\/li>\n\n\n\n<li>Run\u00a0<code>npm ci<\/code>.<\/li>\n\n\n\n<li>Run\u00a0<code>npm run lint<\/code>.<\/li>\n\n\n\n<li>Run\u00a0<code>npm test<\/code>.<\/li>\n\n\n\n<li>Run\u00a0<code>npm run build<\/code>.<\/li>\n\n\n\n<li>Upload\u00a0<code>dist\/<\/code>\u00a0as an artifact.<\/li>\n\n\n\n<li>Name the artifact using\u00a0<code>github.sha<\/code>.<\/li>\n\n\n\n<li>Add a failure-diagnostics step.<\/li>\n\n\n\n<li>Convert Node.js versions 22 and 24 into a matrix.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">One possible solution<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-64\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">name: CI\n\non:\n  push:\n    branches: &#91;main]\n  pull_request:\n  workflow_dispatch:\n\npermissions:\n  contents: read\n\nconcurrency:\n  group: ci-${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: <span class=\"hljs-keyword\">true<\/span>\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    timeout-minutes: <span class=\"hljs-number\">15<\/span>\n\n    strategy:\n      fail-fast: <span class=\"hljs-keyword\">false<\/span>\n      matrix:\n        node: &#91;<span class=\"hljs-number\">22<\/span>, <span class=\"hljs-number\">24<\/span>]\n\n    steps:\n      - name: Checkout\n        uses: actions\/checkout@v6\n\n      - name: Setup Node\n        uses: actions\/setup-node@v7\n        with:\n          node-version: ${{ matrix.node }}\n          cache: npm\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Lint\n        run: npm run lint\n\n      - name: Test\n        run: npm test\n\n      - name: Build\n        run: npm run build\n\n      - name: Upload artifact\n        uses: actions\/upload-artifact@v4\n        with:\n          name: application-node-${{ matrix.node }}-${{ github.sha }}\n          path: dist\/\n          retention-days: <span class=\"hljs-number\">7<\/span>\n\n      - name: Failure diagnostics\n        <span class=\"hljs-keyword\">if<\/span>: failure()\n        run: |\n          <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Workflow: $GITHUB_WORKFLOW\"<\/span>\n          <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Commit: $GITHUB_SHA\"<\/span>\n          <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Runner: $RUNNER_OS\/$RUNNER_ARCH\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-64\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">If you can explain every line in this workflow, you have learned the essential GitHub Actions model.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">17. GitHub Actions Essential Cheat Sheet<\/h1>\n\n\n\n<h2 class=\"wp-block-heading\">17.1 Workflow location<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">.github\/workflows\/*.yml\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">17.2 Minimal workflow<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-65\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">name: CI\n\non:\n  push:\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Hello\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-65\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">17.3 Common triggers<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">on:\n  push:\n  pull_request:\n  workflow_dispatch:\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">17.4 Push only to main<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-66\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">on<\/span>:\n  <span class=\"hljs-selector-tag\">push<\/span>:\n    <span class=\"hljs-selector-tag\">branches<\/span>: <span class=\"hljs-selector-attr\">&#91;main]<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-66\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">17.5 Checkout<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- uses: actions\/checkout@v6\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">17.6 Setup Node<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-67\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">- uses: actions\/setup-node@v7\n  <span class=\"hljs-attr\">with<\/span>:\n    node-version: <span class=\"hljs-string\">\"24\"<\/span>\n    <span class=\"hljs-attr\">cache<\/span>: npm\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-67\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">17.7 Run a command<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- run: npm test\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">17.8 Multiline shell<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- run: |\n    npm ci\n    npm test\n    npm run build\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">17.9 Job dependency<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-68\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http\"><span class=\"hljs-attribute\">needs<\/span>: build\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-68\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">17.10 Condition<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-69\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">if<\/span>: github.ref == <span class=\"hljs-string\">'refs\/heads\/main'<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-69\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">17.11 Secret<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">env:\n  API_TOKEN: ${{ secrets.API_TOKEN }}\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">17.12 Read-only permissions<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">permissions:\n  contents: read\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">17.13 Context values<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">${{ github.repository }}\n${{ github.sha }}\n${{ github.ref_name }}\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">17.14 Matrix<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-70\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">strategy<\/span>:\n  <span class=\"hljs-selector-tag\">matrix<\/span>:\n    <span class=\"hljs-selector-tag\">node<\/span>: <span class=\"hljs-selector-attr\">&#91;22, 24]<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-70\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Use:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">${{ matrix.node }}\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">17.15 Upload artifact<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- uses: actions\/upload-artifact@v4\n  with:\n    name: application\n    path: dist\/\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">17.16 Download artifact<\/h2>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">- uses: actions\/download-artifact@v5\n  with:\n    name: application\n    path: dist\/\n<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">17.17 Cancel stale CI<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-71\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">concurrency:\n  group: ci-${{ github.workflow }}-${{ github.ref }}\n  cancel-<span class=\"hljs-keyword\">in<\/span>-progress: <span class=\"hljs-literal\">true<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-71\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">17.18 Failure-only step<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-72\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">- <span class=\"hljs-keyword\">if<\/span>: failure()\n  run: <span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Something failed\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-72\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">17.19 Environment<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-73\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http\"><span class=\"hljs-attribute\">environment<\/span>: production\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-73\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">17.20 The five security rules<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-74\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-number\">1.<\/span> Least privilege.\n<span class=\"hljs-number\">2.<\/span> Never hard-code secrets.\n<span class=\"hljs-number\">3.<\/span> Never <span class=\"hljs-keyword\">print<\/span> secrets.\n<span class=\"hljs-number\">4.<\/span> Treat Actions <span class=\"hljs-keyword\">as<\/span> executable dependencies.\n<span class=\"hljs-number\">5.<\/span> Treat pull request input <span class=\"hljs-keyword\">as<\/span> untrusted.\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-74\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">18. 15-Question Knowledge Check<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Try to answer before reading the answer key.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Questions<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. Where must GitHub Actions workflow files normally be stored?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A.&nbsp;<code>.github\/actions\/<\/code>&nbsp;B.&nbsp;<code>.github\/workflows\/<\/code>&nbsp;C.&nbsp;<code>.git\/workflows\/<\/code>&nbsp;D.&nbsp;<code>workflows\/<\/code><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2. What does&nbsp;<code>on<\/code>&nbsp;define?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A. The runner B. Workflow permissions C. Workflow triggers D. Workflow artifacts<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">3. What is the difference between a job and a step?<\/h3>\n\n\n\n<h3 class=\"wp-block-heading\">4. Do two jobs automatically share the same filesystem?<\/h3>\n\n\n\n<h3 class=\"wp-block-heading\">5. What does&nbsp;<code>runs-on<\/code>&nbsp;select?<\/h3>\n\n\n\n<h3 class=\"wp-block-heading\">6. What is the difference between&nbsp;<code>uses<\/code>&nbsp;and&nbsp;<code>run<\/code>?<\/h3>\n\n\n\n<h3 class=\"wp-block-heading\">7. What does&nbsp;<code>needs: build<\/code>&nbsp;do?<\/h3>\n\n\n\n<h3 class=\"wp-block-heading\">8. Which should hold an API password?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A.&nbsp;<code>vars<\/code>&nbsp;B.&nbsp;<code>secrets<\/code>&nbsp;C.&nbsp;<code>github<\/code>&nbsp;D.&nbsp;<code>runner<\/code><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">9. What does&nbsp;<code>${{ github.sha }}<\/code>&nbsp;represent?<\/h3>\n\n\n\n<h3 class=\"wp-block-heading\">10. What problem does a matrix solve?<\/h3>\n\n\n\n<h3 class=\"wp-block-heading\">11. What is the main purpose of a cache?<\/h3>\n\n\n\n<h3 class=\"wp-block-heading\">12. What is the main purpose of an artifact?<\/h3>\n\n\n\n<h3 class=\"wp-block-heading\">13. Why should workflow permissions be minimal?<\/h3>\n\n\n\n<h3 class=\"wp-block-heading\">14. Why is direct interpolation of pull request titles into shell commands risky?<\/h3>\n\n\n\n<h3 class=\"wp-block-heading\">15. If a workflow does not start at all, what should you check first?<\/h3>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Answer key<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. B &#8211;&nbsp;<code>.github\/workflows\/<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">That is the standard workflow directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2. C &#8211; Workflow triggers<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>on<\/code>&nbsp;tells GitHub when the workflow is eligible to run.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">3. Job vs step<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A job is independently scheduled to a runner. A step is one ordered task inside a job.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">4. No<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Jobs normally run on separate runner environments. Use artifacts, outputs, or another explicit mechanism to transfer data.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">5. The runner<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-75\" data-shcb-language-name=\"HTTP\" data-shcb-language-slug=\"http\"><span><code class=\"hljs language-http\"><span class=\"hljs-attribute\">runs-on<\/span>: ubuntu-latest\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-75\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTTP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">http<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">6.&nbsp;<code>uses<\/code>&nbsp;vs&nbsp;<code>run<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>uses<\/code>&nbsp;invokes an Action.&nbsp;<code>run<\/code>&nbsp;executes a shell command.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">7. It creates a dependency<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The dependent job waits for&nbsp;<code>build<\/code>&nbsp;and normally only runs if the dependency succeeds.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">8. B &#8211;&nbsp;<code>secrets<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Sensitive values should not be ordinary configuration variables.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">9. The commit SHA<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">It identifies the commit associated with the workflow run.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">10. Repeated compatibility combinations<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A matrix expands one job definition across multiple values such as runtime versions or operating systems.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">11. Performance<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A cache reduces repeated download or computation work.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">12. Retaining workflow output<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Artifacts preserve files such as build output, reports, or packages.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">13. Reduce blast radius<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If a job or dependency is compromised, least privilege limits what the workflow identity can do.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">14. Command injection risk<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Pull request metadata is user-controlled. It should be passed as data rather than inserted directly into executable shell syntax.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">15. Trigger and workflow definition<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Check the workflow location, YAML validity, event, branch\/path filters, and whether the workflow is enabled.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">19. What to Learn Next<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">After completing this tutorial, you should be comfortable with:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>workflows;<\/li>\n\n\n\n<li>triggers;<\/li>\n\n\n\n<li>jobs;<\/li>\n\n\n\n<li>steps;<\/li>\n\n\n\n<li>runners;<\/li>\n\n\n\n<li>Actions;<\/li>\n\n\n\n<li>variables;<\/li>\n\n\n\n<li>contexts;<\/li>\n\n\n\n<li>expressions;<\/li>\n\n\n\n<li>secrets;<\/li>\n\n\n\n<li><code>GITHUB_TOKEN<\/code>;<\/li>\n\n\n\n<li>permissions;<\/li>\n\n\n\n<li>matrix builds;<\/li>\n\n\n\n<li>caching;<\/li>\n\n\n\n<li>artifacts;<\/li>\n\n\n\n<li>basic CI\/CD flow;<\/li>\n\n\n\n<li>environments;<\/li>\n\n\n\n<li>security basics;<\/li>\n\n\n\n<li>troubleshooting basics.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The next learning sequence should be:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-76\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">GitHub Actions Essentials\n        |\n        v\nReusable Workflows\n        |\n        v\nAdvanced Security\n        |\n        v\nOIDC \/ Cloud Authentication\n        |\n        v\nDocker CI\/CD\n        |\n        v\nCloud \/ Kubernetes Deployment\n        |\n        v\nInfrastructure <span class=\"hljs-keyword\">as<\/span> Code\n        |\n        v\n<span class=\"hljs-keyword\">Self<\/span>-hosted Runners \/ ARC\n        |\n        v\nOrganization <span class=\"hljs-keyword\">and<\/span> Enterprise Governance\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-76\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">19.1 Recommended next topics<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Reusable automation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Learn:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>reusable workflows;<\/li>\n\n\n\n<li>composite actions;<\/li>\n\n\n\n<li>workflow inputs;<\/li>\n\n\n\n<li>workflow outputs;<\/li>\n\n\n\n<li>centralized CI standards.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Advanced security<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Learn:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>pull_request<\/code>\u00a0vs\u00a0<code>pull_request_target<\/code>;<\/li>\n\n\n\n<li>third-party Action pinning;<\/li>\n\n\n\n<li>cache poisoning;<\/li>\n\n\n\n<li>supply-chain trust;<\/li>\n\n\n\n<li>branch protection;<\/li>\n\n\n\n<li>rulesets;<\/li>\n\n\n\n<li>environment protection.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Cloud authentication<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Learn OpenID Connect so workflows can obtain short-lived cloud credentials instead of storing long-lived cloud access keys.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Containers<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Learn:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Docker build and push;<\/li>\n\n\n\n<li>container registries;<\/li>\n\n\n\n<li>image tags and digests;<\/li>\n\n\n\n<li>image scanning;<\/li>\n\n\n\n<li>SBOMs;<\/li>\n\n\n\n<li>provenance.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Kubernetes<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Learn:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>secure cluster authentication;<\/li>\n\n\n\n<li>Helm or Kustomize;<\/li>\n\n\n\n<li>rollout verification;<\/li>\n\n\n\n<li>namespace and identity isolation;<\/li>\n\n\n\n<li>GitOps patterns.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Infrastructure as Code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Learn a controlled pipeline such as:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">fmt\n |\n v\nvalidate\n |\n v\nsecurity checks\n |\n v\nplan\n |\n v\nreview \/ approval\n |\n v\napply\n<\/code><\/span><\/pre>\n\n\n<h3 class=\"wp-block-heading\">Runner architecture<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Learn:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>self-hosted runners;<\/li>\n\n\n\n<li>ephemeral runners;<\/li>\n\n\n\n<li>runner groups;<\/li>\n\n\n\n<li>private networking;<\/li>\n\n\n\n<li>Actions Runner Controller;<\/li>\n\n\n\n<li>trust-zone separation.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h1 class=\"wp-block-heading\">Final Summary<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">If you remember only one model, remember this:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">Event\n  |\n  v\nWorkflow\n  |\n  v\nJobs\n  |\n  v\nRunners\n  |\n  v\nSteps\n  |\n  v\nActions \/ Commands\n<\/code><\/span><\/pre>\n\n\n<p class=\"wp-block-paragraph\">And if you remember only one production pipeline shape, remember this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-77\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">Change\n  |\n  v\nCI Trigger\n  |\n  v\nCheckout\n  |\n  v\nInstall\n  |\n  v\nLint\n  |\n  v\nTest\n  |\n  v\nBuild\n  |\n  v\nArtifact\n  |\n  v\n<span class=\"hljs-keyword\">Protected<\/span> Deployment\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-77\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">And if you remember only one security principle:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Give every workflow, job, runner, Action, and credential only the access it actually needs.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">That foundation is enough to start building useful GitHub Actions workflows safely and confidently.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Audience: Beginners, developers, junior DevOps engineers, QA engineers, and students Level: Beginner to early-intermediate Duration: About 120 minutes Format: Learn, modify, run, break, and fix Goal: By&#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_joinchat":[],"footnotes":""},"categories":[11138],"tags":[],"class_list":["post-78744","post","type-post","status-publish","format-standard","hentry","category-best-tools"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/78744","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/comments?post=78744"}],"version-history":[{"count":1,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/78744\/revisions"}],"predecessor-version":[{"id":78745,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/78744\/revisions\/78745"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media?parent=78744"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/categories?post=78744"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/tags?post=78744"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}