GitHub Actions Resource Optimization and Automated Cleanup Strategies

The efficiency of a Continuous Integration and Continuous Deployment (CI/CD) pipeline is often measured not only by the speed of its execution but by the meticulous management of the resources it consumes. In the ecosystem of GitHub Actions, resource exhaustion typically manifests in three primary dimensions: the accumulation of orphaned workflow runs, the depletion of disk space on the runner instance, and the bloat of the GitHub Container Registry (GHCR). Failure to implement an aggressive cleanup strategy leads to increased latency, unpredictable build failures due to "disk full" errors, and an unmanageable sprawl of container images that can complicate versioning and security audits. Achieving a lean operational state requires a multi-layered approach, combining native GitHub concurrency controls, specialized third-party actions for registry pruning, and surgical manual intervention to reclaim runner storage.

Workflow Run Lifecycle Management and Concurrency

The proliferation of workflow runs can lead to a cluttered "Actions" tab and unnecessary resource consumption when multiple commits are pushed to the same branch in rapid succession. Historically, this necessitated third-party solutions to ensure that only the most recent push was processed, while previous, now-obsolete runs were cancelled.

The rokroskar/workflow-run-cleanup-action serves as a dedicated tool for this purpose. Its primary function is to identify and clean up previously running instances of a workflow on the same branch. This mirrors a common feature found in most mature CI systems, where a new push triggers the automatic cancellation of any existing runs for that specific branch to save compute time and avoid race conditions during deployment.

The technical implementation of this action relies on the GITHUB_TOKEN and the DEBUG environment variables. To operate correctly, the action must be granted access to the repository via the token; specifically, if a Personal Access Token (PAT) is utilized instead of the default secret, the repo scope must be explicitly included to allow the action to query and modify workflow runs.

The operational impact of using this action is a significant reduction in "ghost" runs that would otherwise consume your organization's total available GitHub Actions minutes. By ensuring that only one version of a branch is being tested at a time, developers receive faster feedback loops and avoid the confusion of multiple concurrent "green" or "red" build statuses for the same logical line of development.

However, the strategic application of this cleanup is critical. It is often recommended to disable this action on specific triggers, such as tags or the master (or main) branch. This is because Continuous Deployment (CD) pipelines are often linked to these specific refs, and cancelling a production deployment run mid-stream could lead to inconsistent environment states or failed releases.

To implement a conditional cleanup that avoids the master branch and tags, the following configuration is utilized:

yaml name: CI on: push: [] jobs: cleanup-runs: runs-on: ubuntu-latest steps: - uses: rokroskar/workflow-run-cleanup-action@master env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" if: "!startsWith(github.ref, 'refs/tags/') && github.ref != 'refs/heads/master'"

From a technical perspective, it is important to note that GitHub now provides native functionality to handle this via the concurrency command. This native feature allows users to group jobs; if a job is triggered while another job in the same concurrency group is still running, the previous job can be cancelled automatically. This is the preferred modern method, as it is integrated directly into the GitHub workflow syntax.

GitHub Container Registry (GHCR) Image Pruning

The GitHub Container Registry can quickly accumulate thousands of image layers, many of which are untagged or obsolete. Because container images are composed of multiple layers and manifest descriptors, deleting them requires a sophisticated understanding of how these images are linked.

The dataaxiom/ghcr-cleanup-action is designed to automate the removal of these images. The action functions by downloading manifest descriptors of all images and mapping their content to the underlying GitHub packages. This is a critical technical step because it prevents the accidental deletion of "untagged" packages that are actually required components of a multi-architecture image. If the action identifies that an untagged package is referenced by a multi-architecture image, it skips that package to maintain image integrity. Conversely, to fully delete a multi-architecture image, the action must delete all associated underlying packages.

The operational risk of registry cleanup is high, as deleting a production image can break deployments. Therefore, the action provides a dry-run: true option. This simulates the cleanup process and prints the intended actions to the workflow log without actually deleting any data. This is mandatory when utilizing complex selectors like wildcards, regular expressions, or the older-than parameter.

The configuration parameters for the ghcr-cleanup-action are detailed in the following table:

Option Required Defaults Description
token yes secrets.GITHUB_TOKEN Token used to connect with ghcr.io and the Package API
owner no project owner The GitHub repository owner (organization or user)
repository no repository name The GitHub repository name
package(s) no repository name Comma-separated list of packages to cleanup (supports wildcards/regex if expand-packages is enabled)

To prevent the "accumulation of junk" while keeping a safety buffer of recent versions, the keep-n-tagged option is employed. This allows the user to specify the number of most recent tagged images to retain. These images are sorted by date, and only the $N$ most recent are kept. If the delete-tags option is also used, keep-n-tagged only applies to the tags specified in that setting; otherwise, it operates on the entire registry set.

A critical technical constraint exists regarding public packages. GitHub prohibits the deletion of public packages that have been downloaded more than 5,000 times. Because there is currently no public GitHub API to programmatically retrieve download counts, the ghcr-cleanup-action cannot automatically detect these "protected" images. Users must manually specify these images in the exclude-tags option to prevent the action from attempting to delete them and subsequently failing or throwing warnings.

