Composite Actions in GitHub Actions: Encapsulating Workflow Steps for Modular CI/CD

GitHub Actions provides a robust framework for automating software development workflows, but as projects scale, the repetition of identical steps across multiple repositories becomes a significant maintenance burden. Composite actions represent an advanced feature designed to address this challenge by encapsulating a sequence of actions into a single, reusable entity. This approach enhances modularity and efficiency, allowing teams to bundle multiple workflow steps—such as database migrations, Docker builds, or security scans—into a unified component. Unlike reusable workflows, which operate at the workflow level and can function without a repository checkout, composite actions operate at the step level and inherently require a repository checkout for utilization. Understanding the mechanics, structure, and integration of composite actions is essential for optimizing CI/CD processes and enforcing consistent standards across development teams.

Defining Composite Actions and Their Structure

A composite action is distinct from traditional Docker or JavaScript actions because it allows developers to group multiple commands or even other actions into a single, reusable unit. This distinction is crucial for maintaining clean, modular codebases. The definition of a composite action is housed in a YAML file, conventionally named action.yml, located at the root of the repository. While this is the standard convention, the file does not strictly need to be named action.yml nor reside at the top level; multiple composite actions can exist within a single repository by placing them in separate directories, each with its own definition file.

The core of the action.yml file begins with action metadata, which provides identity and purpose. For instance, an action might define a name of "Database Migration" and a description indicating it migrates a Postgres service spun up for testing purposes. Following the metadata, inputs are defined to allow the action to accept dynamic values from the calling workflow.

The actual execution logic is defined within the runs section. The using field is explicitly set to composite to signal to GitHub Actions that this is a multi-step entity rather than a Docker container or JavaScript script. Within the steps array, individual commands are defined. Each step specifies a shell (such as bash, sh, pwsh, or python) and a run command. The choice of shell determines the syntax and features available for the execution. For example, setting the shell to bash allows for the use of Bash-specific syntax and features within the run fields. This structure ensures that complex sequences of commands are executed consistently every time the action is invoked.

Implementing a Database Migration Composite Action

To illustrate the practical application of composite actions, consider a scenario involving a database migration for a Postgres service. The implementation begins by initializing a new GitHub repository, which serves as the foundation for the action. After creating the repository on GitHub, it is cloned to the local machine using a command such as git clone [email protected]:Ikeh-Akinyemi/composite-github-action.git. Note that the repository URL must be updated to match the specific project.

Once cloned, an action.yml file is created in the root directory. The file is populated with the necessary metadata, inputs, and steps. For a database migration action, the inputs might include parameters for database credentials or configuration files. The steps would then execute the migration logic, potentially utilizing SQL scripts. The action might expect a source directory for migration files, such as db/migrations, containing files like 000001_init_db.up.sql and 000001_init_db.down.sql. By encapsulating these details, the action transforms complex migration tasks into a single, streamlined step in a workflow.

Publishing and Versioning Composite Actions

After the action.yml file is finalized, the action must be published to GitHub to make it available for use in workflows. This involves standard Git operations. First, the changes are staged and committed:

bash git add . git commit -m "Publish composite action" git push origin main

For better management and to facilitate controlled updates, it is recommended to tag the action with a version, such as v1 or v2. Versioning allows workflows to reference specific, stable releases of the action. The tagging process is executed as follows:

bash git tag -a v1 -m "Initial release of db migration action" git push origin v1

When managing multiple composite actions within a single repository, it is critical to understand that Git tags apply to the entire repository. If one composite action is updated and a new tag is created, that version number applies to all composite actions in the repository, even if others remain unchanged. This nuance requires careful planning to avoid unintended updates to unrelated components. Alternatively, actions can be referenced by commit SHA or branch name, providing flexibility in how updates are managed.

Integrating Composite Actions into Workflows

To utilize a composite action, it must be integrated into a workflow file, typically located in the .github/workflows/ directory. For example, a test.yml file can be created to trigger the action on push events to the main branch. While it is technically possible for a composite action to reside in the same repository as the workflows that call it, it is recommended to keep actions in separate repositories for clarity and modularity.

The workflow file defines the metadata, including the name and triggering events. For instance:

yaml name: Test running composite github action on: push: branches: [ main ]

Within the jobs section, the composite action is invoked using the uses keyword. The full format for referencing a composite action is {owner}/{repo}/.github/workflows/{filename}@{ref}. However, for composite actions defined in action.yml at the root or in subdirectories, the reference typically follows the pattern {owner}/{repo}@{ref}. For example, Ikeh-Akinyemi/composite-github-action@v1 points to version one of the action. Instead of a tag, a specific commit SHA (e.g., 4a3ddaf9b2914638ca2be9f4b21af5d01d9d3e22) or a branch name (e.g., main) can also be used.

Inputs defined in the action are passed via the with field in the workflow. This allows the calling workflow to customize the action's behavior without altering the action's core logic. For example, the migration_files_source input might point to the db/migrations directory in the calling repository. After the action executes, it is beneficial to capture the outcome, such as reporting the migration status, to ensure visibility into the CI/CD process.

Composite Actions vs. Reusable Workflows

GitHub Actions offers two primary mechanisms for reusability: composite actions and reusable workflows. Understanding the distinction between them is vital for designing an effective CI/CD strategy. Reusable workflows allow teams to define entire workflows that can be called from other workflows, promoting standardization across multiple projects. However, reusable workflows cannot call other reusable workflows, and they function without a repository checkout in the calling workflow.

In contrast, composite actions bundle multiple steps into a single action and require a repository checkout for utilization. This makes composite actions ideal for encapsulating specific tasks or sequences of commands that need access to the repository context, such as running tests, building Docker images, or executing Terraform plans. When used together, composite actions and reusable workflows form a clean, modular, and DRY (don’t repeat yourself) strategy. Composite actions handle the granular steps, while reusable workflows orchestrate the high-level flow, enabling teams to scale their CI/CD pipelines efficiently.

Conclusion

Composite actions provide a powerful mechanism for enhancing the modularity and efficiency of GitHub Actions workflows. By encapsulating complex sequences of steps into reusable entities, developers can reduce redundancy, enforce consistency, and simplify maintenance. The ability to define actions with metadata, inputs, and multi-step execution logic allows for precise control over CI/CD processes. Whether managing database migrations, security scans, or build steps, composite actions offer a robust solution for scaling automation efforts. As teams continue to refine their development pipelines, leveraging composite actions alongside reusable workflows ensures that standards are maintained and workflows remain streamlined and efficient. Tools like Earthly Lunar further assist in enforcing these standards directly in pull requests, helping teams manage complexity as they scale.

Sources

  1. GitHub Actions Composite Actions Tutorial
  2. GitHub Actions Composite vs Reusable Workflows

Related Posts