---
title: "GitHub Actions"
lang: "en"
author: "Mohammad Abu Mattar"
canonical: https://mkabumattar.com/github-actions
---

[Home](/)›[Cheatsheets](/cheatsheets)

Cheatsheets

# GitHub Actions

The workflow YAML you write over and over. Triggers, jobs and steps, secrets, matrix builds, caching, artifacts, and reusable workflows in one reference.

6 Categories11 Sections14 ExamplesPublished: 04 Aug 2026Updated: 04 Aug 2026

GitHub ActionsworkflowCI/CDmatrix buildsecretscachingreusable workflowcomposite action

[Markdown for AI(opens in a new tab)](/github-actions/index.md "Open the plain-Markdown version of this page, for pasting into an AI tool")

Series

[CI/CD & GitOps](/series/cicd--gitops)1/1

All posts in this series (1)

Cheatsheets1

1.  [GitHub ActionsYou are here](/cheatsheets/github-actions)

GitHub Actions runs your CI/CD directly from YAML files in `.github/workflows/`, and most of the job is knowing which key does what. A workflow reacts to events, splits into jobs that run on runners, and each job is an ordered list of steps that either call a reusable action or run a shell command.

This cheatsheet walks the parts you touch on every pipeline: triggers that decide when things run, the job and step structure, `env` and secrets across their three scopes, matrix builds for testing many versions at once, caching and artifacts to stay fast, and reusable workflows and composite actions to stop copy-pasting the same YAML into every repo.

## [Key features](#key-features)

-   **Trigger precisely**: filter `push` and `pull_request` by branch and path, plus `schedule` and `workflow_dispatch`.
-   **Structure jobs**: `needs` for ordering, `if` for conditions, `runs-on` for the machine.
-   **Handle secrets safely**: three `env` scopes, masked `secrets`, and a least-privilege `GITHUB_TOKEN`.
-   **Scale and reuse**: matrix builds, dependency caching, artifacts, reusable workflows, and composite actions.

