Pinning GitHub Actions for Reproducibility and Security
Episode 44: Stop relying on mutable version tags. Lock your workflows to immutable commit SHAs for production-grade stability and security.

GitHub Actions are typically referenced by version tags (e.g., actions/checkout@v5), but version tags can be moved, force-pushed, or accidentally updated. For better reproducibility and security, you can pin actions to specific version SHAs (immutable references that never change).
This is the approach that tools like Renovate (and Dependabot) use behind the scenes to keep your dependencies secure and consistent. If you’re managing GitHub Actions dependencies at scale, Renovate remains the preferred way to automate version updates and SHA pinning. But understanding where these version SHAs come from and how they work is valuable in several ways:
Debug Renovate behaviour: If you’re wondering why Renovate is pinning to a specific SHA, or if it’s not updating when you expect, understanding the mechanics helps you troubleshoot.
Implement the logic yourself: Sometimes you need custom logic for dependency management. Knowing how SHAs relate to version tags lets you build your own tooling.
Understand the fundamentals: Version pinning is a core security and reproducibility practice. Understanding the why behind it makes you a more thoughtful infrastructure engineer.
Note: SHA pinning isn’t unique to GitHub Actions. The same principle appears everywhere in the infrastructure and dependency management world. Most programming languages use SHA hashes in lock files (npm, pip, cargo, etc.) to pin transitive dependencies. Docker images are pinned to SHAs for the same reasons (security and reproducibility). Understanding this pattern at the GitHub Actions level helps you recognise and apply it across your entire stack.
In the Quiet: Where I’ve Been
If you’ve been wondering why I went quiet back in February, I owe you an explanation.
Earlier this year, I left Elastic after three great years. I needed time to step back and recharge. I spent five months travelling, reconnecting with family in Italy, and just absorbing things at a slower pace. It gave me space to think clearly about what came next.
In July, I joined Loveholidays as a Senior Platform Engineer. Loveholidays is a UK-based travel booking platform that helps customers find and book holidays across Europe. The role was a significant shift: much heavier on the DevOps and infrastructure side. I had to quickly absorb Terraform, Grafana, Google Cloud, and a whole ecosystem of tools I’d never worked with deeply before. Those first months weren’t about writing; they were about listening, learning, and taking in everything I could. I was quietly observing patterns, asking questions, and building foundational knowledge.
There’s also the reality of life outside of work: I’m raising a two-year-old who’s in full toddler mode. Most weekends are spoken for, and writing demands the kind of mental space that wasn’t available.
But here we are at four months in, and something’s shifted. The fog is lifting. I’ve absorbed enough to start seeing clearly, and more importantly, I’ve been quietly accumulating ideas: real, concrete things I’ve learned about infrastructure, platform engineering, and cloud-native practices that I genuinely want to share.
So here I am, breaking the quiet. I’m back, and I have things to say.
Why Pin to SHAs?
Version tags are convenient, but they’re mutable. A maintainer can re-release a tag, move it to a different commit, or even force-push to it. This means your workflow could run different code tomorrow than it did today without you changing anything.
Here’s the key insight: a version tag like v5.0.1 is just a pointer to a specific commit. When you pin to the commit SHA that tag currently points to, you’re locking in that exact version. If the tag ever gets moved or re-released to point to a different commit, your workflow stays pinned to the original commit. You’re insulated from any future changes to that tag.
Commit SHAs are different. A 40-character SHA like 93cb6efe18208431cddfb8368fd83d5badbf9bfd points to one specific commit, forever. It can never change, never be re-released, never be overwritten.
For production workflows and security-sensitive operations, this immutability is invaluable.
How to Find the SHA
Prerequisites
You’ll need:
GitHub CLI installed:
https://cli.github.com/
Authenticated with GitHub: Run
gh auth login
The Command
For any GitHub Action release, use the GitHub CLI to get the commit SHA that the version tag currently points to:
gh api repos/actions/checkout/commits/v5.0.1 --jq ‘.sha’
Replace actions/checkout and v5.0.1 with your action and desired version. See the GitHub Actions documentation for more details on security best practices.
Result: You’ll get back a 40-character commit SHA like 93cb6efe18208431cddfb8368fd83d5badbf9bfd. This is the commit that the v5.0.1 tag is currently pointing to: your immutable lock.
That’s it. One command, one immutable reference.
Using SHAs in Your Workflow
Replace the version tag with the full commit SHA:
# Before: Version tag (can change)
- uses: actions/checkout@v5
# After: Immutable commit SHA with version comment for readability
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
It’s common practice to include a comment with the version tag (e.g., # v5.0.1) next to the SHA. This gives human readers a quick reference to which version you’re pinned to, while the SHA itself provides the immutable guarantee. The comment doesn’t affect functionality, it’s purely for documentation and clarity in code review.
The behaviour is identical to the tag, but now you have a guarantee that the exact same code will run every single time.
The Benefits
Immutability
SHAs never move. Version tags can be re-released, updated, or accidentally modified. A SHA pin locks you to a specific point in git history forever.
Security
You can explicitly control which version of an action runs in your workflow. No surprises, no unexpected updates that could introduce bugs or vulnerabilities.
Auditability
When you audit your workflows, you know exactly which version of code ran. The 40-character SHA serves as an immutable audit trail.
Reproducibility
Your workflow behaves identically across runs and across time. This is critical for debugging, compliance, and understanding system behaviour.
When to Use This
Use commit SHAs for:
Production workflows where stability is critical and unexpected updates could break deployments
Security-sensitive operations where you need complete control over dependency versions
Compliance requirements that demand audit trails and version pinning
For development workflows, version tags may be convenient enough. But for production, SHAs provide the security and predictability you need.
Ready to pin your actions? Start with your most critical workflows and expand from there. The small effort of pinning to SHAs pays dividends in reproducibility and peace of mind.
References
GitHub Actions Documentation - Official guide to GitHub Actions workflows, syntax, and capabilities.
GitHub Actions Security Hardening Guide - Best practices for securing GitHub Actions workflows, including SHA pinning recommendations.
GitHub CLI - Command-line tool for interacting with GitHub repositories and retrieving commit SHAs.
Renovate Documentation - Comprehensive guide to automating dependency updates and SHA pinning across your repositories.
GitHub Dependabot Documentation - GitHub’s native tool for dependency updates and security vulnerability alerts.
Container Image Digests - Guide to pinning Docker images using immutable digest SHAs.



The immutabillity of SHAs is such a simple concept but makes a huge diffrence in production. I've been burned by tag updates before and this apprach would have saved me hours of debugging. Really clear explanation of why this matters.