Kubernetes - Automated Dependency Updates with Renovate
Table of Contents
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,IssuesandWorkflowsRead-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:
1gh secret set RENOVATE_TOKEN -R kiriapurv/k8s-gitops
Public repositories selected, Renovate fails on a private repository with platform-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:
1name: renovate
2
3on:
4 workflow_dispatch:
5 inputs:
6 logLevel:
7 description: Renovate log level
8 type: choice
9 options: [info, debug]
10 default: info
11 schedule:
12 - cron: "0 3 * * 1"
13
14permissions:
15 contents: read
16
17jobs:
18 renovate:
19 runs-on: ubuntu-latest
20 steps:
21 - name: Self-hosted Renovate
22 uses: renovatebot/github-action@2804199d260ad00c1fae0e9e842888369391c776 # v46.3.10
23 with:
24 token: ${{ secrets.RENOVATE_TOKEN }}
25 env:
26 RENOVATE_REPOSITORIES: ${{ github.repository }}
27 RENOVATE_GIT_AUTHOR: "Renovate Bot <[email protected]>"
28 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:
1{
2 "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 "extends": [
4 "config:recommended",
5 "helpers:pinGitHubActionDigests"
6 ],
7 "timezone": "Asia/Kolkata",
8 "flux": {
9 "managerFilePatterns": [
10 "/cluster/.+\\.ya?ml$/",
11 "/apps/.+\\.ya?ml$/"
12 ]
13 },
14 "packageRules": [
15 {
16 "description": "Sample app image tags are managed by Flux image automation",
17 "matchPackageNames": [
18 "ghcr.io/kiriapurv/**"
19 ],
20 "enabled": false
21 }
22 ]
23}
This configuration includes several important customizations:
config:recommendedgives us Renovate’s sensible defaults, like the Dependency Dashboard and PR rate limitinghelpers:pinGitHubActionDigestspins GitHub Actions to commit digests and keeps those digests updatedflux.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:
1docker run --rm -v "$PWD":/usr/src/app -w /usr/src/app renovate/renovate:44.190.1 renovate-config-validator --strict renovate.json
2
3 INFO: Validating renovate.json as global config
4 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:
1gh workflow run renovate.yaml -R kiriapurv/k8s-gitops
Once the run is done, the workflow log shows what Renovate found:
1 INFO: Renovate started
2 INFO: Repository started (repository=kiriapurv/k8s-gitops)
3 INFO: Dependency extraction complete (repository=kiriapurv/k8s-gitops, baseBranch=main)
4 "stats": {
5 "managers": {
6 "flux": {"fileCount": 9, "depCount": 9},
7 "github-actions": {"fileCount": 2, "depCount": 7}
8 "total": {"fileCount": 11, "depCount": 16}
9 INFO: Branch created (repository=kiriapurv/k8s-gitops, branch=renovate/pin-dependencies)
10 INFO: PR created (repository=kiriapurv/k8s-gitops, branch=renovate/pin-dependencies)
11 "prTitle": "Pin dependencies",
12 INFO: Branch created (repository=kiriapurv/k8s-gitops, branch=renovate/fluxcd-flux2-2.x)
13 INFO: PR created (repository=kiriapurv/k8s-gitops, branch=renovate/fluxcd-flux2-2.x)
14 "prTitle": "Update dependency fluxcd/flux2 to v2.10.0",
15 INFO: Repository finished (repository=kiriapurv/k8s-gitops)
16 "cloned": true,
17 "durationMs": 41762,
18 "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:
1kubectl get pods -l app.kubernetes.io/name=reflector -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.spec.containers[0].image}{"\n"}{end}'
2
3reflector-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:
1 chart:
2 spec:
3 chart: reflector
4- version: "9.0.318"
5+ version: "9.1.45"
6 interval: 10m
7 sourceRef:
8 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:
1flux reconcile source git flux-system
2
3◎ waiting for GitRepository reconciliation
4✔ fetched revision main@sha1:0f4e575ef86d2bb51fce04a0301141cf8cd1a74a
1flux reconcile helmrelease reflector -n default
2
3✔ HelmRelease annotated
4◎ waiting for HelmRelease reconciliation
5✔ applied revision 9.1.45
Now let’s check if our release was upgraded:
1flux get helmrelease reflector -n default
2
3NAME REVISION SUSPENDED READY MESSAGE
4reflector 9.1.45 False True Helm upgrade succeeded for release default/reflector.v2 with chart [email protected]
1kubectl get pods -l app.kubernetes.io/name=reflector
2
3NAME READY STATUS RESTARTS AGE
4reflector-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:
1kubectl get secrets -A --field-selector metadata.name=github-registry-secret
2
3NAMESPACE NAME TYPE DATA AGE
4cert-manager github-registry-secret kubernetes.io/dockerconfigjson 1 602d
5cilium-secrets github-registry-secret kubernetes.io/dockerconfigjson 1 596d
6default github-registry-secret kubernetes.io/dockerconfigjson 1 609d
7flux-system github-registry-secret kubernetes.io/dockerconfigjson 1 602d
8ingress-nginx github-registry-secret kubernetes.io/dockerconfigjson 1 596d
9kube-node-lease github-registry-secret kubernetes.io/dockerconfigjson 1 602d
10kube-public github-registry-secret kubernetes.io/dockerconfigjson 1 602d
11kube-system github-registry-secret kubernetes.io/dockerconfigjson 1 602d
12monitoring github-registry-secret kubernetes.io/dockerconfigjson 1 602d
13reloader 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.
v2.10.0 update 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/