Skip to main content

Dependency Auditability β€” Version Pinning, Digest Locking & Renovate

Every open-source tool deployed via Helm (Open WebUI, Grafana, Vault, cert-manager, etc.) has no source code in this repository. That creates a silent auditability gap: configuration changes are git-tracked, but the upstream software versions, why they were chosen, and what changed between versions are not.

This runbook documents the three-layer strategy used to close that gap on minicloud.


The Problem: What Was Not Auditable​

When a Helm-managed tool is deployed with a wildcard version like "5.*" or "0.10.x", the actual version that gets installed depends on what the chart repository serves at sync time. Two syncs of the same ArgoCD app can install different chart versions β€” and nothing in git records which one ran when.

Similarly, a container image tag like 0.9.4 is mutable. An upstream maintainer can push a new build to that same tag. The next pod restart pulls different code with an identical version label, and your git history still says 0.9.4.

There are three distinct gaps:

GapSymptomRoot cause
Wildcard chart versionsTwo ArgoCD syncs install different versionstargetRevision: "5.*" resolves differently over time
Mutable image tagsPod restarts pull different code silentlyimage: tool:1.2.3 tag can be overwritten by upstream
No upgrade trailUnknown why a version was chosen or what changedNo link between git commits and upstream changelogs

Layer 1 β€” Exact Chart Version Pinning​

All ArgoCD Application manifests in minicloud-gitops/apps/ pin Helm charts to an exact version. No wildcards.

Before (non-deterministic):

# apps/kured.yaml
targetRevision: "5.*" # could resolve to 5.10, 5.11, 5.12 on any sync

After (deterministic):

# apps/kured.yaml
targetRevision: "5.12.1" # always resolves to exactly 5.12.1

Charts pinned during the 2026-07-08 hardening pass:

ChartWildcard (before)Pinned version (after)
kured5.*5.12.1
polaris6.*6.0.0
external-secrets0.10.x0.10.7

All other charts were already on exact versions.

How to find the running version when pinning​

If an app is already deployed with a wildcard and you need to find what's actually running:

# From the Helm release label on any managed resource
kubectl get daemonset -n <namespace> <name> \
-o jsonpath='{.metadata.labels.helm\.sh/chart}'
# β†’ kured-5.12.1

# Or from the ArgoCD sync history
kubectl get application -n argocd <name> \
-o jsonpath='{.status.history[-1].revision}'
# β†’ 0.10.7

Pin to that exact version in the ArgoCD Application YAML, commit, and the next sync is a no-op (same version, now declared).


Layer 2 β€” Image Digest Pinning​

Chart version pinning locks the Helm chart. But the chart specifies an image tag, and tags are mutable. To lock the exact bytes of the running container, pin by digest.

A digest (sha256:...) is a content hash of the image layers β€” it is physically impossible for two different images to share the same digest. Unlike a tag, it cannot be overwritten.

Open WebUI β€” digest pinned in helm-values​

Open WebUI is the only chart where the image configuration is explicitly set in our Helm values (because the init containers reference the same image). The values file at helm-values/open-webui-values.yaml pins it:

image:
repository: ghcr.io/open-webui/open-webui
tag: "0.9.4"
digest: "sha256:172e1fe0e89af2a07f42f2b1d943f30c8ddd7b9c2e182f3678b4790cdb83abea"
pullPolicy: IfNotPresent

The two init containers (inject-minicloud-ca, patch-bm25-french) also use the full digest reference:

image: ghcr.io/open-webui/open-webui:0.9.4@sha256:172e1fe0e89af2a07f42f2b1d943f30c8ddd7b9c2e182f3678b4790cdb83abea

How to get a digest for any image​

# Using crane (already installed β€” used for Harbor pushes)
crane digest ghcr.io/open-webui/open-webui:0.9.4
# β†’ sha256:172e1fe0e89af2a07f42f2b1d943f30c8ddd7b9c2e182f3678b4790cdb83abea

# Or via docker
docker inspect --format='{{index .RepoDigests 0}}' ghcr.io/open-webui/open-webui:0.9.4

How to upgrade (tag + digest together)​

When a new Open WebUI version is released:

# 1. Get the new digest
crane digest ghcr.io/open-webui/open-webui:0.10.0

