Renovate: Automated Dependency Updates for a GitOps Cluster

Stop manually checking Docker Hub for new image tags. Renovate scans your GitOps repo, opens PRs for outdated versions, and automerges the safe ones.

Renovate: Automated Dependency Updates for a GitOps Cluster

Every container image and Helm chart in the cluster was manually version-managed. Checking Docker Hub for new tags, editing YAML files, committing, pushing, waiting for Flux to reconcile. Across 15+ apps and 6 infrastructure charts, this was already falling behind. Renovate automates the entire workflow: it scans the repo, detects outdated versions, and opens pull requests with the updates.

Why Not Self-Host?

Renovate can run as a self-hosted CronJob in the cluster. That means a GitHub token stored as a Secret, a container running on a schedule, resource requests eating into the 16 GB budget, and maintenance when Renovate itself needs updating. For a homelab, the hosted Mend Renovate GitHub App is the obvious choice: it's free for public repos, runs on Mend's infrastructure, triggers on push events, and requires zero cluster resources.

Install the GitHub App, point it at the repo, and drop a renovate.json in the root. That's it.

The Configuration

Renovate's power is in its package rules. The default config:recommended preset handles most cases, but a GitOps Kubernetes repo needs specific tuning:

{
  "extends": [
    "config:recommended",
    "docker:enableMajor",
    ":semanticCommits",
    ":automergePatch"
  ],
  "schedule": ["every weekend"],
  "timezone": "America/Los_Angeles",
  "prHourlyLimit": 5,
  "prConcurrentLimit": 10,
  "flux": {
    "fileMatch": [
      "(^|/)infrastructure/.+\\.ya?ml$",
      "(^|/)apps/.+\\.ya?ml$"
    ]
  },
  "kubernetes": {
    "fileMatch": [
      "(^|/)apps/.+\\.ya?ml$",
      "(^|/)infrastructure/.+\\.ya?ml$"
    ]
  },
  "ignorePaths": [
    "**/*.sops.yaml",
    "**/*.sops.yml"
  ]
}

What the presets do

  • config:recommended. Renovate's standard defaults: pin versions, separate major from minor/patch, create PRs with changelogs
  • docker:enableMajor. By default Renovate ignores major Docker image bumps. This enables them as separate PRs for manual review
  • :semanticCommits. Commit messages follow conventional commits format, matching the repo's feat: / fix: / chore: convention
  • :automergePatch. Patch-level updates merge automatically without human review. A 1.23.161.23.17 bump is low-risk enough to trust

Flux and Kubernetes managers

Renovate has built-in managers for Flux HelmReleases and Kubernetes manifests, but they need fileMatch patterns to know where to look. Without these, Renovate only scans standard paths like Dockerfile and package.json. The patterns tell it: scan every YAML file under infrastructure/ and apps/ for Helm chart versions and container image tags.

Ignoring SOPS files

ignorePaths: ["**/*.sops.yaml"] tells Renovate to skip encrypted secret files. These contain encrypted blobs, not version strings, but Renovate's parsers would waste time trying to read them.

Package Rules: The Update Strategy

Not all updates are equal. A patch to an app image is low risk. A major Helm chart bump for the monitoring stack can break dashboards and alert rules. Package rules let you define different policies for different categories:

"packageRules": [
  {
    "description": "Infrastructure Helm charts - group, no automerge",
    "matchPaths": ["infrastructure/**"],
    "matchManagers": ["flux"],
    "groupName": "infrastructure-charts",
    "automerge": false
  },
  {
    "description": "Container image patches - automerge",
    "matchManagers": ["kubernetes"],
    "matchUpdateTypes": ["patch"],
    "automerge": true
  },
  {
    "description": "Container image major - separate PRs, no automerge",
    "matchManagers": ["kubernetes"],
    "matchUpdateTypes": ["major"],
    "automerge": false
  },
  {
    "description": "PostgreSQL - pin to major 16",
    "matchPackageNames": ["postgres"],
    "allowedVersions": "16.*",
    "automerge": true
  },
  {
    "description": "Redis - pin to major 7",
    "matchPackageNames": ["redis"],
    "allowedVersions": "7.*",
    "automerge": true
  },
  {
    "description": "kube-prometheus-stack - never automerge",
    "matchPackageNames": ["kube-prometheus-stack"],
    "automerge": false,
    "separateMajorMinor": true
  }
]

The logic

