iTranslated by AI
The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🦾
Patterns for Organizing GitHub Actions
GitHub Actions can be started with a single YAML file, but as you try to write multiple workflows or jobs, redundant parts increase rapidly.
For example:
- The build and test steps are the same for both merging and pushing, but you want to perform release processing only during a merge.
- You want to separate jobs, but you need to perform the same setup every time.
- You want to parallelize parts that can be checked concurrently (e.g., running tests and lints) as much as possible. If they are serial, one failure prevents the other from running, and you don't get the results.
To meet these requirements while reducing code duplication and keeping things organized, I have my own set of patterns for writing them, so I will summarize those methods here.
Summary
- Divide roles into Single Workflows and Composite Workflows
- Role of Single Workflow
- Called only via
workflow_call. - Created for units like build, test, or release processing.
- Called only via
- Role of Composite Workflow
- Called by external triggers such as
on pushoron schedule. - Combines and arranges multiple Single Workflows as Jobs.
- Example: Build -> Test for testing on push.
- Example: Build -> Test -> Release for releasing on merge.
- Called by external triggers such as
- Role of Single Workflow
- Exchange artifacts between multiple Jobs using cache
- Utilize composite actions
Sample
composite action
Example of an action that performs common setup
name: common-setup
description: |
action for common setup of node/pnpm/bun
inputs:
NODE_VERSION:
description: ''
required: false
default: '20'
PNPM_VERSION:
description: ''
required: false
default: '8'
BUN_VERSION:
description: ''
required: false
default: '1'
REQUIRE_BUN:
description: ''
required: false
default: 'true'
runs:
using: 'composite'
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.NODE_VERSION }}
- uses: pnpm/action-setup@v3
with:
version: ${{ inputs.PNPM_VERSION }}
run_install: false
- uses: oven-sh/setup-bun@v1
if: inputs.REQUIRE_BUN == 'true'
with:
bun-version: ${{ inputs.BUN_VERSION }}
Single Workflow
name: _install-pnpm-deps
on:
workflow_call:
inputs:
use-pnpm-store-cache:
type: boolean
required: false
default: true
jobs:
cache-and-install:
runs-on: ubuntu-latest
timeout-minutes: 1
env:
PNPM_STORE_PATH: ''
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/common-setup
with:
REQUIRE_BUN: 'false'
- uses: ./.github/actions/restore-pnpm-store-cache
id: restore-pnpm-store-cache
if: inputs.use-pnpm-store-cache == true
- run: pnpm install
if: steps.restore-pnpm-store-cache.outputs.cache-hit != 'true'
- name: Get pnpm store directory
run: echo "PNPM_STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Save pnpm-store cache
uses: actions/cache/save@v4
if: steps.restore-pnpm-store-cache.outputs.cache-hit != 'true'
with:
path: ${{ env.PNPM_STORE_PATH }}
key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
Composite Workflow
name: Build🔨, Verify🧪 on push
on:
push:
jobs:
install-pnpm-deps:
uses: ./.github/workflows/_install-pnpm-deps.yaml
build-uniproc:
needs: install-pnpm-deps
uses: ./.github/workflows/_build-uniproc.yaml
build-sveltecf:
needs:
- install-pnpm-deps
- build-uniproc
uses: ./.github/workflows/_build-sveltecf.yaml
verify-uniproc:
needs: build-uniproc
uses: ./.github/workflows/_verify-uniproc.yaml
verify-sveltecf:
needs: build-sveltecf
uses: ./.github/workflows/_verify-sveltecf.yaml
Execution Results
When including failures

When all succeeded

Directory Structure

Supplement
You can grasp the overall atmosphere by reading the summary and samples above.
Here are a few additional notes.
- Write in the order of Single Workflow → Composite Workflow. As you continue writing and start noticing common parts, extract them into a composite action.
- Composite action syntax: Metadata syntax for GitHub Actions - GitHub Docs
-
Because the cache is used frequently, be mindful of GitHub Storage usage in private repositories, etc.
- While up to 1GB/month is available for free, the cache size can grow larger than expected.
- Starting the filename of a Single Workflow with
_makes it easier to distinguish from Composite Workflows.
References
-
Step/Job/Workflow design theory in GitHub Actions
- About the specifications, characteristics, and proper usage of Step/Job/Workflow
-
Optimizing GitHub Actions tests by splitting jobs into smaller pieces
- About optimizing tests using cache
- Creating a composite action - GitHub Docs
Discussion