5.1 Action Architecture & action.yml Specification
Key Takeaways
- The action.yml (or action.yaml) metadata manifest is the mandatory declarative contract defining an action's name, description, inputs, outputs, branding, and execution runtime (runs.using).
- GitHub Actions supports three distinct action execution models: JavaScript actions (Node.js runtime), Docker container actions (Linux container runtime), and Composite actions (multi-step shell and action pipelines).
- Action inputs support validation keys including description, required, default, and deprecationMessage, while outputs define values mapped from execution steps or runtime code.
- Actions can be published in dedicated public or private repositories (referenced via owner/repo@ref) or stored in subdirectories of a workflow repository (referenced locally via uses: ./.github/actions/my-action).
- The branding block configures visual metadata for GitHub Marketplace, specifying a Feather icon and background badge color.
Action Architecture & action.yml Specification
In GitHub Actions, Actions represent the smallest reusable unit of computational work within a workflow step. While workflows orchestrate jobs and execution graphs, actions encapsulate specialized logic—such as configuring a toolchain, compiling binaries, interacting with the GitHub REST API, scanning container images, or publishing deployment artifacts.
Authoring custom actions allows organizations to standardize CI/CD patterns, eliminate boilerplate code across repositories, encapsulate security policies, and share automated solutions across the broader GitHub ecosystem. On the GitHub Actions Certification (GH-200) exam, custom action authoring accounts for a substantial portion of Domain 3 ("Author and Maintain Actions"). Candidates must demonstrate deep mastery of metadata schemas, runtime engine selection, and referencing syntax.
1. The Action Manifest: action.yml
Every custom action requires a metadata manifest file named either action.yml or action.yaml placed in the root of the action's repository or within a dedicated subfolder. This file is written in YAML syntax and defines the contract between the action author and the workflow caller.
+-----------------------------------------------------------------------------+
| ACTION.YML MANIFEST ARCHITECTURE |
| |
| +---------------------------------------------------------------------+ |
| | METADATA IDENTIFIERS | |
| | - name: 'Enterprise Security Linter' | |
| | - author: 'DevOps Security Team' | |
| | - description: 'Audits source code for credential leaks and CVEs' | |
| +---------------------------------------------------------------------+ |
| | |
| v |
| +----------------------------------+----------------------------------+ |
| | | | |
| v v v |
| +--------------------+ +--------------------+ +--------------------+ |
| | INPUTS DEFINITION | | OUTPUTS DEFINITION | | BRANDING DEFINITION| |
| | - target-path | | - scan-status | | - icon: 'shield' | |
| | - fail-on-critical | | - vulnerabilities | | - color: 'red' | |
| +--------------------+ +--------------------+ +--------------------+ |
| | |
| v |
| +---------------------------------------------------------------------+ |
| | EXECUTION ENGINE: runs.using | |
| | - 'composite' (Combines run / uses steps) | |
| | - 'node20' (Executes JavaScript bundle on Node 20 runtime) | |
| | - 'docker' (Builds or pulls Linux container image) | |
| +---------------------------------------------------------------------+ |
+-----------------------------------------------------------------------------+
Top-Level Manifest Keys
| Key | Required | Type | Description & Purpose |
|---|---|---|---|
name | Yes | String | The human-readable name of the action displayed in workflow logs and the GitHub Marketplace. |
author | No | String | The name of the individual developer or organization maintaining the action. |
description | Yes | String | A detailed summary explaining what the action does, its prerequisites, and its operational behavior. |
inputs | No | Object | Defines the input parameters accepted by the action, including data types, defaults, and requirements. |
outputs | No | Object | Defines the output variables produced by the action and exposed to subsequent workflow steps. |
runs | Yes | Object | Configures the underlying execution engine, runtime environment, entrypoint scripts, and lifecycle hooks. |
branding | No | Object | Customizes the visual badge icon and background color when published to the GitHub Marketplace. |
2. Deep Dive: inputs and outputs Schema
Parameterizing with inputs
The inputs: map defines parameters callers can pass via the with: keyword in a workflow step. Each input key supports four configuration parameters:
inputs:
api-token:
description: 'Authentication token for API communication'
required: true
scan-mode:
description: 'Scanning mode verbosity'
required: false
default: 'standard'
legacy-flag:
description: 'Deprecated flag replaced by scan-mode'
required: false
deprecationMessage: 'The legacy-flag input is deprecated. Use scan-mode instead.'
description(Required): Clear explanation of the input's purpose and expected values.required(Optional, Boolean): Iftrue, the caller workflow step must provide this input; if omitted, GitHub Actions aborts the job before execution with a validation error. Defaults tofalse.default(Optional, String): The fallback value injected if the caller does not supply this input. Note that all defaults inaction.ymlare interpreted as strings.deprecationMessage(Optional, String): When a workflow supplies this input, the runner logs a warning annotation containing this message, informing developers to migrate to a newer parameter.
Declaring outputs
The outputs: block registers data generated during execution for use downstream:
outputs:
scan-passed:
description: 'Boolean flag indicating whether the security scan succeeded'
value: ${{ steps.audit-step.outputs.passed }} # Mandatory for composite actions
findings-count:
description: 'Total number of detected vulnerabilities'
value: ${{ steps.audit-step.outputs.total_cves }}
[!IMPORTANT] For Composite Actions, the
value:parameter is mandatory for every output inaction.yml. It binds the action's public output key to a specific internal step's output (${{ steps.<step_id>.outputs.<output_key> }}). For JavaScript and Docker actions, outputs are registered dynamically at runtime via the core SDK or$GITHUB_OUTPUT.
3. The branding Block for GitHub Marketplace
If you publish an action to the GitHub Marketplace, the branding block defines the visual badge rendered on the listing:
branding:
icon: 'shield'
color: 'purple'
icon: Must match an official Feather icon name (e.g.,activity,alert-circle,archive,award,box,check-circle,cloud,code,cpu,database,download,file-text,git-branch,globe,layers,lock,package,play,server,shield,terminal,tool,upload-cloud,zap).color: Restricted to standard Marketplace badge colors:white,yellow,blue,green,orange,red,purple, orgray-dark.
4. Comparing the Three Action Types
GitHub Actions supports three distinct runtime paradigms under runs.using:
+-----------------------------------------------------------------------------+
| COMPARISON OF ACTION EXECUTION TYPES |
| |
| +-----------------------+-----------------------+---------------------+ |
| | JAVASCRIPT ACTION | COMPOSITE ACTION | DOCKER ACTION | |
| +-----------------------+-----------------------+---------------------+ |
| | - runs.using: 'node20'| - runs.using: 'comp..'| - runs.using: 'doc..'| |
| | - Native Node runtime | - Multiple run/uses | - Linux container | |
| | - Linux, macOS, Win | - Linux, macOS, Win | - Linux ONLY | |
| | - Sub-second startup | - Sub-second startup | - Container pull/bld| |
| | - Bundled dist/index | - Shell scripts | - System tooling | |
| +-----------------------+-----------------------+---------------------+ |
+-----------------------------------------------------------------------------+
Comprehensive Action Types Comparison Matrix
| Characteristic | JavaScript Actions | Composite Actions | Docker Container Actions |
|---|---|---|---|
runs.using Identifier | 'node20' | 'composite' | 'docker' |
| Operating System Support | Linux, macOS, Windows | Linux, macOS, Windows | Linux ONLY (Fails on macOS/Windows) |
| Startup Overhead | Extremely Low (< 1 second) | Extremely Low (< 1 second) | High (Image pull or local docker build) |
| Execution Speed | Very Fast (Direct Node.js execution) | Fast (Executes native shell processes) | Moderate (Container namespace isolation) |
| Runtime Environment | Node.js 20 bundled on runner | Host runner shell (bash, pwsh, etc.) | Custom Linux image (Dockerfile / registry) |
| Dependency Management | Pre-bundled via @vercel/ncc | Uses tools pre-installed on runner | Packaged inside the container filesystem |
| Nested Actions Support | No (Uses Octokit / Node libraries) | Yes (Can execute uses: steps) | No (Executes container entrypoint) |
Lifecycle Hooks (pre/post) | Yes (pre, main, post) | No | Yes (pre-entrypoint, post-entrypoint) |
| Primary Use Cases | API integrations, PR bots, fast logic | Multi-step workflow scripts, reusable setups | Heavy CLI toolchains, C/Rust, custom OS packages |
5. Referencing Custom Actions in Workflows
Workflows invoke custom actions using the uses: keyword. Actions can be consumed from external repositories or from within the local repository.
Referencing Remote Actions
steps:
# Pinned to a major release tag
- name: Run Security Audit
uses: enterprise-org/security-linter@v2
with:
scan-mode: 'deep'
# Pinned to an immutable commit SHA (Recommended for security)
- name: Run Build Action
uses: enterprise-org/builder-action@8a7b9c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b
# Stored in a repository subdirectory
- name: Run Sub-Action
uses: enterprise-org/monorepo/actions/setup-db@v1
Referencing Local Actions
When an action is authored inside the same repository as the workflow (often in .github/actions/), use relative directory paths starting with ./:
jobs:
ci:
runs-on: ubuntu-latest
steps:
# 1. You MUST check out the repository first so local files exist on the runner
- name: Checkout Repository Code
uses: actions/checkout@v4
# 2. Invoke local action using relative path
- name: Execute In-Tree Linter
uses: ./.github/actions/custom-linter
with:
target-path: './src'
[!CAUTION] The Local Action Checkout Requirement: When using
uses: ./.github/actions/my-action, the runner looks for the action definition on the runner's local filesystem at$GITHUB_WORKSPACE/.github/actions/my-action. If you do not runactions/checkoutbefore calling a local action, the directory does not exist, and the step fails immediately withAction directory does not exist.
An action maintainer needs to deprecate an optional input named auth-token in favor of a new input named api-key. How should the action.yml manifest be structured so that workflows passing auth-token receive a warning annotation in the workflow logs without breaking the build?
A DevOps team is developing a custom action that must execute on Linux, macOS, and Windows GitHub-hosted runners with minimal startup latency (< 1 second). The action executes complex business logic, calls the GitHub REST API, and parses JSON payloads. Which action type should the team select?
A workflow file contains a step invoking a local custom action: uses: ./.github/actions/setup-toolchain. When the workflow runs, the step fails immediately with an error indicating that the action path cannot be found. What is the most likely cause of this failure?