Automatically Updating Versions in GitHub Actions Using Renovate

Everyone starts off by never pinning their versions, relying on :latest till the day some dependency pops up and fucks whatever you are deploying. Afterwards, you start following best practices but encounter why so many people follow bad practices - like :latest. Manually updating dependencies is a pain in the ass.

Usually Dependabot does the job. And it would probably do here as well. But a while ago I started using Renovate at work and I became quiet a fan.

Here’s how I wired Renovate to keep my Hugo version up-to-date in a GitHub Pages workflow for my Hugo-powered blog.


Problem

My GitHub Actions deployment workflow pinned the HUGO_VERSION to a specific release for reproducible builds. But Hugo releases fast — and checking for updates manually became a chore.

So let’s automate it.


The Goal

  • Automatically detect new Hugo Extended releases
  • Open a pull request that updates HUGO_VERSION in my workflow yaml

Renovate Setup

First, let’s create a renovate.json file in the root of the repo:

{
  "extends": [
    "config:recommended"
  ],
  "automerge": false,
  "prConcurrentLimit": 5,
  "gitAuthor": "renovate[bot] <renovate[bot]@users.noreply.github.com>",
  "customManagers": [
    {
      "customType": "regex",
      "managerFilePatterns": [
        "/^\\.github/workflows/[^/]+\\.ya?ml$/"
      ],
      "matchStrings": [
        "HUGO_VERSION:\\s*(?<currentValue>\\d+\\.\\d+\\.\\d+)"
      ],
      "depNameTemplate": "gohugoio/hugo",
      "datasourceTemplate": "github-releases"
    }
  ]
}

How It Works

  • customManagers: Scans GitHub Actions YAML files for HUGO_VERSION using a regular expression

  • datasourceTemplate: Pulls new versions from GitHub Releases (gohugoio/hugo)

  • depNameTemplate: Used to label the update PRs properly

When a new release like v0.147.9 drops, Renovate opens a PR to update this line:

env:
  HUGO_VERSION: 0.147.8

Running Renovate with GitHub Actions

You could run Renovate’s hosted app. But I kinda like that approach more - if I can selfhost it, I do. There would be the option to actually run a Renovate Server. But that would be overkill for my lil repo I have here.

.github/workflows/renovate.yaml

name: Renovate

on:
  schedule:
    - cron: '0 3 * * *'  # Run daily
  workflow_dispatch:

jobs:
  renovate:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
      issues: write
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '22'

      - run: npm install -g renovate

      - uses: actions/create-github-app-token@v2
        id: app-token
        with:
          app-id: ${{ secrets.RENOVATE_APP_ID }}
          private-key: ${{ secrets.RENOVATE_PRIVATE_KEY }}

      - run: renovate --autodiscover
        env:
          RENOVATE_TOKEN: ${{ steps.app-token.outputs.token }}
          RENOVATE_PLATFORM: github
          RENOVATE_REPOSITORIES: ${{ github.repository }}
          LOG_LEVEL: debug

How to authenticate

If you store your versions in a .version file, the default GitHub Token provided by GitHub Actions should do the trick. But I don’t, so I went with an alternative method.

You can either go with a PAT (Personal Access Token) - which I am personally not a fan of. Or a GitHub App. One of the benefits of a GitHub App is, that it basically let’s you create PAT's, but makes them short-lived. It’s not neccessary, but I like that approach and it’s a best practice in my opinion.

To create a GitHub App, follow the official documentation: https://docs.github.com/en/apps/creating-github-apps

Once that’s done, generate a PRIVATE_KEY and save it as a GitHub Actions secret. The APP_ID does not need to be a secret, but I do it anyway. Now you can create PAT's with the actions/create-github-app-token action.

Hugo Deployment Workflow

The deployment workflow uses the version Renovate manages:

env:
  HUGO_VERSION: 0.147.8

Then installs Hugo like this:

wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
&& sudo dpkg -i ${{ runner.temp }}/hugo.deb

Because the version is hardcoded, builds are repeatable — and thanks to Renovate, always current.


The Result

Whenever a new Hugo version is released:

  • Renovate opens a PR like chore(deps): update hugo to v0.147.9
  • The PR changes only the version number in the workflow YAML
  • I review, merge and GitHub deploys automatically with the new Hugo Version