Image Patching and Lifecycle#
Building a container image and deploying it is the easy part. Keeping it patched over weeks, months, and years is where most teams fail. A container image deployed today with zero known vulnerabilities will accumulate CVEs as new vulnerabilities are disclosed against its OS packages, language runtime, and dependencies. You need an automated system that detects stale base images, triggers rebuilds, and rolls out updates safely.
The Core Problem: Pinned vs Floating Tags#
Pinning a base image to a specific version (python:3.12.4-slim) gives you reproducibility. You know exactly what you are running. But pinned images do not receive security patches automatically. You must actively update the pin.
Floating tags (python:3.12-slim) automatically pick up patch releases when you rebuild. But they introduce non-determinism: the same Dockerfile produces different images on different days. A rebuild on Tuesday might pull in a broken patch release.
The practical answer: pin your base images, but automate the process of updating those pins. This gives you both reproducibility and freshness.
Dependabot for Dockerfiles#
GitHub’s Dependabot can monitor Dockerfiles and open PRs when new base image versions are available.
.github/dependabot.yml:
version: 2
updates:
- package-ecosystem: docker
directory: "/"
schedule:
interval: weekly
day: monday
reviewers:
- "platform-team"
labels:
- "dependencies"
- "docker"
allow:
- dependency-type: directWhen a new python:3.12.5-slim is published, Dependabot opens a PR updating the FROM line. Your CI pipeline builds and tests the new image. A human reviews and merges.
Limitations: Dependabot only handles the FROM line. It does not update pinned apt-get package versions or language dependency files inside the Dockerfile.
Renovate Bot#
Renovate is more flexible than Dependabot. It handles Dockerfiles, Docker Compose files, Kubernetes manifests, Helm values, and more.
renovate.json:
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"docker": {
"enabled": true,
"pinDigests": true
},
"packageRules": [
{
"matchDatasources": ["docker"],
"matchPackageNames": ["python"],
"allowedVersions": "/^3\\.12\\./"
},
{
"matchDatasources": ["docker"],
"matchPackageNames": ["nginx"],
"automerge": true,
"automergeType": "pr",
"requiredStatusChecks": ["ci/build", "ci/scan"]
}
],
"kubernetes": {
"fileMatch": ["k8s/.+\\.yaml$"]
}
}Key Renovate capabilities beyond Dependabot:
- Digest pinning – Renovate can pin
python:3.12-slimto its digest (python:3.12-slim@sha256:...) and update the digest when the tag is republished with patches. - Kubernetes manifest updates – It can update image references in your deployment YAML files, not just Dockerfiles.
- Automerge with checks – For low-risk base image updates (like nginx patch versions), Renovate can auto-merge after CI passes.
- Version filtering – Constrain updates to a specific major/minor range.
Automated Rebuilds on Base Image Changes#
For teams that build many images from a shared base, trigger rebuilds when the base image changes.
GitHub Actions triggered by a schedule and base image check:
name: Rebuild on base image update
on:
schedule:
- cron: "0 6 * * *" # Daily at 6 AM UTC
jobs:
check-and-rebuild:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check if base image changed
id: check
run: |
# Get current digest from Dockerfile
CURRENT=$(grep -oP 'sha256:\w+' Dockerfile)
# Get latest digest from registry
LATEST=$(crane digest python:3.12-slim)
if [ "$CURRENT" != "$LATEST" ]; then
echo "changed=true" >> $GITHUB_OUTPUT
echo "digest=$LATEST" >> $GITHUB_OUTPUT
fi
- name: Update Dockerfile
if: steps.check.outputs.changed == 'true'
run: |
sed -i "s|sha256:[a-f0-9]*|${{ steps.check.outputs.digest }}|" Dockerfile
git config user.name "github-actions"
git config user.email "actions@github.com"
git checkout -b update-base-image
git commit -am "chore: update base image digest"
git push origin update-base-image
gh pr create --title "Update base image" --body "Base image digest changed"Kubernetes Image Update Controllers#
These tools watch container registries for new image tags and update your deployments automatically, following GitOps principles.
Flux Image Automation#
Flux can monitor a registry for new tags matching a pattern and commit updated image references to your Git repository.
# ImageRepository: tells Flux which registry to watch
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
name: myapp
namespace: flux-system
spec:
image: myregistry.io/myapp
interval: 5m
---
# ImagePolicy: defines which tags to track
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
name: myapp
namespace: flux-system
spec:
imageRepositoryRef:
name: myapp
policy:
semver:
range: ">=1.0.0"
---
# ImageUpdateAutomation: commits changes to Git
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageUpdateAutomation
metadata:
name: myapp
namespace: flux-system
spec:
interval: 5m
sourceRef:
kind: GitRepository
name: fleet-repo
git:
checkout:
ref:
branch: main
commit:
author:
name: flux
email: flux@example.com
messageTemplate: "chore: update myapp to {{.NewTag}}"
push:
branch: main
update:
path: ./clusters/production
strategy: SettersIn your deployment manifest, add a marker comment:
containers:
- name: myapp
image: myregistry.io/myapp:1.4.2 # {"$imagepolicy": "flux-system:myapp"}Flux updates the tag in Git when a new semver-matching tag appears in the registry.
ArgoCD Image Updater#
For ArgoCD users, the Image Updater watches registries and updates image tags in ArgoCD Application specs:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
annotations:
argocd-image-updater.argoproj.io/image-list: myapp=myregistry.io/myapp
argocd-image-updater.argoproj.io/myapp.update-strategy: semver
argocd-image-updater.argoproj.io/myapp.allow-tags: "regexp:^[0-9]+\\.[0-9]+\\.[0-9]+$"
argocd-image-updater.argoproj.io/write-back-method: gitKeel#
Keel is a lighter-weight option. Annotate your deployment directly:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
labels:
keel.sh/policy: major
keel.sh/trigger: poll
keel.sh/pollSchedule: "@every 5m"Keel modifies the deployment in-cluster directly rather than committing to Git, which makes it simpler but less auditable than Flux or ArgoCD.
Rolling Out Image Updates Safely#
Automated image updates should follow the same safety practices as any deployment:
- CI must pass – Never auto-deploy an image that has not been built and tested by your pipeline.
- Canary or staged rollout – Update staging first. Promote to production only after validation.
- Rollback plan – With GitOps, rolling back is a
git revert. The previous image tag is in your commit history. - Monitoring gates – Flux and Argo Rollouts support health checks and automated rollback if metrics degrade after an update.
Dealing with End-of-Life Base Images#
Base images reach end of life. Python 3.8, Node 16, Ubuntu 20.04 – they all stop receiving security patches eventually. When this happens:
- Check EOL dates proactively. The endoflife.date project tracks these. Renovate can warn you when a base image version is approaching EOL.
- Plan migration early. Upgrading a major Python or Node version is not trivial. Allow time for testing.
- Do not just rebuild. Rebuilding an image on an EOL base gives you a fresh build timestamp but the same unpatched vulnerabilities.
- Pin to the successor. Move from
python:3.8-slimtopython:3.12-slim, not topython:3-slim(which is a floating major version tag).
When to Rebuild vs When to Patch#
Rebuild when the base image has a new version, when your application dependencies changed, or when a CVE affects an OS package in your image. Rebuilding from a patched base image is the cleanest fix.
In-place patching (running apt-get upgrade inside a running container) is almost never correct. It creates drift between your running container and your image, breaks reproducibility, and the patch is lost when the pod restarts.
The rule is simple: the image is the source of truth. If you need a change, rebuild the image.
Key Takeaways#
- Pin base images and automate the process of updating pins with Dependabot or Renovate.
- Renovate is more capable than Dependabot for container workflows: digest pinning, Kubernetes manifest updates, and automerge.
- Use Flux Image Automation or ArgoCD Image Updater for GitOps-driven image updates in Kubernetes.
- Never patch running containers. Rebuild the image from a patched base.
- Track base image EOL dates and plan major version migrations before security patches stop.