# 2. Update helm-values/open-webui-values.yaml:
# - tag: "0.9.4" β†’ tag: "0.10.0"
# - digest: "sha256:172e..." β†’ digest: "sha256:<new>"
# - Both init container image lines

# 3. Commit with changelog reference:
git commit -S -m "chore(deps): bump open-webui 0.9.4 β†’ 0.10.0

Changelog: https://github.com/open-webui/open-webui/releases/tag/v0.10.0
Digest: sha256:<new>
Review: checked ENABLE_MEMORY_FEATURE default β€” unchanged in 0.10.0"

The commit message IS the audit record. Reference the upstream changelog URL so future maintainers know what changed and that it was reviewed.


Layer 3 β€” Renovate Bot (Automated Upgrade PRs)​

Renovate Bot runs on andrelair-platform/minicloud-gitops via the Mend Renovate GitHub App.

It scans the repository weekly (weekends) and opens pull requests when upstream releases a new version of any tracked chart or image. Each PR includes the upstream changelog β€” making the upgrade motivation visible without any manual research.

What Renovate monitors​

Configured in renovate.json at the repo root:

ManagerFiles watchedWhat it tracks
argocdapps/*.yamltargetRevision for all Helm chart sources
helm-valueshelm-values/*.yamlContainer image tags referenced in values files

What a Renovate PR looks like​

Title: chore(deps): bump open-webui from 14.4.0 to 14.5.0

## Release Notes
open-webui/open-webui (open-webui)
### v0.10.0
- feat: new memory management UI
- fix: ENABLE_MEMORY_FEATURE now defaults to false in fresh installs ← important!
- ...

Changelog: https://github.com/open-webui/open-webui/releases/tag/v0.10.0

The changelog is fetched automatically. You read it, decide if it's safe, and merge.

Labels and review rules​

ChartLabelAction required
vault, argo-cd, authentikmanual-review-requiredRead changelog fully β€” these can break SSO or GitOps control plane
open-webuicheck-default-changesCheck if any env var defaults changed β€” may affect feature behavior
cert-manager, external-secrets, nginx-ingresspatchPatch updates are lower risk β€” still review before merging
All othersdependencies, renovateStandard review

Schedule​

Renovate runs every weekend. During the week, it queues discovered updates but does not open PRs. This prevents daily noise while ensuring nothing stays unpatched for more than 7 days.

Dependency Dashboard​

Renovate maintains a live Dependency Dashboard issue in minicloud-gitops. It shows:

  • All packages currently tracked
  • Which ones have available updates (pending the weekend schedule)
  • Any PRs currently open
  • Rate-limited or ignored updates

Find it at: github.com/andrelair-platform/minicloud-gitops/issues β€” look for the issue titled "Dependency Dashboard" opened by renovate[bot].


Audit Trail Summary​

With all three layers in place, every dependency on the platform has a complete audit trail:

QuestionWhere the answer lives
What chart version is running?apps/<name>.yaml β†’ exact targetRevision
What config was applied?helm-values/<name>-values.yaml β†’ git history
What exact image bytes are running?helm-values/open-webui-values.yaml β†’ digest: field
Why was this version chosen?Renovate PR body β†’ upstream changelog link
When was it upgraded?Git commit timestamp on the merged Renovate PR
Who approved the upgrade?GitHub PR merge β€” requires PR review
What changed upstream?Renovate PR body embeds release notes

Operational Reference​

Check if an upgrade is available right now​

# Check Renovate dashboard issue
gh issue list --repo andrelair-platform/minicloud-gitops --label renovate

# Or check latest chart version manually
helm repo add <name> <url>
helm search repo <chart> --versions | head -5

Force Renovate to scan immediately (bypass weekend schedule)​

Go to the Dependency Dashboard issue in GitHub and check the box next to any package β€” Renovate opens a PR immediately regardless of schedule.

Or trigger a full scan at: https://developer.mend.io/github/andrelair-platform

Add digest pinning to a new chart​

Only needed when you explicitly configure the image in your values file. For charts where you don't override the image, pinning the chart version (targetRevision) is sufficient β€” the chart pins the image tag internally.

# 1. Find the image the chart deploys
kubectl get deployment -n <ns> <name> \
-o jsonpath='{.spec.template.spec.containers[0].image}'

# 2. Get its digest
crane digest <image>:<tag>

# 3. Add to helm-values/<name>-values.yaml
image:
tag: "<tag>"
digest: "sha256:<hash>"