| name | version-management |
| description | Use when asking about version management, Renovate annotations, versions.ts patterns, or pinning image/chart versions. |
Version Management
Overview
src/cdk8s/src/versions.ts is the single source of truth for all versions in the homelab. It uses Renovate annotations for automated dependency updates.
File Structure
const versions = {
"argo-cd": "9.2.0",
"linuxserver/sonarr":
"4.0.16@sha256:8b9f2138ec50fc9e521960868f79d2ad0d529bc610aef19031ea8ff80b54c5e0",
"shepherdjerred/temporal-worker": "latest",
};
export default versions;
Adding a New Version
Helm Chart
"mychart": "1.2.3",
Docker Image (with digest)
"org/image": "1.0.0@sha256:abc123def456...",
Docker Hub Image
"library/nginx": "1.25.0@sha256:...",
GitHub Release
"owner/repo": "v1.2.3",
Custom/Manually-Managed (No Renovate)
"myorg/custom-image": "latest",
Renovate Annotation Format
// renovate: datasource={source} registryUrl={url} versioning={scheme}
Datasources
| Datasource | Use For |
|---|
helm | Helm charts |
docker | Container images |
github-releases | GitHub releases |
custom.papermc | Custom registries |
Registry URLs
| Registry | URL |
|---|
| Docker Hub | https://docker.io |
| GitHub Container Registry | https://ghcr.io |
| Quay.io | https://quay.io |
| Helm chart repos | Chart-specific URL |
Versioning Schemes
| Scheme | Use For |
|---|
semver | Semantic versioning (1.2.3) |
docker | Docker tag conventions |
loose | Non-standard versions |
Usage in Code
Container Images
import versions from "../versions.ts";
deployment.addContainer({
image: `ghcr.io/linuxserver/sonarr:${versions["linuxserver/sonarr"]}`,
});
Helm Charts
import versions from "../../versions.ts";
new Application(chart, "myapp", {
spec: {
source: {
targetRevision: versions["myapp"],
chart: "myapp",
},
},
});
SHA256 Digests
Always include digests for production images:
"org/image": "1.0.0@sha256:abc123...",
"org/image": "1.0.0",
Benefits:
- Immutable deployments
- Reproducible builds
- Security (prevents tag mutation attacks)
Getting the digest:
crane digest ghcr.io/org/image:1.0.0
docker pull ghcr.io/org/image:1.0.0
docker inspect ghcr.io/org/image:1.0.0 --format='{{index .RepoDigests 0}}'
Talos / Kubernetes Pins Reflect Deployed Reality
The "kubernetes/kubernetes" and "siderolabs/talos" entries in versions.ts must match the version actually deployed and running on torvalds — not whatever upstream Renovate would bump to. These two are not consumed by code; they exist for Renovate to track AND as a source-of-truth record of cluster state.
After any talosctl upgrade or talosctl upgrade-k8s that lands on a version different from the existing pin (e.g. the Sidero kubelet image for the latest patch isn't published yet, so you pick the prior k8s patch), update versions.ts and the README upgrade snippet (packages/homelab/README.md VERSION= example lines) to the now-running version in the same change. If they drift from reality, future upgrade sessions can't tell a Renovate target from a record of what's deployed.
First-Party Image Versions (automated by version commit-back)
First-party image entries are rewritten by the replatformed static Buildkite
pipeline (.buildkite/pipeline.yml, landed 2026-07 after the old Dagger CI was
removed): the images step bakes/pushes images (tags :$GIT_SHA + :latest;
the 2.0.0-<build> in a pin is a cosmetic label on a digest-pinned ref) and
records content-gated digests, and the version commit-back step
(scripts/update-versions.ts --commit-back) opens the auto-merge
"chore: bump pending image versions" PR rewriting the matching pins:
"shepherdjerred/temporal-worker":
"2.0.0-1020@sha256:…",
Commit-back only rewrites bare keys and /beta stage keys — never /prod.
/beta and /prod are deployment-stage keys, not image names
App images publish to a single GHCR package (e.g. ghcr.io/shepherdjerred/scout-for-lol:2.0.0-710) — there is no /beta or /prod in the image name. But versions.ts has separate …/beta and …/prod entries because they are deployment stages that may pin different versions:
"shepherdjerred/scout-for-lol/beta": "2.0.0-710@sha256:…",
"shepherdjerred/scout-for-lol/prod": "2.0.0-700@sha256:…",
The catalog's versionKey (used in --tags ghcr.io/{versionKey}:…) must not carry a /beta|/prod suffix; only the versions.ts entries and the cdk8s resources that read them use the stage suffixes to deploy a different version per stage.
First-party prod pins are Renovate promotions (minted release tags)
shepherdjerred/scout-for-lol/prod and shepherdjerred/starlight-karma-bot/prod
carry Renovate annotations (docker datasource) and are promoted by merging
the Renovate PR — the "Prod images" packageRule pins them to
automerge: false. Renovate can only offer tags CI minted:
- scout: the
scout-tag-release pipeline step mints
ghcr.io/shepherdjerred/scout-for-lol:2.0.0-<n> only after site version
<n> is archived, pointing at the backend digest beta serves it against —
each tag is an atomic backend+site release pair. There is no separate
site pin: scout-prod-reconcile derives the prod site version from the
pin's tag portion, so one pin moves both halves in lockstep (the tRPC-skew
guarantee lives in the tag mint, not in paired pins).
- starlight-karma-bot:
bake-images.sh pushes a 2.0.0-<build> tag
whenever a content change records a digest.
Rollback = git revert the promotion merge, or hand-edit the pin to any
older minted tag@digest. Never pin a tag CI didn't mint or hand-pair a
tag with a different digest. See packages/scout-for-lol/AGENTS.md § Stage
deploys.
Renovate Configuration
The project uses Renovate for automated updates:
- Renovate parses
versions.ts looking for annotations
- Creates PRs for version bumps
- PRs run
bun run verify (affected-scoped) on the static Buildkite pipeline (.buildkite/pipeline.yml) — check the buildkite/monorepo/pr status before merging
Digest/pin updates bypass minimumReleaseAge
minimumReleaseAge + internalChecksFilter: strict only hold back major/minor/patch PRs (Dependency Dashboard "Pending Status Checks"); they do not apply to digest / pinDigest / pin updates, which open immediately and would otherwise merge before the window. The Buildkite stability guard that used to block these (renovateStabilityPending() in the CI generator) was removed with the pipeline 2026-07 — check the renovate/stability-days status yourself before merging a digest/pin PR. Escape hatch for a fast-moving digest: a minimumReleaseAge: "0 days" packageRule. (renovate-config-validator segfaults under Bun — run via npx --yes --package renovate -- renovate-config-validator renovate.json.)
Never silence upstream-blocked items
Do not add packageRules with enabled: false to renovate.json to suppress dashboard items that are blocked by an upstream peer/compat issue. Silencing hides a live constraint (same failure mode as swallowing exceptions) and requires remembering to remove the rule later. Instead, leave the item surfaced, document the block and its unblock condition in a tracking doc (packages/docs/plans/*), and re-probe each session until it clears. "Blocked for months" is not a reason to silence — a still-blocked dashboard is correctly reporting reality.
Best Practices
- Always use annotations for external dependencies
- Include SHA256 digests for container images
- Use semantic versioning when possible
- Mark internal images as "not managed by renovate"
- Group related updates (e.g., linuxserver images)
Common Patterns
Multiple Images from Same Org
"linuxserver/sonarr": "4.0.16@sha256:...",
"linuxserver/radarr": "5.2.6@sha256:...",
"linuxserver/bazarr": "1.4.0@sha256:...",
Helm Chart with Custom Registry
"gitlab": "7.8.0",
Key Files
src/cdk8s/src/versions.ts - Version registry
renovate.json - Renovate configuration