Study guide
5.2 Authoring Composite Actions
GitHub Actions Certification GH-200
Track your progress as you read
Finished sections get a check mark.
1 Chapter 1: Introduction & Exam Overview
1.1 Exam Structure, Format & Scoring 1.2 GitHub Actions Architecture & Core Mental Model 1.3 Hands-On Preparation & Exam Day Strategy
2 Chapter 2: Workflow Triggers & Event Architecture
2.1 Webhook & Code Events: push, pull_request, and pull_request_target 2.2 Scheduled & Manual Triggers: schedule, workflow_dispatch, and repository_dispatch 2.3 Release, Issue, and Dynamic Lifecycle Events 2.4 Branch, Tag, and Path Filtering Patterns
3 Chapter 3: Jobs, Matrix Strategies & Execution Control
3.1 Job Anatomy, Step Execution & Job Dependencies (needs) 3.2 Contexts, Expressions & Functions 3.3 Environment Variables & Workflow Commands 3.4 Matrix Strategies & Multi-Platform Builds 3.5 YAML Anchors, Aliases & Merge Keys
4 Chapter 4: Reusable Workflows & Troubleshooting
4.1 Authoring & Calling Reusable Workflows (workflow_call) 4.2 Comparing Reusable Workflows, Composite Actions & Starter Workflows 4.3 Troubleshooting Workflow Runs & Debug Logging 4.4 Concurrency Controls & Status Check Functions
5 Chapter 5: Authoring Custom Actions
5.1 Action Architecture & action.yml Specification 5.2 Authoring Composite Actions 5.3 Building JavaScript Actions 5.4 Building Docker Container Actions
6 Chapter 6: Action Distribution, Marketplace & Advanced Action Types
6.1 Action Versioning, Semantic Releases & Tagging 6.2 Publishing Actions to GitHub Marketplace 6.3 Service Containers & Network Topology 6.4 actions/github-script & Workflow Tooling
7 Chapter 7: Runner Infrastructure & Self-Hosted Runners
7.1 GitHub-Hosted Runners & Larger Runners 7.2 Self-Hosted Runner Setup, Architecture & Communication 7.3 Self-Hosted Runner Security & Ephemeral Runners 7.4 Runner Groups, Labels & Organization Routing
8 Chapter 8: Enterprise Governance, Policies & Environments
8.1 Enterprise & Organization Action Policies 8.2 Environments, Protection Rules & Deployment Approvals 8.3 Repository Rulesets, Required Status Checks & Governance 8.4 Usage Limits, Billing Multipliers & Usage Metrics
9 Chapter 9: Secrets, Permissions & OIDC Cloud Authentication
9.1 Encrypted Secrets Architecture & Scopes 9.2 GITHUB_TOKEN & Least Privilege Configuration 9.3 OpenID Connect (OIDC) & Workload Identity Federation 9.4 Script Injection Prevention & Secure Workflow Patterns
10 Chapter 10: Supply Chain Hardening & Performance Optimization
10.1 Supply Chain Hardening: SHA Pinning, Dependabot & Artifact Attestations 10.2 Caching Dependencies with `actions/cache` 10.3 Artifacts Management & Job Handoff 10.4 Workflow Performance, Cost Optimization & Architecture Review
5.2 Authoring Composite Actions
Key Takeaways Explain This Topic AI
Composite actions combine multiple workflow steps (including shell commands and nested actions) into a single reusable action definition using runs.using: 'composite'.
Every single run: step in a composite action MUST explicitly declare the shell: parameter (e.g., shell: bash or shell: pwsh); failure to specify shell causes an immediate parser validation error.
Inputs are referenced using ${{ inputs.<input_name> }}, while action outputs must be explicitly mapped in action.yml using value: ${{ steps.<step_id>.outputs.<output_name> }}.
The ${{ github.action_path }} context variable resolves to the absolute filesystem path of the action directory, enabling companion scripts inside the action repository to be executed reliably.
Composite actions do not support services:, top-level env: blocks, or runner specification (runs-on); environment variables must be declared per step under steps[].env:.
Last updated: August 2026
Authoring Composite Actions
Composite Actions allow workflow authors to consolidate multiple sequential workflow steps—including shell scripts and nested GitHub Actions—into a single reusable action. Prior to composite actions, packaging reusable multi-step automation required writing custom Node.js code or building Docker container images.
With composite actions, developers can write pure YAML to bundle setup routines, custom shell tooling, test suites, and deployment logic into modular, maintainable units. Understanding composite action mechanics, mandatory syntax rules, and context resolution is essential for the GH-200 examination.
1. Anatomy of a Composite Action
A composite action is defined in an action.yml file where runs.using is set to 'composite' . Unlike standard workflow jobs, composite actions define their execution sequence under runs.steps . +-----------------------------------------------------------------------------+
- | COMPOSITE ACTION DIRECTORY LAYOUT |
- | |
- | .github/actions/composite-deploy/ |
- | ├── action.yml # Manifest declaring inputs, outputs & steps |
- | ├── scripts/ |
- | │ ├── validate-config.sh # Companion validation script |
- | │ └── deploy-k8s.py # Main execution script |
- | └── README.md # Usage documentation & examples |
+-----------------------------------------------------------------------------+ name: 'Composite Kubernetes Deployer'
description: 'Installs kubectl, validates manifests, and deploys to target cluster'
inputs:
kubeconfig-data:
- description: 'Base64 encoded kubeconfig string'
- required: true
namespace:
- description: 'Target Kubernetes namespace'
- required: false
- default: 'default'
manifest-path:
- description: 'Path to deployment manifests'
- required: true
outputs:
deployment-status:
- description: 'Final rollout status'
- value: ${{ steps.rollout.outputs.status }}
runs:
using: 'composite'
steps:
- name: Setup Kubectl Toolchain
- uses: azure/setup-kubectl@v3
with:
version: 'v1.28.0'
- name: Configure Kubeconfig File
- shell: bash
- run: |
- mkdir -p "$HOME/.kube"
- echo "${{ inputs.kubeconfig-data }}" | base64 --decode > "$HOME/.kube/config"
- chmod 600 "$HOME/.kube/config"
- name: Validate Manifests
- shell: bash
- run: ${{ github.action_path }}/scripts/validate-config.sh "${{ inputs.manifest-path }}"
- name: Apply Deployment & Capture Status
- id: rollout
- shell: bash
env:
- TARGET_NS: ${{ inputs.namespace }}
- run: |
- kubectl apply -n "$TARGET_NS" -f "${{ inputs.manifest-path }}"
- STATUS=$(kubectl rollout status deployment -n "$TARGET_NS" --timeout=120s)
- echo "status=${STATUS}" >> "$GITHUB_OUTPUT"
- 2. The Mandatory shell: Rule
The most critical syntax rule in composite actions—and one of the most frequently tested topics on the GH-200 exam—is the mandatory shell: requirement .
In standard workflow files ( .github/workflows/*.yml ), if you omit shell: in a run: step, GitHub Actions defaults to bash on Linux/macOS and pwsh (or cmd ) on Windows.
Caution
Composite Action Parsing Requirement: In a composite action.yml , every single run: step MUST explicitly specify the shell: keyword . Composite actions do not inherit default shells from the runner or caller workflow. Omitting shell: causes the workflow parser to reject the action with a fatal schema validation error before execution begins.
- Supported Shell Keywords in Composite Actions
- shell: bash (Bourne-Again SHell — cross-platform on hosted runners via MSYS2 on Windows)
- shell: sh (POSIX standard shell on Unix)
- shell: pwsh (PowerShell Core — cross-platform on Linux, macOS, and Windows)
- shell: powershell (Windows Desktop PowerShell)
- shell: cmd (Windows Command Prompt)
- shell: python (Directly executes Python script if Python is in $PATH )
- 3. Context Variables: github.action_path vs GITHUB_WORKSPACE
When a composite action includes standalone helper scripts, configuration templates, or binary utilities within its own repository, referencing their location on disk requires careful use of context variables. +-----------------------------------------------------------------------------+
- | FILESYSTEM LOCATIONS DURING COMPOSITE RUN |
- | |
- | +---------------------------------------------------------------------+ |
- | | RUNNER HOST FILESYSTEM | |
- | | | |
- | | $GITHUB_WORKSPACE (/home/runner/work/caller-repo/caller-repo) | |
- | | └── Target application source code checked out by caller | |
- | | | |
- | | ${{ github.action_path }} (/home/runner/work/_actions/org/act/v1) | |
- | | ├── action.yml | |
- | | └── scripts/validate-config.sh <-- Action's internal script! | |
- | +---------------------------------------------------------------------+ |
- +-----------------------------------------------------------------------------+
The Critical Distinction:
$GITHUB_WORKSPACE / ${{ github.workspace }} : Points to the working directory of the caller repository where actions/checkout unpacked the user's project files.
${{ github.action_path }} : Resolves to the absolute path where the composite action itself is located on the runner. For remote actions, this is inside the runner's internal _actions/ cache directory (e.g., /home/runner/work/_actions/owner/repo/v1 ). # ✅ CORRECT: Executes script located inside the action repository
- name: Run Internal Action Script
- shell: bash
- run: bash "${{ github.action_path }}/scripts/audit.sh"
- # ❌ INCORRECT: Looks for script inside caller's repository root
- name: Run Failed Script
- shell: bash
- run: bash "./scripts/audit.sh"
- 4. Input & Output Mapping Mechanics
- Accessing Inputs
Within composite action steps, input parameters are referenced using standard expression syntax: ${{ inputs.<input_name> }} .
Passing Outputs to Callers
Passing data from a composite action back to the calling workflow requires two coordinated steps:
Step Output Generation: An internal step writes key-value pairs to the $GITHUB_OUTPUT file descriptor.
Action Manifest Output Mapping: The top-level outputs: block in action.yml maps each public output to the internal step's output using the value: key. # action.yml
outputs:
release-id:
- description: 'The created release ID'
- value: ${{ steps.create-rel.outputs.rel_id }} # REQUIRED mapping
runs:
using: 'composite'
steps:
- id: create-rel
- shell: bash
- run: |
- ID=$(curl -s https://api.example.com/release)
- echo "rel_id=${ID}" >> "$GITHUB_OUTPUT" # caller-workflow.yml
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: my-composite
- uses: ./.github/actions/release-action
- run: echo "Created Release: ${{ steps.my-composite.outputs.release-id }}"
- 5. Architectural Limitations of Composite Actions
While composite actions are versatile and lightweight, they have specific architectural boundaries that differentiate them from workflows and JavaScript actions:
No services: Container Support: Composite actions cannot define Docker service containers (e.g., PostgreSQL, Redis). Service containers must be defined at the workflow job level.
No Top-Level env: Block in action.yml : Composite actions cannot declare global environment variables under the root of action.yml . Environment variables must be declared within individual steps under steps[].env: .
No runs-on or Job-Level Properties: Composite actions execute within whatever runner environment the caller job provides.
No Native secrets: Declaration in action.yml : Composite manifests cannot define a top-level secrets: schema. Caller workflows pass secrets explicitly via inputs: or inject them into step-level env: variables.