Furthermore, the choice of token is vital. While secrets.GITHUB_TOKEN is sufficient for packages created by the repository's own pipeline, a Classic Personal Access Token (PAT) is required if the action needs to access packages in a different repository or if expand-packages is set to true. This is a limitation of the GitHub Registry API, which currently only supports Classic tokens for these specific operations.

To ensure that the registry remains in a consistent state, it is recommended to use a concurrency group for the cleanup job. This prevents multiple cleanup processes from running simultaneously, which could lead to data corruption or API rate limiting.

yaml cleanup-images: name: cleanup-images runs-on: ubuntu-latest concurrency: group: cleanup-images steps: - uses: dataaxiom/ghcr-cleanup-action@v1 with: keep-n-tagged: 3 validate: true

The validate option, when set to true, triggers a full scan of the image repository after execution to ensure that no multi-architecture images have missing platform images, providing an additional layer of safety.

Runner Disk Space Reclamation

GitHub Actions runners, particularly the Ubuntu-based ones, come pre-installed with a vast array of software toolsets. While this versatility is beneficial, it consumes a significant amount of the available disk space—often leaving the user with limited room for large build artifacts or complex dependencies.

On a standard x64 runner, the usable space might be around 53 GB, while an arm64 runner provides approximately 55 GB. However, a large portion of this is occupied by tool caches for languages and frameworks that the specific project may not even use (e.g., Android SDKs, .NET, Swift, or JVM).

There are two primary methods for reclaiming this space: utilizing a dedicated reusable action or implementing a manual shell script.

The mathio/gha-cleanup@v1 action provides a streamlined, reusable way to remove these unnecessary tools. It allows users to toggle specific cleanup targets, such as browsers, via the remove-browsers option. This is particularly useful for developers who do not perform end-to-end testing with Selenium or Playwright and want to reclaim the space occupied by Chrome or Firefox.

For those who prefer a transparent, manual approach, a shell script can be inserted into the workflow. This method involves identifying the directories where GitHub stores its pre-installed tool caches and removing them using sudo rm -rf.

The following technical mapping identifies the target directories for removal:

  • /usr/lib/jvm: Removes Java Development Kit (JDK) caches.
  • /usr/local/.ghcup: Removes Haskell toolchains.
  • /usr/local/lib/android: Removes the Android SDK and related tools.
  • /usr/local/share/powershell: Removes PowerShell installations.
  • /usr/share/dotnet: Removes the .NET framework.
  • /usr/share/swift: Removes the Swift programming language toolset.
  • $AGENT_TOOLSDIRECTORY: Removes the general GitHub agent tool cache.

The real-world impact of this manual cleanup is substantial. In tested scenarios on x64 runners, the disk usage can drop from 70% (approximately 50 GB used) to 27% (approximately 20 GB used), effectively freeing up 30 GB of space for the actual build process.

The implementation of this cleanup in a YAML workflow is as follows:

yaml - name: Clean Up Disk Space if: runner.os == 'Linux' run: | # Space usage before cleanup df -h / # Remove unused tool caches sudo rm -rf /usr/lib/jvm sudo rm -rf /usr/local/.ghcup sudo rm -rf /usr/local/lib/android sudo rm -rf /usr/local/share/powershell sudo rm -rf /usr/share/dotnet sudo rm -rf /usr/share/swift sudo rm -rf "$AGENT_TOOLSDIRECTORY" # Verify gains df -h /

By executing df -h / both before and after the cleanup, developers can verify the exact amount of space reclaimed, ensuring that the runner has sufficient headroom for large Docker layers or heavy compilation tasks.

Conclusion

The comprehensive optimization of GitHub Actions requires a strategic intersection of workflow management, registry maintenance, and hardware resource reclamation. The transition from manual cleanup to automated, event-driven pruning allows organizations to scale their CI/CD pipelines without hitting the ceiling of runner disk limits or registry storage quotas.

The use of the rokroskar/workflow-run-cleanup-action or native concurrency groups mitigates the waste of compute minutes, while the dataaxiom/ghcr-cleanup-action ensures that the container registry remains a lean repository of current images rather than a graveyard of obsolete builds. Finally, the aggressive removal of pre-installed software on the runner—either through mathio/gha-cleanup or manual rm -rf commands—transforms the runner from a general-purpose environment into a specialized, high-performance build machine.

Ultimately, the goal of cleanup is not merely the deletion of data, but the restoration of predictable performance. By implementing these three layers of cleanup, developers ensure that their pipelines are not only faster but more resilient to the "out of disk space" failures that plague large-scale automated testing and deployment environments.

Sources

  1. Workflow Run Cleanup Action
  2. GHCR.io Cleanup Action
  3. Mastering Disk Space on GitHub Actions Runners
  4. GitHub Runner Cleanup Reusable Action

Related Posts