RSS Amplifier

Cloud Native Engineer · Nov 22, 2025

Pinning GitHub Actions for Reproducibility and Security

0
Sign in to vote or save

Giuseppe Santoro 🚢 · Cloud Native Engineer

An isometric illustration of a large pushpin piercing through a stack of colourful paper cards on a board, rendered in flat colours with a minimalist aesthetic. The visual metaphor represents how commit SHA pinning permanently locks GitHub Actions to specific, immutable code versions.
Just a pushpin locks cards in place, commit SHAs lock your workflows to immutable versions that can never change

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.

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.

Thanks for reading Cloud Native Engineer! This post is public so feel free to share it.

Share

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.

You’ll need:

  • GitHub CLI installed:

https://cli.github.com/

  • Authenticated with GitHub: Run gh auth login

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.

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.

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.

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.

Read the original on cloudnativeengineer.substack.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.