Anatomy of a Reusable GitHub Action

January 30, 20265 min read44 views

Decoupling GitHub workflow logic into standalone, version-controlled actions for the global Marketplace.

“Don’t Repeat Yourself” (DRY) is one of the earliest principles drilled into every developer’s head. From the moment you write your first class, you’re taught to centralize logic, extract common functionality, and avoid writing the same code twice.

At its core, DRY gives you two major advantages:

  1. Maintainability: Fix a bug in one place, fix it everywhere.
  2. Development Speed: Reusable functions, classes, and modules let you build faster and with fewer mistakes.

Developers apply DRY instinctively in code. But here’s my question:

Why don’t we apply DRY with the same intensity in DevOps?

Most of us write infrastructure automation in YAML: GitHub Actions, GitLab CI, Ansible playbooks, Helm charts, Kubernetes manifests, you name it. And while DRY shows up in some tools (Terraform modules, for example), GitHub Actions workflows tend to become cluttered with copy-pasted steps, hardcoded logic, and long blocks of Bash.

I used to write GitHub workflow files the same way: repetitive, handwritten chunks of logic in the .github/workflows directory. Every repo had its own version of the same steps. Every fix meant updating each workflow manually. Every improvement meant diffing YAML in five different repos.

Eventually, I realized something important:

If I’m repeating the same logic across repositories, I should treat it like code and package it for reuse.

That shift completely changed the way I think about automation and CI/CD.


From Hardcoded Workflows to Reusable Actions

In the past month, I published two GitHub Actions to the GitHub Marketplace:

Logos for two github actions, one a tag on a green background, the other a bell on a purple background
Logos for the two actions

Both of these started as local workflow steps that were simple, repetitive scripts copy-pasted between projects.

At some point I stopped and asked myself: Why am I rewriting this? Why isn’t this an action?

Instead of continuing to hardcode logic inside each project’s workflow YAML, I extracted the logic, documented the inputs, created version tags, and published them as standalone, version-controlled GitHub Actions.

Suddenly:

  • My other projects could import the logic with a single line
  • Fixes and improvements automatically propagated across every repo
  • I could control versions, write tests, and follow real software release patterns
  • Other developers outside my ecosystem could use them too

That last part is the one that really stuck with me.

This wasn’t just DevOps anymore. This was open-source contribution.


Example: Automating Semantic Versioning for All Your Projects

My Tag and Release action is a great example.

Originally, this was a step of a workflow living inside one project. Then two. Then three. Every project I worked on (MythicMate, Dumpsterr, and anything else I built) needed the same versioning logic.

The workflow:

  • Determines the new version based on the release type (major/minor/patch)
  • Creates a tag for the new release
  • Updates child tags (i.e. update v1 and v1.1 when releasing from v1.1.1v1.1.2)
  • Generates release notes from the commit messages
  • Creates a GitHub release

Useful? Absolutely. Reusable? Definitely. DRY? Not at all, when duplicated across repos.

Turning it into a standalone GitHub Action meant:

  • MythicMate and Dumpsterr now share one consistent semantic-versioning workflow
  • If I fix a bug in tag generation, every project benefits instantly
  • I can publish formal versions (v1, v1.1, v1.2, etc.)
  • Anyone else on GitHub can now automate their versioning too

Now the workflow logic boils down to 3 lines of yaml:

- uses: chase-roohms/tag-and-release@v1
  with:
    version_bump: patch

This is the DevOps equivalent of extracting repeated code into a single shared library.


DevOps for One vs. DevOps for Everyone

This experience taught me something subtle but important.

Working as a DevOps Engineer means building automation for my team. Working as an open-source contributor means building automation for the world.

The mindset shift is powerful:

  • You stop thinking about solving a single problem
  • You start thinking about how others will consume your solution
  • You design inputs, outputs, documentation, and versioning
  • You adopt testing and release practices instead of “just enough to work” hacks
  • You begin treating DevOps scripts like real software artifacts

It creates better code, better tooling, and it makes you a better engineer.


Why DevOps Engineers Should Embrace DRY Like Devs

The same benefits developers get from DRY apply (sometimes even more strongly) in DevOps:

1. Centralized Logic → Global Fixes

One fix in an action updates automation across your entire ecosystem.

2. Faster Development → Less Boilerplate

No more writing 20 lines of workflow logic for something you could call with one line.

3. Versioning → Predictability

Actions follow real release cycles, not ad-hoc YAML edits.

4. Open Source → Community Improvement

Publishing your automation lets others improve it. You get feature requests, bug reports, ideas, and contributions.

5. Portfolio → Real-World Impact

Reusable DevOps tools are some of the most valuable open-source assets a young engineer can build.


TLDR: If It’s Repeated DevOps Logic, Make It a Reusable Action

If you find yourself copy-pasting the same lines of YAML, the same Bash scripts, or the same notification logic across repos, that’s your signal.

Treat automation the same way you treat code:

  • Abstract it
  • Reuse it
  • Version it
  • Document it
  • Publish it