Make GitHub Actions Do More for You

mikemcquaid1 pts1 comments

Make GitHub Actions Do More For You | Mike McQuaid

Mike McQuaid

CTPO and Homebrew Project Leader

Articles

Talks

Interviews

Thoughts

Projects

CV

Now

About

Make GitHub Actions Do More For You

22 June 2026

Most people just use GitHub Actions to run their tests.<br>It can do far more: deploy a PR to production before merge, make release processes more robust and automate the boring chores you keep forgetting to do.

Here are a few patterns I’ve used to make my life easier with GitHub Actions.<br>I’ve done a bunch to evolve Homebrew’s CI over the years so hopefully I can teach you something.

🚀 Merge Queues with Deployments

GitHub’s Merge Queue was the last big project I led at GitHub so I’m biased towards it.<br>It provides a queue of one or more pull requests which are stacked, tested and merged together.

At GitHub, we had a “deploy then merge” workflow where PRs would be tested and then deployed to production before being merged.<br>Most of our customers had a “merge then deploy” workflow where merges to the default branch would then be deployed.<br>I always liked the GitHub approach but doing it with non-GitHub tooling was a bit tricky.

I found a nice way to do it with Merge Queues in Workbrew and Administrate.<br>The GitHub Actions trigger is the merge_group event: a job that only runs there can deploy the merge commit to production before it is pushed to main (or: the default branch).<br>This provides the nice benefit that anything that ends up on your default branch has already been successfully deployed to production.

on:<br># The merge queue trigger event<br>merge_group:<br># The usual pull request jobs<br>pull_request:<br># Duplicate push job to keep caches warm (see below)<br>push:<br>branches:<br>- main

concurrency:<br>group: ${{ github.workflow }}-${{ github.event_name }}${{ github.event.pull_request.number }}<br>cancel-in-progress: true

permissions:<br>contents: read

jobs:<br>tests:<br>runs-on: ubuntu-latest<br>steps:<br>- uses: actions/checkout@v7<br>with:<br>persist-credentials: false<br>- run: script/test

deploy:<br># Only deploy from the merge queue, before the merge lands on `main`.<br>if: github.event_name == 'merge_group'<br>needs: tests<br>runs-on: ubuntu-latest<br>environment: production<br>concurrency:<br># Never let two production deploys race each other.<br>group: deploy-production<br>cancel-in-progress: false<br>steps:<br>- uses: actions/checkout@v7<br>with:<br>persist-credentials: false<br>- run: script/deploy --production

environment: production gets you deployment history, environment protection rules and secrets.<br>The concurrency group makes sure two production deploys never occur at once.<br>Homebrew’s tests.yml is a real-world example of wiring up merge_group (without the deploy step).

Some gotchas here:

merge_group runs read actions/cache entries but can’t write them.<br>Only a push to your default branch populates the cache, which is why main is in the trigger list above.<br>Skip any non-push jobs or steps that aren’t needed to write cache entries so you’re not running steps unnecessarily.

the merge queue will only wait for required jobs to be successful or skipped before merging.<br>This means you should think carefully about what jobs should run at PR time, merge group time or both.

merge queues unfortunately need a paid GitHub organisation plan (so don’t work on personal repositories).

🏷️ Releasing inside GitHub Actions

Another thing I’ve found myself wanting to do on a bunch of projects (e.g. Homebrew, Workbrew) is making a GitHub release with a binary built then uploaded by GitHub Actions.<br>The typical way this is done is to create a release on GitHub, have a GitHub Action build the binary and then upload it to the release.<br>This is nice when it works but periodically: whoops, something you did since the last release broke the release pipeline.<br>At this point you have a broken release and tag and the only real way to fix it is to release again.

Some people will delete and recreate a tag when doing this.<br>Please don’t!<br>Git really dislikes changing tags and will not update them in local clones like you expect.<br>Package managers like Homebrew also get confused as to whether this was on purpose or if you got hacked.<br>Now that GitHub supports immutable tags: enable them to avoid even tempting yourself.

Instead, we can rely on the fact that you can create a tag locally in a Git repository and push it later.<br>A workflow_dispatch trigger is really handy here: it gives you a manual “Run workflow” button in the GitHub UI, complete with the inputs you define (like the tag name below) so you can make a release with a few clicks in GitHub and no development environment.

on:<br># Manual trigger used to create a new release.<br>workflow_dispatch:<br>inputs:<br>tag:<br>description: "Git tag for the release"<br>required: true<br>type: string<br># Run a dry run when pushing relevant files to avoid breakage.<br>push:<br>paths:<br>- .github/workflows/release.yml

permissions:<br>contents: write # to push the tag and create the release

jobs:<br>release:<br>runs-on: ubuntu-latest<br>steps:<br>- uses: actions/checkout@v7

- name: Create the...

github release merge actions production deploy

Related Articles