[Workflow Triggers](#category-workflow-triggers)

-   [Event Triggers](#section-event-triggers)
-   [workflow\_call and Reuse](#section-workflow-call)

[Jobs and Steps](#category-jobs-and-steps)

-   [Job Structure](#section-job-structure)
-   [Steps, uses, and run](#section-steps-uses-run)

[Variables and Secrets](#category-variables-and-secrets)

-   [Environment Variables](#section-env-scopes)
-   [Secrets and GITHUB\_TOKEN](#section-secrets)

[Matrix Builds](#category-matrix-builds)

-   [strategy.matrix](#section-matrix-strategy)

[Caching and Artifacts](#category-caching-and-artifacts)

-   [Dependency Caching](#section-caching)
-   [Build Artifacts](#section-artifacts)

[Reuse and Composition](#category-reuse-and-composition)

-   [Reusable Workflows](#section-reusable-workflows)
-   [Composite Actions](#section-composite-actions)

No commands found

Try adjusting your search term

## Workflow Triggers

The on key decides when a workflow runs. Most workflows use one or two of these, but knowing the full set saves you from cron hacks and manual reruns.

### Event Triggers

Fire on repository activity like pushes and pull requests, and filter down to the branches, tags, and paths you actually care about.

#### Best Practices

-   Scope triggers with paths and branches from day one. An unfiltered on:push burns runner minutes on every commit, including typo fixes.

#### Common Errors

-   **A workflow file exists but never runs:** The file must live in .github/workflows/ on the branch being pushed, and the on: events must match. A workflow only on a feature branch won't run for pushes to main.

#### Keywords

onpushpull\_requestpathsbranches

[Learn more](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows)

#### Trigger on push and pull request with filters

paths and paths-ignore skip runs that can't matter, which keeps queues short and saves runner minutes on docs commits.

Code

```
1on:2  # Run on pushes to main, but only when source or workflows change3  push:4    branches: [main]5    paths:6      - 'src/**'7      - '.github/workflows/**'8  # Run on PRs targeting main, ignoring docs-only changes9  pull_request:10    branches: [main]11    paths-ignore:12      - '**.md'
```

-   Use branches-ignore or paths-ignore for the inverse of branches/paths. Don't set both the positive and negative form for the same key.
-   Tag pushes need a tags filter (for example, tags:\['v\*'\]). A branches filter alone never matches a tag push.

#### Scheduled and manual triggers

workflow\_dispatch gives you a manual run button plus typed inputs, and schedule runs on cron without any external trigger.

Code

```
1on:2  # Cron runs in UTC. This is 02:00 UTC every day.3  schedule:4    - cron: '0 2 * * *'5  # A "Run workflow" button in the Actions tab, with inputs6  workflow_dispatch:7    inputs:8      environment:9        description: 'Target environment'10        type: choice11        options: [staging, production]12        default: staging
```

-   Scheduled workflows only run from the default branch, and GitHub can delay or skip them under heavy load. Don't rely on exact timing.
-   Reach an input at runtime with ${{ inputs.environment }} (or github.event.inputs.environment on older syntax).

### workflow\_call and Reuse

workflow\_call turns a workflow into something other workflows can invoke, which is the foundation of reusable pipelines.

#### Keywords

workflow\_callreusableinputsoutputs

[Learn more](https://docs.github.com/en/actions/using-workflows/reusing-workflows)

#### Expose a workflow as callable

A workflow with on:workflow\_call declares typed inputs and named secrets, so callers pass exactly what it needs and nothing more.

Code

```
1on:2  workflow_call:3    inputs:4      node-version:5        required: false6        type: string7        default: '20'8    secrets:9      npm-token:10        required: true
```

-   Reusable-workflow inputs are typed (string, number, boolean). This is stricter than workflow\_dispatch, which historically treated everything as a string.

## Jobs and Steps

Jobs run on runners and can depend on each other. Steps run in order inside a job, either calling an action with uses or running a shell command with run.

### Job Structure

runs-on picks the machine, needs builds a dependency graph, and if gates whether a job runs at all.

#### Best Practices

-   Pin runner labels (ubuntu-24.04) instead of ubuntu-latest when you need reproducible builds. latest moves when GitHub updates images.

#### Common Errors

-   **Job depends on unknown job:** The value in needs must match another job's key exactly. Check for a typo, and remember needs references the job id, not its name.

#### Keywords

jobsruns-onneedsifenvironment

[Learn more](https://docs.github.com/en/actions/using-jobs/using-jobs-in-a-workflow)

#### Chain jobs with needs and conditions

needs makes deploy wait for build to succeed, and the if guard stops deploys from running on pull requests or feature branches.

Code

```
1jobs:2  build:3    runs-on: ubuntu-latest4    steps:5      - run: echo "building"6
7  deploy:8    # Wait for build, and only deploy from main9    needs: build10    if: github.ref == 'refs/heads/main'11    runs-on: ubuntu-latest12    environment: production13    steps:14      - run: echo "deploying"
```

-   By default a needed job must succeed. Use if:always() or if:needs.build.result == 'success' to control behaviour when an upstream job fails.
-   An environment: with required reviewers pauses the job for manual approval before it runs.

#### Run a job across multiple OSes

Setting runs-on to a matrix value fans the same job out across every listed OS in parallel.

Code

```
1jobs:2  test:3    runs-on: ${{ matrix.os }}4    strategy:5      matrix:6        os: [ubuntu-latest, macos-latest, windows-latest]7    steps:8      - uses: actions/checkout@v49      - run: echo "testing on ${{ matrix.os }}"
```

### Steps, uses, and run

Every step either reuses a published action (uses) or runs shell commands (run). with passes inputs, env sets variables.

#### Common Errors

-   **Can't find action.yml, action.yaml or Dockerfile:** The uses reference is wrong. Marketplace actions look like owner/repo@ref. A local action needs a path like ./.github/actions/my-action.

#### Keywords

stepsusesrunwithshell

[Learn more](https://docs.github.com/en/actions/using-jobs/using-conditions-to-control-job-execution)

#### Combine actions and shell steps

uses pulls in a reusable action and with feeds it inputs, while run executes shell directly. The | starts a multi-line script block.

Code

```
1steps:2  # An action from the marketplace, pinned to a major version3  - uses: actions/checkout@v44
5  - uses: actions/setup-node@v46    with:7      node-version: '20'8      cache: 'npm'9
10  # A multi-line shell command with a step id and env11  - name: Build12    id: build13    env:14      NODE_ENV: production15    run: |16      npm ci17      npm run build
```

-   Give a step an id when a later step needs its outputs via ${{ steps.build.outputs.name }}.
-   Default shell is bash on Linux/macOS and pwsh on Windows. Override per step with shell:bash for consistency.

## Variables and Secrets

env holds plain configuration at three scopes, while secrets and the GITHUB\_TOKEN handle anything sensitive without ever printing it in logs.

### Environment Variables

Set env at the workflow, job, or step level. The narrowest scope wins when the same name is defined twice.

#### Keywords

envGITHUB\_ENVvariablesscope

[Learn more](https://docs.github.com/en/actions/learn-github-actions/variables)

#### env at three levels and dynamic values

A step-level env wins over job-level, which wins over workflow-level. To pass a computed value forward, append it to the $GITHUB\_ENV file.

Code

```
1env:2  APP_NAME: my-app        # available to every job and step3
4jobs:5  build:6    runs-on: ubuntu-latest7    env:8      LOG_LEVEL: debug    # available to every step in this job9    steps:10      - name: One-off value11        env:12          STAGE: build     # only this step13        run: echo "$APP_NAME $LOG_LEVEL $STAGE"14
15      - name: Export to later steps16        # Writing to $GITHUB_ENV persists a var to the next steps17        run: echo "VERSION=1.2.3" >> "$GITHUB_ENV"
```

-   Read repository or environment configuration variables (non-secret) with ${{ vars.NAME }}, distinct from ${{ secrets.NAME }}.

### Secrets and GITHUB\_TOKEN

Secrets are encrypted, masked in logs, and injected only where you reference them. Every run also gets a scoped GITHUB\_TOKEN for free.

#### Best Practices

-   Start every workflow with permissions:contents:read and add scopes only where a job needs them. The default token is broad otherwise.

#### Common Errors

-   **Resource not accessible by integration:** The GITHUB\_TOKEN lacks a scope. Add the needed permission (for example, contents:write or pull-requests:write) at the workflow or job level.

#### Keywords

secretsGITHUB\_TOKENpermissionsOIDC

[Learn more](https://docs.github.com/en/actions/security-guides/automatic-token-authentication)

#### Use secrets and pass them to a reusable workflow

Reference a secret with ${{ secrets.NAME }}, and forward secrets to a called workflow explicitly or with secrets:inherit.

Code

```
1jobs:2  publish:3    runs-on: ubuntu-latest4    steps:5      - env:6          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}7        run: npm publish8
9  call-shared:10    uses: ./.github/workflows/deploy.yml11    # Forward a named secret to the reusable workflow12    secrets:13      npm-token: ${{ secrets.NPM_TOKEN }}14    # Or hand over everything the caller has:15    # secrets: inherit
```

-   GitHub masks secret values in logs automatically, but a secret you echo after transforming (base64, for example) can leak. Don't print them.
-   Prefer OIDC (id-token:write plus a cloud role) over long-lived cloud keys stored as secrets.

#### Least-privilege GITHUB\_TOKEN

Set a restrictive top-level permissions block, then widen it per job. The auto-generated GITHUB\_TOKEN inherits exactly those scopes.

Code

```
1# Default to read-only for the whole workflow2permissions:3  contents: read4
5jobs:6  release:7    runs-on: ubuntu-latest8    # Grant just what this job needs9    permissions:10      contents: write11      packages: write12    steps:13      - run: gh release create v1.0.014        env:15          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

-   Secrets are not passed to workflows triggered by pull\_request from a fork, which is a deliberate safeguard against untrusted PRs.

## Matrix Builds

A matrix runs the same job across a grid of parameters (versions, OSes) in parallel, with include and exclude to fine-tune the combinations.

### strategy.matrix

List the axes and GitHub generates one job per combination. include adds extra entries, exclude removes specific ones.

#### Common Errors

-   **The whole matrix cancels when one job fails:** That's fail-fast:true, the default. Set strategy.fail-fast:false to let the remaining combinations finish and report independently.

#### Keywords

matrixstrategyincludeexcludefail-fast

[Learn more](https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs)

#### Multi-axis matrix with include and exclude

The two axes create 6 jobs, exclude removes one, and include appends a tailored entry, so you end up with a precise test grid.

Code

```
1strategy:2  # Keep other jobs running if one combo fails3  fail-fast: false4  # Cap parallel jobs so you don't exhaust runners5  max-parallel: 46  matrix:7    node: [18, 20, 22]8    os: [ubuntu-latest, windows-latest]9    # Drop a combo that isn't supported10    exclude:11      - node: 1812        os: windows-latest13    # Add an extra one-off combo with a custom flag14    include:15      - node: 2216        os: ubuntu-latest17        experimental: true
```

-   fail-fast:true (the default) cancels every other matrix job the moment one fails. Set it false when you want the full result grid.
-   Reference any axis value in the job with ${{ matrix.node }} or ${{ matrix.os }}.

## Caching and Artifacts

Caching reuses dependencies between runs to save time. Artifacts pass build outputs between jobs or hand them to you after the run.

### Dependency Caching

actions/cache keys a directory by a hash of your lockfile, restoring it when the hash matches and saving a fresh copy when it doesn't.

#### Best Practices

-   Put a lockfile hash in the key so the cache invalidates when dependencies change. A static key serves stale packages forever.

#### Keywords

cacheactions/cachekeyrestore-keyshashFiles

[Learn more](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows)

#### Cache dependencies keyed by lockfile

A cache hit restores ~/.npm instantly. On a miss, restore-keys grabs the newest partial match and the run saves a new cache under the exact key.

Code

```
1- uses: actions/cache@v42  with:3    path: ~/.npm4    # Exact key: changes when the lockfile changes5    key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}6    # Fallback prefixes for a partial (warm) restore7    restore-keys: |8      ${{ runner.os }}-npm-
```

-   Many setup actions have caching built in (setup-node with cache:'npm'). Prefer that over a manual actions/cache step when it exists.
-   Caches are scoped to a branch and its base. A PR can read the base branch cache but a branch can't read an unrelated branch's cache.

### Build Artifacts

Upload files from one job and download them in another, or keep them attached to the run for later inspection.

#### Common Errors

-   **Unable to find any artifacts for the associated workflow:** download-artifact ran before upload, or the name didn't match. Add needs: on the downloading job and confirm the name is identical.

#### Keywords

artifactsupload-artifactdownload-artifactretention

[Learn more](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts)

#### Pass a build between jobs

upload-artifact stores dist/ from the build job, and download-artifact pulls it into the deploy job by matching the artifact name.

Code

```
1jobs:2  build:3    runs-on: ubuntu-latest4    steps:5      - run: npm run build6      - uses: actions/upload-artifact@v47        with:8          name: dist9          path: dist/10          retention-days: 711
12  deploy:13    needs: build14    runs-on: ubuntu-latest15    steps:16      - uses: actions/download-artifact@v417        with:18          name: dist19          path: dist/20      - run: ls dist/
```

-   Artifacts are not caches. Use artifacts to move files between jobs or keep outputs, and use actions/cache to speed up dependency installs.
-   v4 of the artifact actions is not cross-compatible with v3. Keep upload and download on the same major version.

## Reuse and Composition

Kill the copy-paste. Reusable workflows share a whole pipeline across repos, and composite actions bundle a sequence of steps into one uses call.

### Reusable Workflows

Call an entire workflow from another with uses, passing inputs and secrets. This is how you standardize deploys across many repos.

#### Keywords

reusable workflowusesinputssecrets

[Learn more](https://docs.github.com/en/actions/using-workflows/reusing-workflows)

#### Call a reusable workflow

A job that sets uses to a workflow file calls it wholesale, forwarding typed inputs and either named secrets or secrets:inherit.

Code

```
1jobs:2  deploy:3    # Local reusable workflow4    uses: ./.github/workflows/deploy.yml5    with:6      node-version: '20'7    secrets:8      npm-token: ${{ secrets.NPM_TOKEN }}9
10  deploy-shared:11    # Reusable workflow from another repo, pinned to a tag12    uses: my-org/ci-workflows/.github/workflows/deploy.yml@v213    secrets: inherit
```

-   A caller can nest reusable workflows up to 4 levels deep. Beyond that GitHub refuses to run the chain.
-   Pin cross-repo reusable workflows to a tag or SHA, never a moving branch, so a change upstream can't silently alter your pipeline.

### Composite Actions

Bundle several steps into a single action defined by an action.yml, then call it like any marketplace action.

#### Common Errors

-   **shell is required for a composite action step that runs a command:** Add shell:bash (or pwsh) to each run step in the composite action's action.yml. Composite steps have no default shell.

#### Keywords

composite actionaction.ymlruns.usinginputs

[Learn more](https://docs.github.com/en/actions/creating-actions/creating-a-composite-action)

#### Define a composite action

runs.using:composite lets one action.yml wrap multiple steps, so callers replace a repeated block with a single uses:./.github/actions/setup.

Code

.github/actions/setup/action.yml

```
1name: 'Setup project'2description: 'Checkout, install Node, and restore deps'3inputs:4  node-version:5    default: '20'6runs:7  using: composite8  steps:9    - uses: actions/setup-node@v410      with:11        node-version: ${{ inputs.node-version }}12    # Composite run steps MUST declare a shell13    - run: npm ci14      shell: bash
```

-   Every run step inside a composite action must set shell explicitly. Omitting it is the most common composite-action error.

Was this useful?

## Tags

#GitHub Actions#Workflow#CI/CD#Matrix build#Secrets#Caching#Reusable workflow#Composite action

## Share

[Facebook](https://facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmkabumattar.com%2Fcheatsheets%2Fgithub-actions "Share on Facebook")[Twitter](https://twitter.com/intent/tweet/?text=GitHub%20Actions&url=https%3A%2F%2Fmkabumattar.com%2Fcheatsheets%2Fgithub-actions "Share on Twitter")[LinkedIn](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fmkabumattar.com%2Fcheatsheets%2Fgithub-actions&title=GitHub%20Actions&summary=The%20workflow%20YAML%20you%20write%20over%20and%20over.%20Triggers%2C%20jobs%20and%20steps%2C%20secrets%2C%20matrix%20builds%2C%20caching%2C%20artifacts%2C%20and%20reusable%20workflows%20in%20one%20reference.&source=https://mkabumattar.com "Share on LinkedIn")[WhatsApp](https://wa.me/?text=GitHub%20Actions%20https%3A%2F%2Fmkabumattar.com%2Fcheatsheets%2Fgithub-actions "Share on WhatsApp")[Telegram](https://t.me/share/url?url=https%3A%2F%2Fmkabumattar.com%2Fcheatsheets%2Fgithub-actions&text=GitHub%20Actions "Share on Telegram")[Reddit](https://www.reddit.com/submit?url=https%3A%2F%2Fmkabumattar.com%2Fcheatsheets%2Fgithub-actions&title=GitHub%20Actions "Share on Reddit")[Hacker News](http://news.ycombinator.com/submitlink?u=https%3A%2F%2Fmkabumattar.com%2Fcheatsheets%2Fgithub-actions&t=GitHub%20Actions "Share on Hacker News")[Pinterest](https://pinterest.com/pin/create/button/?url=https%3A%2F%2Fmkabumattar.com%2Fcheatsheets%2Fgithub-actions&media=&description=The%20workflow%20YAML%20you%20write%20over%20and%20over.%20Triggers%2C%20jobs%20and%20steps%2C%20secrets%2C%20matrix%20builds%2C%20caching%2C%20artifacts%2C%20and%20reusable%20workflows%20in%20one%20reference. "Share on Pinterest")[Email](<mailto:?subject=GitHub%20Actions&body=Check out this article: https%3A%2F%2Fmkabumattar.com%2Fcheatsheets%2Fgithub-actions>)

## Comments

## You might also enjoy

More posts on similar topics

## [AWS CLI](/cheatsheets/aws-cli)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   Cloud Computing
-   AWS
-   DevOps
-   CLI
-   Automation

The AWS Command Line Interface (CLI) lets you orchestrate infrastructure, move cloud data, and configure security entirely from the terminal. This reference covers configuring identity profiles, manag

#AWS CLI#EC2#S3+4 tags

[read more](/cheatsheets/aws-cli)

## [YAML](/cheatsheets/yaml)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   Data Format
-   YAML
-   Configuration
-   Data Serialization
-   DevOps
-   Infrastructure

YAML (YAML Ain't Markup Language) is a human-friendly data serialization language widely used for configuration files, data exchange, and infrastructure-as-code. It emphasizes readability and uses ind

#YAML#Configuration#Data Serialization+6 tags

[read more](/cheatsheets/yaml)

## [Linux Networking](/cheatsheets/linux-networking)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   Linux
-   Networking
-   DevOps
-   CLI
-   SysAdmin

Every Linux box speaks the network through a small set of tools, and knowing them turns "the network is broken" into a specific, fixable answer. This cheatsheet covers the modern stack: ip for inter

#Linux networking#Ip command#Tcpdump+5 tags

[read more](/cheatsheets/linux-networking)

## [kubectl](/cheatsheets/kubectl)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   DevOps
-   Kubernetes
-   CLI
-   Container Orchestration
-   Cloud Native

kubectl is the command-line tool you use to talk to a Kubernetes cluster. Whatever you can do through a dashboard, you can do faster here: deploy apps, inspect resources, stream logs, run commands ins

#Kubectl#Kubernetes#K8s+5 tags

[read more](/cheatsheets/kubectl)

## [Terraform Cheatsheet](/cheatsheets/terraform)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   Infrastructure as Code
-   DevOps

A practical cheatsheet covering the Terraform CLI commands engineers reach for constantly: init, plan, apply, import, taint, and state operations. It also covers the key HCL patterns for variables, lo

#Terraform#HCL#State management+3 tags

[read more](/cheatsheets/terraform)

## [Docker Swarm](/cheatsheets/docker-swarm)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   DevOps
-   Docker
-   Containers
-   Orchestration
-   Deployment

This cheatsheet is a reference for managing Docker Swarm clusters, services, and stacks. It covers the commands and best practices for scaling, updating, and monitoring your swarm applications.

#Docker Swarm#Orchestration#Containers+4 tags

[read more](/cheatsheets/docker-swarm)

6 related posts