CategoryGroupingAutomergeWhy
Infrastructure Helm chartsGrouped PRNoChanges to Traefik, Cilium, Longhorn affect the whole cluster
Container image patchesIndividualYesLow risk: bug fixes and security patches
Container image minorIndividualNoNew features that might change behavior
Container image majorSeparate PRsNoBreaking changes: read the changelog first
PostgreSQLN/AYes (within 16.x)Pinned to major 16 to prevent data format changes
RedisN/AYes (within 7.x)Pinned to major 7 for protocol compatibility
kube-prometheus-stackSeparate PRsNoMajor bumps break Grafana dashboards and alert rules

The PostgreSQL and Redis pins are important. A major PostgreSQL upgrade (16 → 17) requires a data migration. Renovate can't do that by bumping an image tag. The allowedVersions constraint prevents Renovate from even proposing the upgrade.

How It Works with Flux

The workflow after Renovate is set up:

  1. Renovate scans the repo every weekend (or on push)
  2. Detects an outdated version, say louislam/uptime-kuma:1.23.172.1.3
  3. Opens a PR with the version bump in the deployment YAML
  4. If the package rule says automerge: true, it merges automatically
  5. If not, you review the PR and merge manually
  6. Flux detects the new commit on main and reconciles within 10 minutes
  7. The pod restarts with the new image

The entire chain (detection, PR, merge, deployment) happens without touching kubectl. For automerged patches, it happens without touching anything at all.

The First Scan

Within minutes of deploying the config, Renovate opened six PRs. Turns out several things had fallen behind:

PRUpdateTypeResult
#1nginx → 1.29MinorMerged
#2cloudflared → 2025.11.1Patch (stale)Closed, superseded by #4
#3cloudflared → 2025.2.1PatchMerged
#4cloudflared → 2026.xMajorMerged after review
#5kube-prometheus-stack → v82Major HelmMerged after review
#6uptime-kuma → v2MajorMerged after review

Six updates I hadn't noticed. The cloudflared one was two major versions behind. Without Renovate, these would have sat there until something broke or I happened to check.

What Renovate Tracks

Renovate automatically detects version references across two manager types:

Flux manager parses HelmRelease manifests for chart versions:

  • Traefik, Cilium, cert-manager, Longhorn, Authentik, kube-prometheus-stack, Metrics-Server

Kubernetes manager parses deployment/cronjob manifests for container image tags:

  • Ghost, Miniflux, PostgreSQL, Uptime Kuma, Vaultwarden, Navidrome, Audiobookshelf, Linkding, Mealie, Actual Budget, IT-Tools, Stirling PDF, Homepage, ntfy, cloudflared, Alpine (backup jobs)

That's 20+ versioned dependencies tracked automatically. Each one would otherwise require manually checking upstream for new releases.

Schedule Tuning

"schedule": ["every weekend"] means Renovate only opens PRs on Saturday and Sunday. During the week, it still scans on every push. It just queues the PRs until the weekend. This prevents a stream of dependency PRs interrupting weekday work.

If you want to force a scan, temporarily change the schedule to "at any time", push, and revert. Or enable the Dependency Dashboard (a GitHub issue where you can check boxes to force specific updates).

Rate limiting is also configured: prHourlyLimit: 5 and prConcurrentLimit: 10 prevent Renovate from flooding the repo with 20 PRs at once on the first scan.

What I Learned

Set up Renovate from day one. Manual version tracking doesn't scale: even a small homelab has 20+ versioned dependencies. I set it up at stage 19 of a 19-stage build. By then, things were already behind. Every week without automated updates is another week of security patches you're missing.

Pin databases to major versions with allowedVersions. PostgreSQL and Redis major bumps require data migrations that a simple image tag bump can't handle. Let Renovate auto-merge patch and minor updates, but use allowedVersions: "16.*" to prevent it from proposing a major upgrade that would corrupt your data.

Group infrastructure Helm charts, separate app images. If Traefik, Longhorn, and cert-manager all update in the same week, you want them in one PR so you can test the infrastructure as a unit. App images are independent: Uptime Kuma updating doesn't affect Miniflux, so separate PRs let you merge them individually.

The hosted GitHub App beats self-hosting for homelabs. Zero cluster resources, zero maintenance, zero token management. Self-hosting makes sense when you need air-gapped scanning or custom registries. For a public GitHub repo with Docker Hub and Helm chart repos, the hosted app is the right call.

Automerge patches, review everything else. A patch update (1.23.161.23.17) is a bug fix. A minor update (1.231.24) adds features. A major update (1.x2.x) breaks things. Match your review effort to the risk level: automate the safe stuff, pay attention to the rest.