This post is part of our ongoing series of posts for Kubernetes. In this post, we will automate dependency updates for our GitOps repository using Renovate, an open-source tool that finds outdated dependencies and raises pull requests to update them.
All components in our cluster are pinned to a specific version: Helm charts in HelmRelease, Flux itself in gotk-components.yaml, and actions in our GitHub workflows. This is what GitOps expects, but nothing tells us when these versions become outdated. We hadn't touched our cluster for a while, and by the time we came back, every Helm chart had a newer release available. Checking release pages manually for each of them is error-prone and time-consuming.
Renovate addresses this challenge by scanning the repository, looking up newer versions of each dependency, and raising a pull request per update with release notes attached. We stay in control by reviewing and merging, and FluxCD takes care of rolling out the change. Let's dive into setting up Renovate as a self-hosted GitHub Actions workflow.
1. Create a Token for Renovate
Renovate needs a token to access the repository, push update branches and open pull requests. Let's create a fine-grained personal access token that only has access to our GitOps repository:
- Repository access: Only select repositories →
k8s-gitops -
Read and writepermission forContents,Pull requests,IssuesandWorkflows -
Read-onlyaccess toMetadata(added automatically)
Workflows permission is needed so Renovate can update action versions in .github/workflows, and Issues is used for the Dependency Dashboard we'll see later.
Next, save the token as a repository secret. gh asks for the value, so the token never ends up in shell history:
gh secret set RENOVATE_TOKEN -R kiriapurv/k8s-gitops
Important: Don't forget to give the token access to the repository itself, not just the permissions. With
Public repositoriesselected, Renovate fails on a private repository withplatform-unknown-error, and only the debug log shows the actual problem:Could not resolve to a Repository with the name 'kiriapurv/k8s-gitops'.
2. Setup Renovate Workflow
Let's schedule weekly Renovate runs by creating .github/workflows/renovate.yaml:
name: renovate
on:
workflow_dispatch:
inputs:
logLevel:
description: Renovate log level
type: choice
options: [info, debug]
default: info
schedule:
- cron: "0 3 * * 1"
permissions:
contents: read
jobs:
renovate:
runs-on: ubuntu-latest
steps:
- name: Self-hosted Renovate
uses: renovatebot/github-action@2804199d260ad00c1fae0e9e842888369391c776 # v46.3.10
with:
token: ${{ secrets.RENOVATE_TOKEN }}
env:
RENOVATE_REPOSITORIES: ${{ github.repository }}
RENOVATE_GIT_AUTHOR: "Renovate Bot <3841024+kiriapurv@users.noreply.github.com>"
LOG_LEVEL: ${{ inputs.logLevel || 'info' }}
This configuration:
- Runs every Monday at 03:00 UTC, and on demand through
workflow_dispatchwith an optionaldebuglog level for troubleshooting - Pins the Renovate action to a full commit SHA instead of a tag, as this step gets a token with write access
- Restricts Renovate to the current repository with
RENOVATE_REPOSITORIES - Sets
RENOVATE_GIT_AUTHORso commits are made with our account instead of Renovate's default Mend address
3. Configure Renovate
Next, let's tell Renovate what to look for by creating renovate.json in the root of the repository:
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
"helpers:pinGitHubActionDigests"
],
"timezone": "Asia/Kolkata",
"flux": {
"managerFilePatterns": [
"/cluster/.+\\.ya?ml$/",
"/apps/.+\\.ya?ml$/"
]
},
"packageRules": [
{
"description": "Sample app image tags are managed by Flux image automation",
"matchPackageNames": [
"ghcr.io/kiriapurv/**"
],
"enabled": false
}
]
}
This configuration includes several important customizations:
-
config:recommendedgives us Renovate's sensible defaults, like the Dependency Dashboard and PR rate limiting -
helpers:pinGitHubActionDigestspins GitHub Actions to commit digests and keeps those digests updated -
flux.managerFilePatternsis the most important setting for our layout. By default, Renovate'sfluxmanager only looks atgotk-components.yaml, so it wouldn't find theHelmReleasedefinitions incluster/default/ - The
packageRulesentry stops updates for our sample application image, since Flux image automation already handles it as set up in Kubernetes GitOps with FluxCD - Part 3 - Automated Image Updates
Before committing, let's validate the configuration with Renovate's own validator:
docker run --rm -v "$PWD":/usr/src/app -w /usr/src/app renovate/renovate:44.190.1 renovate-config-validator --strict renovate.json
INFO: Validating renovate.json as global config
INFO: Config validated successfully against 1 file(s)
Now let's commit and push these changes to our GitOps repository.
4. Validate Setup
Let's run the workflow manually right away instead of waiting for Monday:
gh workflow run renovate.yaml -R kiriapurv/k8s-gitops
Once the run is done, the workflow log shows what Renovate found:
INFO: Renovate started
INFO: Repository started (repository=kiriapurv/k8s-gitops)
INFO: Dependency extraction complete (repository=kiriapurv/k8s-gitops, baseBranch=main)
"stats": {
"managers": {
"flux": {"fileCount": 9, "depCount": 9},
"github-actions": {"fileCount": 2, "depCount": 7}
"total": {"fileCount": 11, "depCount": 16}
INFO: Branch created (repository=kiriapurv/k8s-gitops, branch=renovate/pin-dependencies)
INFO: PR created (repository=kiriapurv/k8s-gitops, branch=renovate/pin-dependencies)
"prTitle": "Pin dependencies",
INFO: Branch created (repository=kiriapurv/k8s-gitops, branch=renovate/fluxcd-flux2-2.x)
INFO: PR created (repository=kiriapurv/k8s-gitops, branch=renovate/fluxcd-flux2-2.x)
"prTitle": "Update dependency fluxcd/flux2 to v2.10.0",
INFO: Repository finished (repository=kiriapurv/k8s-gitops)
"cloned": true,
"durationMs": 41762,
"result": "done",
Renovate detected 16 dependencies in 11 files and already opened two pull requests. The rest of the updates are listed in the Dependency Dashboard, an issue created and maintained by Renovate itself:
All Helm charts in our cluster had an update available:
| Chart | Current | Available |
|---|---|---|
| cilium | 1.17.1 | 1.20.4 |
| cert-manager | 1.17.1 | v1.21.3 |
| ingress-nginx | 4.11.3 | 4.15.1 |
| kube-prometheus-stack | 69.5.2 | 69.8.2, 92.14.1 |
| descheduler | 0.32.2 | 0.37.0 |
| reflector | 9.0.318 | 9.1.45, 10.0.69 |
| reloader | 1.3.0 | 2.2.19 |
These updates are listed under Rate-Limited because config:recommended allows at most two new pull requests per hour, so a stale repository isn't flooded with PRs. Ticking a checkbox on the dashboard lifts the limit for that one update. Major versions, like kube-prometheus-stack 92, get a separate branch, so minor and major upgrades can be reviewed separately.
5. Rolling Out an Update
To show the full flow, we'll upgrade Reflector, which we configured in Kubernetes - Replicate Secrets with Reflector. First, let's check which version is running now:
kubectl get pods -l app.kubernetes.io/name=reflector -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.spec.containers[0].image}{"\n"}{end}'
reflector-dcc5cf554-4wl95 docker.io/emberstack/kubernetes-reflector:9.0.318
After ticking Update Helm release reflector to v9.1.45 on the dashboard and running the workflow again, Renovate creates a pull request with the release notes. The only line that changes:
chart:
spec:
chart: reflector
- version: "9.0.318"
+ version: "9.1.45"
interval: 10m
sourceRef:
kind: HelmRepository
Once the pull request is merged, FluxCD picks up the change. Instead of waiting for the next polling interval, let's trigger the reconciliation:
flux reconcile source git flux-system
◎ waiting for GitRepository reconciliation
✔ fetched revision main@sha1:0f4e575ef86d2bb51fce04a0301141cf8cd1a74a
flux reconcile helmrelease reflector -n default
✔ HelmRelease annotated
◎ waiting for HelmRelease reconciliation
✔ applied revision 9.1.45
Now let's check if our release was upgraded:
flux get helmrelease reflector -n default
NAME REVISION SUSPENDED READY MESSAGE
reflector 9.1.45 False True Helm upgrade succeeded for release default/reflector.v2 with chart reflector@9.1.45
kubectl get pods -l app.kubernetes.io/name=reflector
NAME READY STATUS RESTARTS AGE
reflector-556f7cbb75-mfvvz 1/1 Running 0 43s
The new pod runs docker.io/emberstack/kubernetes-reflector:9.1.45, and the GitHub registry secret is still replicated to all namespaces:
kubectl get secrets -A --field-selector metadata.name=github-registry-secret
NAMESPACE NAME TYPE DATA AGE
cert-manager github-registry-secret kubernetes.io/dockerconfigjson 1 602d
cilium-secrets github-registry-secret kubernetes.io/dockerconfigjson 1 596d
default github-registry-secret kubernetes.io/dockerconfigjson 1 609d
flux-system github-registry-secret kubernetes.io/dockerconfigjson 1 602d
ingress-nginx github-registry-secret kubernetes.io/dockerconfigjson 1 596d
kube-node-lease github-registry-secret kubernetes.io/dockerconfigjson 1 602d
kube-public github-registry-secret kubernetes.io/dockerconfigjson 1 602d
kube-system github-registry-secret kubernetes.io/dockerconfigjson 1 602d
monitoring github-registry-secret kubernetes.io/dockerconfigjson 1 602d
reloader github-registry-secret kubernetes.io/dockerconfigjson 1 601d
The update went from pull request to running pod without touching the cluster directly.
6. Retiring the Custom Flux Update Workflow
In Kubernetes GitOps with FluxCD - Part 7 - Resource Optimization and Automated Version Management with Github Actions, we created a GitHub Actions workflow to regenerate gotk-components.yaml every month. Renovate's flux manager does this automatically: the Update dependency fluxcd/flux2 to v2.10.0 pull request regenerates cluster/default/flux-system/gotk-components.yaml for the new Flux version.
Since both would now conflict over the same file, we removed .github/workflows/update-flux.yaml and closed its pending pull request. Renovate saw that the workflow was gone and automatically closed its own Pin dependencies pull request, which only changed that file.
Note: We are holding back the Flux
v2.10.0update and the major chart upgrades for now. They need a newer Kubernetes version, so we'll upgrade K3s first in an upcoming post.
What next ?
Future posts will keep bringing the cluster up to date, including:
- Automated K3s upgrades with System Upgrade Controller
- Migrating from ingress-nginx to Gateway API
Stay tuned for each of these topics.
References
- Renovate Documentation - https://docs.renovatebot.com/
- Renovate Flux Manager - https://docs.renovatebot.com/modules/manager/flux/
- Renovate GitHub Action - https://github.com/renovatebot/github-action
- FluxCD Documentation - https://fluxcd.io/flux/


Top comments (0)