I’ve been using GitHub Actions for years, and I’m constantly surprised by what people build with them. What started as “that CI/CD thing” has turned into a genuinely powerful automation platform—one that most developers barely scratch the surface of.
So I’m starting a series on GitHub Actions. Not the basics-you-can-Google stuff, but the patterns, tricks, and real-world workflows that actually make a difference. This first post is a tour of what’s possible. Think of it as the buffet before the main course.
The Obvious Stuff (That Still Matters)
Let’s get the standard use cases out of the way. Yes, GitHub Actions does CI/CD. You probably already know this.
Continuous Integration: Run tests on every push. Lint your code. Check formatting. The bread and butter.
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm test
Continuous Deployment: Push to main, deploy to production. Or staging. Or both, depending on the branch.
Build artifacts: Compile your code, upload the binary, make it available for download. This is how a lot of open-source projects distribute releases.
If this is all you’re using Actions for, you’re missing out. Let’s look at what else is on the menu.
Automating the Tedious
Some of my favorite workflows aren’t about CI/CD at all—they’re about eliminating repetitive tasks I’d otherwise do by hand.
Auto-labeling Pull Requests
You can automatically label PRs based on the files they touch. Changed something in /docs? Add the documentation label. Modified the API? Tag it api-change. Touched tests? has-tests.
on:
pull_request:
types: [opened, synchronize]
jobs:
label:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v5
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
The labeler reads a config file that maps paths to labels. Set it up once, never think about it again.
Stale Issue Management
Open-source maintainers know this pain: issues pile up. Some are legitimate bugs waiting for attention. Others are questions that got answered in the comments but never closed. Others are feature requests from 2019 that nobody cares about anymore.
The stale action automatically nudges old issues and eventually closes them if there’s no response.
on:
schedule:
- cron: '0 0 * * *' # Daily at midnight
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
stale-issue-message: 'This issue has been inactive for 60 days...'
days-before-stale: 60
days-before-close: 14
Is it a bit ruthless? Maybe. Does it keep your issue tracker usable? Absolutely.
Dependency Updates
Dependabot is the obvious choice, but you can go further. Schedule workflows that check for updates, run your test suite against the new versions, and even auto-merge if everything passes.
on:
schedule:
- cron: '0 6 * * 1' # Every Monday at 6 AM
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm update
- run: npm test
- uses: peter-evans/create-pull-request@v6
with:
title: 'Weekly dependency updates'
commit-message: 'chore: update dependencies'
Code Quality Gates
Beyond “does it compile” and “do tests pass”, there’s a whole world of quality checks you can automate.
Security Scanning
GitHub’s built-in code scanning with CodeQL catches security vulnerabilities before they ship. It’s free for public repos and worth setting up for anything serious.
on:
push:
branches: [main]
schedule:
- cron: '0 14 * * 1' # Weekly scans
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
with:
languages: javascript
- uses: github/codeql-action/analyze@v3
License Compliance
If you’re shipping software, you need to know what licenses your dependencies use. There are actions that scan your dependency tree and flag anything incompatible with your project’s license.
Coverage Thresholds
Don’t just report coverage—enforce it. Fail the build if coverage drops below a threshold, or if a PR decreases coverage by more than a percentage point.
Documentation That Stays Fresh
Documentation rot is real. Here’s how Actions can help.
Auto-generated API Docs
Push code, auto-generate docs, publish to GitHub Pages. The docs always match the code because they’re built from the code.
on:
push:
branches: [main]
jobs:
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run build:docs
- uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs
README Badges
You’ve seen those badges at the top of README files—build status, coverage percentage, latest version. Many of these update automatically via Actions that regenerate them on each push.
Changelog Generation
Some teams auto-generate changelogs from commit messages or merged PRs. Conventional commits make this especially clean—commits prefixed with feat:, fix:, docs: get automatically sorted into the right sections.
Release Automation
Releases are error-prone when done manually. Actions can make them nearly foolproof.
Semantic Versioning
Push a tag like v1.2.3, and a workflow kicks off that:
- Builds the project for all target platforms
- Creates a GitHub Release
- Uploads the binaries
- Updates the changelog
- Publishes to package registries
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run build
- uses: softprops/action-gh-release@v2
with:
files: dist/*
Multi-Platform Builds
GitHub Actions supports Linux, macOS, and Windows runners out of the box. One workflow can build native binaries for all three platforms using a matrix strategy.
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- run: cargo build --release
This is how a lot of cross-platform CLI tools get distributed.
Scheduled Tasks
The schedule trigger opens up a whole category of automation that has nothing to do with code changes.
Data Collection
I’ve seen teams use Actions to scrape websites, poll APIs, and collect metrics on a schedule. The results get committed back to the repo or pushed to a database.
One clever pattern: a workflow that checks competitor pricing daily and opens an issue if something significant changes.
Health Checks
Ping your production services every few minutes. If something’s down, create an issue automatically—or trigger a webhook that pages someone.
Report Generation
Weekly summaries, monthly metrics, quarterly reviews. Schedule a workflow that gathers data, generates a report, and either commits it to the repo or emails it to stakeholders.
The Weird and Wonderful
Here’s where it gets fun. People have built some genuinely creative things with Actions.
Blog Publishing Pipelines
Write posts in Markdown, push to the repo, and a workflow builds your static site and deploys it. This is how a lot of developer blogs work—including some pretty high-profile ones.
Issue-Driven Workflows
Some projects use issues as a kind of command interface. Open an issue with a specific title or label, and a workflow picks it up and does something—generates a report, triggers a deployment, runs a one-off script.
Social Media Automation
Post a new release? Automatically tweet about it. Merge a big PR? Post to Discord. The GitHub API and various social media actions make this surprisingly easy.
Self-Updating READMEs
Some developers have README files that update themselves. A workflow runs daily, queries their activity (blog posts, tweets, GitHub stats), and regenerates the README with fresh content.
It’s a bit gimmicky, but you have to admire the creativity.
Game Bots
There’s a chess bot that lets you play chess via GitHub issues. Every move is a comment, and an Action validates the move and updates the board state. It’s completely impractical and absolutely delightful.
Matrix Builds for Compatibility
When you need to test across multiple versions or configurations, matrix builds are incredibly powerful.
jobs:
test:
strategy:
matrix:
node: [18, 20, 22]
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm test
This runs six jobs total—Node 18, 20, and 22 on both Ubuntu and macOS. One workflow definition, comprehensive coverage.
Custom Actions
When the pre-built actions don’t quite fit, you can write your own. There are three flavors:
- JavaScript/TypeScript actions: Run directly on the runner, fast startup
- Docker container actions: Bring your own environment, any language you want
- Composite actions: Combine multiple steps into a reusable unit
I’ve built custom actions for project-specific tasks—validation scripts, deployment helpers, notification systems. Once you get comfortable with it, Actions become a legitimate platform for tooling.
The Rest of This Series
This post barely scratches the surface. Here’s what else is in the series:
- Workflow Design Patterns: How to structure workflows that are maintainable and don’t turn into spaghetti
- Security Best Practices: Secrets management, permissions, protecting against supply chain attacks
- Advanced Triggers and Events: Beyond push and PR—workflow_dispatch, repository_dispatch, and the rest
- Optimization and Performance: Caching strategies, artifact handling, making workflows fast
- Self-Hosted Runners: When the GitHub-provided runners aren’t enough
- Real-World Case Studies: Complete workflows from production, with the rough edges included
- Niche and Unexpected Uses: The weird, wonderful, and surprisingly practical things people build
Getting Started
If you haven’t touched GitHub Actions yet, the barrier to entry is remarkably low. Create a .github/workflows/ directory in your repo, add a YAML file, and you’re off. GitHub’s documentation is solid, and there’s a marketplace full of pre-built actions for common tasks.
Start with something simple—run your tests on every push. Then add a linter. Then maybe auto-label your PRs. Before you know it, you’ll be automating things you didn’t know could be automated.
That’s the thing about GitHub Actions—once you start seeing everything as a potential workflow, you can’t unsee it.