Dependency Update Review
1. Overview
Automated checks propose third-party dependency updates as pull requests. They do not apply them. This guide covers the weekly review: where to look, how to decide, how to implement, and how to reverse it.
The design behind the checks — why references are pinned as tag@digest, and why the checks are scoped the way they are — is Dependency Update Checks.
|
For the GitOps repositories, merging is deploying. ArgoCD runs with |
2. Cadence and Where to Look
Checks run Monday 07:00 SAST. Proposals appear as pull requests labelled dependencies, plus a component label.
# Open dependency proposals across the GitOps repository
gh pr list --repo christhonie/idl-xnl-jhb-rc01 --label dependencies --state open
# Full detail for one proposal
gh pr view <number> --repo christhonie/idl-xnl-jhb-rc01
A proposal changes the tag and digest together:
- image: lscr.io/linuxserver/openssh-server:10.2_p1-r0-ls229@sha256:67d4c3a1…
+ image: lscr.io/linuxserver/openssh-server:10.2_p1-r0-ls230@sha256:9f21ab4c…
|
A proposal against one of our own images ( |
3. Assessing a Proposal
3.1. Step 1: Classify the Exposure Tier
How much scrutiny a change deserves depends mostly on what can reach the component.
| Tier | Components | Scrutiny |
|---|---|---|
Internet-facing |
SSH bastion ( |
Highest. Read the upstream release notes in full, with specific attention to changed defaults around authentication, rate limiting, and cryptography. |
Cluster-internal |
|
Moderate. Confirm no configuration-surface or data-format change; check for a required migration. |
Build-time / non-production |
|
Low. A changelog skim is sufficient. |
3.2. Step 2: Classify the Change Magnitude
| Magnitude | Looks like | Handling |
|---|---|---|
Rebuild only |
Same upstream version, new build suffix — |
Usually base-image security patches. Normal weekly approval. |
Patch / minor |
|
Read release notes for behaviour and default changes. Approve if none affect our configuration. |
Major |
|
Do not merge on the weekly sweep. Raise a work item and schedule it with time to test. |
3.3. Step 3: Apply the Approval Conditions
All of the following must hold. If any fails, defer — see Deferring or Rejecting a Proposal.
-
Release notes reviewed for every version between the current and proposed reference, not only the newest.
-
No default-behaviour change that interacts with our configuration. For internet-facing components this is the condition that matters most; see the warning below.
-
Tag and digest are consistent — the proposed digest genuinely belongs to the proposed tag.
-
Not inside a change freeze — no release in flight, and no event weekend where the affected system is in use.
-
A verifier is available — someone can run the post-merge checks now, not tomorrow.
-
Rollback is understood — you know which commit to revert.
|
Condition 2 is the one that has actually bitten us. The 2026-08-17 bastion lockout was caused by an upstream default, not a broken build: OpenSSH enables For any sshd or gateway update, explicitly check whether upstream changed defaults for authentication, connection rate limiting, penalties, or accepted algorithms — and record what you checked in the pull request. |
4. Implementing an Approved Update
-
Merge the pull request to
main.gh pr merge <number> --repo christhonie/idl-xnl-jhb-rc01 --squash -
Wait for ArgoCD to sync — typically under three minutes. Do not apply manifests by hand;
selfHealwill revert anything applied out of band. -
Confirm the rollout completed.
kubectl rollout status ds/ssh-bastion -n bastion --timeout=180s kubectl get pods -n bastion -o wide -
Run the component verification below.
-
Record the outcome in the pull request — what you verified, and anything unexpected.
4.1. Verification: SSH Bastion
The bastion is a DaemonSet, so an update rolls one pod at a time and an established tunnel drops when its own node’s pod cycles. Warn anyone holding a tunnel first.
# 1. All pods healthy on every node
kubectl get ds ssh-bastion -n bastion
kubectl get pods -n bastion -o wide
# 2. Authentication works through every node IP.
# Node IPs and the key path are in the bastion-tunnel skill.
for ip in <node-ip-1> <node-ip-2> <node-ip-3>; do
ssh -i <key> -o BatchMode=yes -p 30022 tunnel@$ip 'echo OK; hostname'
done
Expected: OK from each, each reporting a different pod name — that confirms externalTrafficPolicy: Local is still routing node-locally. A host-key mismatch warning means the shared host-key Secret was not mounted correctly; stop and investigate.
# 3. Effective sshd configuration is unchanged
kubectl exec -n bastion <pod> -- \
/usr/sbin/sshd.pam -T -f /config/sshd/sshd_config \
-h /config/ssh_host_keys/ssh_host_ed25519_key \
| grep -iE 'persource|penalt|maxstartups|logingrace'
Compare against the recorded baseline:
logingracetime 30
maxstartups 10:30:100
persourcemaxstartups none
persourcenetblocksize 32:128
persourcepenaltyexemptlist none
persourcepenalties crash:90 authfail:5 noauth:1 grace-exceeded:10 \
refuseconnection:10 max:600 min:15 max-sources4:65536 \
max-sources6:65536 overflow:permissive overflow6:permissive
Any difference is a behaviour change that arrived with the image. Assess it before leaving the update in place, and update this baseline if you accept it.
|
The bastion runs with
Without Note that |
5. Rolling Back
Reverting the merge is the only correct route — selfHeal will undo a manual kubectl change.
git -C ~/dev/idl-xnl-jhb-rc01 revert <merge-sha>
git -C ~/dev/idl-xnl-jhb-rc01 push origin main
ArgoCD re-syncs to the previous digest within a few minutes. Re-run the component verification, then comment on the original pull request explaining what failed, so the proposal is not blindly re-approved next week.
6. Deferring or Rejecting a Proposal
Leaving a proposal open is a decision that decays — next week’s proposal supersedes it and the reasoning is lost. Close it explicitly with a comment recording why.
| Situation | Action |
|---|---|
Needs scheduled work (major version) |
Raise an ADO work item, link it in the pull request, then |
This version is bad, later ones may be fine |
|
Deliberately staying on the current major |
|
Superseded / stale branch |
|
Never use @dependabot ignore this dependency — it silences the component permanently and reintroduces the frozen-pin risk the checks exist to prevent.
7. Escalation
-
Update merged and the component is degraded → roll back first, diagnose afterwards.
-
Bastion unreachable through all node IPs after an update → roll back.
kubectlaccess does not depend on the bastion, so recovery is always available. -
Proposal appears against one of our own application images → do not merge; fix the scoping in
.github/dependabot.yml.
8. Related
-
Dependency Update Checks — the design: pinning model, tooling choice, and scoping rules.
-
ArgoCD Deployment — sync behaviour and why manual changes are reverted.
-
OpenTelemetry Configuration — central log shipping, which closes the ephemeral-log gap noted above.