Production-grade platform engineering handbook — Kubernetes, Terraform, Flux CD, GitHub Actions, AWS, and more.
67
84%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Passed
No known issues
Companion to /platform-skills:renovate. Deep-dive on managers, presets, security, GitOps integration, private registries, custom regex managers, pre-commit hooks, and troubleshooting.
| Manager | File patterns | Notes |
|---|---|---|
github-actions | .github/workflows/*.yml | Always pinDigests: true — commit SHA instead of tag |
terraform | *.tf, *.tfvars | Covers required_providers and module blocks |
helmv3 | Chart.yaml, requirements.yaml | Helm 3 chart dependencies |
gomod | go.mod | Go module dependencies; use gomodTidy in postUpdateOptions |
npm | package.json, package-lock.json, yarn.lock | Node packages; use npmDedupe in postUpdateOptions |
pip_requirements | requirements*.txt | Pinned Python packages |
pip-compile | requirements*.in | pip-tools input files |
pipenv | Pipfile, Pipfile.lock | Pipenv-managed projects |
poetry | pyproject.toml (Poetry) | Poetry-managed projects |
dockerfile | Dockerfile, Dockerfile.* | FROM image tags |
docker-compose | docker-compose*.yml | Service image tags |
kubernetes | Manifests with kind: Deployment/StatefulSet/DaemonSet/Job/CronJob | Container image tags in spec.containers[].image |
cargo | Cargo.toml | Rust crate dependencies |
regex | Any file via fileMatch | Custom version strings — no native manager support |
config:recommended includes:
| Preset | Effect |
|---|---|
config:recommended | Base: semantic PR titles, dependency dashboard, labels, schedule |
:dependencyDashboard | Creates a GitHub Issue listing all pending/blocked updates |
:semanticCommits | Commit messages follow Conventional Commits format |
:separateMajorReleases | Major version bumps get their own PR, never grouped |
Useful add-ons:
| Preset | When to add |
|---|---|
:pinDigests | Global digest pinning — prefer scoped to github-actions only |
:automergeMinor | Automerge all minor updates — use only if CI is comprehensive |
:automergeDigest | Automerge digest-only updates (no version change) |
security:openssf-scorecard | Adds OpenSSF Scorecard checks to PRs |
helpers:pinGitHubActionDigests | Shorthand for GitHub Actions digest pinning |
| Ecosystem | Recommended automerge | Reason |
|---|---|---|
github-actions | Never | SHA pinning means every update is significant |
terraform providers | Minor + patch | Providers are stable; breaking changes follow semver |
terraform modules | Never | Module updates may change resource topology |
helmv3 | Patch only | Charts may have templating changes in minor |
gomod | Minor + patch | Go semver is well-respected |
npm | Minor + patch | With minimumReleaseAge: "3 days" to avoid supply chain risk |
pip | Minor + patch | Same — pin to minimumReleaseAge |
docker/kubernetes | Never | Image updates may include breaking app changes |
cargo | Minor + patch | Cargo semver is well-respected |
Group related updates into one PR to reduce noise:
{
"description": "Group all AWS provider updates",
"matchManagers": ["terraform"],
"matchPackageNames": ["hashicorp/aws"],
"groupName": "AWS provider"
},
{
"description": "Group all patch updates across ecosystems",
"matchUpdateTypes": ["patch"],
"matchCurrentVersion": "!/^0/",
"groupName": "Patch updates",
"automerge": true,
"minimumReleaseAge": "3 days"
}Weekly, low disruption:
{ "schedule": ["before 6am on monday"] }Nightly, non-work hours:
{ "schedule": ["after 10pm every weekday"] }Immediate (vulnerability alerts):
{ "schedule": ["at any time"] }minimumReleaseAge for supply chain safetyAlways set on automerge rules to prevent typosquatting and freshly-published malicious packages:
{
"matchUpdateTypes": ["minor", "patch"],
"automerge": true,
"minimumReleaseAge": "3 days"
}Renovate creates a GitHub Issue titled "Renovate Dependency Dashboard" when dependencyDashboard: true.
What it shows:
How to trigger a selective update:
How to find the dashboard:
gh issue list --label "renovate" --state openResetting the dashboard: Close and reopen the issue — Renovate recreates it on the next run.
Enable explicitly (already in config:recommended but good to be explicit):
{
"dependencyDashboard": true,
"dependencyDashboardTitle": "Renovate Dependency Dashboard"
}Both can update container image references. Running both on the same files causes conflicts.
Recommended split:
| Responsibility | Owner |
|---|---|
Helm chart versions in HelmRelease | Renovate (helmv3 manager) |
| Terraform provider/module versions | Renovate (terraform manager) |
| Language dep versions (go.mod, package.json) | Renovate |
| GitHub Actions versions | Renovate (github-actions manager) |
| Running workload image tags (Deployment/StatefulSet) | Flux Image Reflector Controller |
| OCI artifact digest pinning in FluxInstance | Flux (ImagePolicy) |
Rule: Renovate owns anything in source control that follows semver. Flux Image Reflector owns runtime image tags for deployed workloads where you want continuous delivery without a PR.
Avoiding conflicts:
If Flux Image Reflector manages spec.containers[].image in a manifest, exclude that manifest from Renovate:
{
"ignorePaths": [
"clusters/**",
"flux-system/**"
]
}Or scope the kubernetes manager to only non-Flux paths:
{
"matchManagers": ["kubernetes"],
"matchPaths": ["apps/**", "workloads/**"],
"automerge": false
}{
"matchManagers": ["github-actions"],
"pinDigests": true
}This converts uses: actions/checkout@v4 → uses: actions/checkout@<sha> # v4.x.x, making the action tamper-proof.
{
"osvVulnerabilityAlerts": true,
"vulnerabilityAlerts": {
"enabled": true,
"labels": ["security"],
"schedule": ["at any time"]
}
}osvVulnerabilityAlerts uses the OSV database (broader coverage than GitHub Advisory Database).
minimumReleaseAge — supply chain defencePrevents automerging packages published less than N days ago, reducing exposure to typosquatting and fast-follow malicious releases:
{
"matchUpdateTypes": ["minor", "patch"],
"automerge": true,
"minimumReleaseAge": "3 days"
}rangeStrategy: pinPin exact versions instead of ranges for reproducibility:
{
"matchManagers": ["npm"],
"rangeStrategy": "pin"
}Use "bump" instead if the project publishes a library (ranges in peerDependencies must stay flexible).
{
"hostRules": [
{
"matchHost": "ghcr.io",
"username": "{{ secrets.GHCR_USERNAME }}",
"password": "{{ secrets.GHCR_TOKEN }}"
}
]
}Store secrets in Renovate's encrypted secrets (Mend Renovate App) or as env vars when self-hosted.
Use regexManagers when no native manager supports the file format.
{
"description": "Track Terraform CLI version in workflow YAML",
"fileMatch": ["^\\.github/workflows/.*\\.ya?ml$"],
"matchStrings": [
"terraform_version:\\s*['\"]?(?<currentValue>[^'\"\\s]+)['\"]?"
],
"depNameTemplate": "hashicorp/terraform",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
}{
"description": "Track kubectl version in install scripts",
"fileMatch": ["scripts/.*\\.sh$"],
"matchStrings": [
"KUBECTL_VERSION=['\"]?(?<currentValue>[^'\"\\s]+)['\"]?"
],
"depNameTemplate": "kubernetes/kubernetes",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
}{
"description": "Track Flux CLI version in docs",
"fileMatch": ["^docs/.*\\.md$", "^examples/.*\\.md$"],
"matchStrings": [
"Flux CLI \\((?<currentValue>[^)]+)\\)"
],
"depNameTemplate": "fluxcd/flux2",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
}renovate-config-validator errors| Error | Cause | Fix |
|---|---|---|
Invalid preset: config:base | Deprecated preset | Change to config:recommended |
matchManagers must be array | Scalar instead of array | "matchManagers": ["terraform"] not "matchManagers": "terraform" |
Unknown option: pinDigest | Typo — missing s | Use pinDigests (plural) |
extends: invalid | Preset not found | Check preset name at docs.renovatebot.com/presets |
The /platform-skills:renovate workflow coverage scan checks for manager name strings in renovate.json. False positives occur when:
ignorePathsrenovate.jsonSuppress by adding the path to ignorePaths and noting it in the description field of the relevant packageRule.
Renovate enforces prConcurrentLimit (open PRs at once) and prHourlyLimit (PRs per hour). If updates are being skipped:
{
"prConcurrentLimit": 10,
"prHourlyLimit": 0
}Set prHourlyLimit: 0 to disable hourly cap entirely (use with caution on large repos).
"dependencyDashboard": true is in renovate.jsonRenovate uses hostRules to authenticate with private registries. Credentials are injected at runtime via env-var templating — they never appear in renovate.json in plaintext.
{
"hostRules": [
{
"matchHost": "123456789012.dkr.ecr.us-east-1.amazonaws.com",
"hostType": "docker",
"username": "AWS",
"password": "{{ env.AWS_ECR_TOKEN }}"
}
]
}Pre-authenticate on self-hosted Renovate:
export AWS_ECR_TOKEN=$(aws ecr get-login-password --region us-east-1)Recommended for Renovate App: attach an IAM role to the Renovate App installation and use the built-in ECR credential helper — no static token needed.
{
"hostRules": [
{
"matchHost": "gcr.io",
"hostType": "docker",
"username": "_json_key",
"password": "{{ env.GCR_SERVICE_ACCOUNT_KEY }}"
}
]
}For Artifact Registry replace gcr.io with <region>-docker.pkg.dev.
{
"hostRules": [
{
"matchHost": "myregistry.azurecr.io",
"hostType": "docker",
"username": "{{ env.ACR_CLIENT_ID }}",
"password": "{{ env.ACR_CLIENT_SECRET }}"
}
]
}Use a service principal with the AcrPull role. Set ACR_CLIENT_ID and ACR_CLIENT_SECRET in the Renovate App environment or self-hosted runner secrets.
{
"hostRules": [
{
"matchHost": "registry.example.com",
"hostType": "docker",
"username": "{{ env.REGISTRY_USERNAME }}",
"password": "{{ env.REGISTRY_PASSWORD }}"
}
]
}{
"hostRules": [
{
"matchHost": "registry.example.com",
"hostType": "docker",
"username": "{{ env.HELM_REGISTRY_USERNAME }}",
"password": "{{ env.HELM_REGISTRY_PASSWORD }}"
}
],
"packageRules": [
{
"matchManagers": ["helmv3"],
"registryUrls": ["oci://registry.example.com"],
"groupName": "Private Helm charts (OCI)"
}
]
}{
"hostRules": [
{
"matchHost": "charts.example.com",
"username": "{{ env.HELM_REGISTRY_USERNAME }}",
"password": "{{ env.HELM_REGISTRY_PASSWORD }}"
}
],
"packageRules": [
{
"matchManagers": ["helmv3"],
"registryUrls": ["https://charts.example.com"],
"groupName": "Private Helm charts (HTTP)"
}
]
}{
"hostRules": [
{
"matchHost": "app.terraform.io",
"token": "{{ env.TF_REGISTRY_TOKEN }}"
}
]
}Set TF_REGISTRY_TOKEN to a Terraform Cloud team token with read access.
Use regexManagers when a dependency version appears in a file format that Renovate's built-in managers do not parse — internal GitHub module sources, pinned tool versions in scripts, or version strings in YAML/JSON config files.
Terraform module sources of the form github.com/<org>/<repo>//<path>?ref=<tag> are not picked up by the standard terraform manager. Add a regex manager:
{
"regexManagers": [
{
"description": "Terraform modules from internal GitHub org myorg",
"fileMatch": ["\\.tf$"],
"matchStrings": [
"source\\s*=\\s*\"github\\.com/myorg/(?<depName>[^/]+)//[^\"]*\\?ref=(?<currentValue>[^\"]+)\""
],
"datasourceTemplate": "github-tags",
"packageNameTemplate": "myorg/{{{depName}}}"
}
],
"packageRules": [
{
"matchManagers": ["regex"],
"matchPackagePatterns": ["^myorg/"],
"automerge": false,
"groupName": "Internal Terraform modules (myorg)"
}
]
}Replace myorg with your GitHub org. Renovate will open PRs that update ?ref=v1.2.3 to the latest tag on the referenced repo.
For modules sourced from a private registry (<hostname>/<namespace>/<module>/<provider>):
{
"regexManagers": [
{
"description": "Terraform modules from private registry app.terraform.io",
"fileMatch": ["\\.tf$"],
"matchStrings": [
"source\\s*=\\s*\"app\\.terraform\\.io/(?<namespace>[^/]+)/(?<depName>[^/]+)/(?<provider>[^\"]+)\""
],
"datasourceTemplate": "terraform-module",
"registryUrlTemplate": "https://app.terraform.io"
}
]
}{
"regexManagers": [
{
"description": "Update Terraform version pinned in GitHub Actions workflows",
"fileMatch": ["^\\.github/workflows/.*\\.ya?ml$"],
"matchStrings": [
"terraform_version:\\s*['\"]?(?<currentValue>[^'\"\\s]+)['\"]?"
],
"depNameTemplate": "hashicorp/terraform",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
}
]
}.tool-versions (asdf){
"regexManagers": [
{
"description": "Update tools pinned in .tool-versions (asdf)",
"fileMatch": ["^\\.tool-versions$"],
"matchStrings": [
"(?<depName>[a-z0-9_-]+)\\s+(?<currentValue>[\\d\\.]+)"
],
"datasourceTemplate": "github-releases",
"packageNameTemplate": "asdf-vm/asdf-{{{depName}}}"
}
]
}{
"regexManagers": [
{
"description": "Update kubectl version pinned in CI scripts",
"fileMatch": ["^\\.github/workflows/.*\\.ya?ml$", "^scripts/.*\\.sh$"],
"matchStrings": [
"kubectl_version[=:]\\s*['\"]?v?(?<currentValue>[\\d\\.]+)['\"]?"
],
"depNameTemplate": "kubernetes/kubectl",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
}
]
}Test your matchStrings pattern against actual file content:
# Install renovate locally
npm install -g renovate
# Dry-run against a single file — prints what Renovate would extract
LOG_LEVEL=debug renovate --dry-run --print-config 2>&1 | grep -A5 "regexManagers"Common mistakes:
\\s not \s)(?<version>) instead of (?<currentValue>) — the named capture must be currentValuedatasourceTemplate — required when there is no built-in datasource inferenceRun renovate-config-validator locally before every commit using the official pre-commit hook. This catches config errors before they reach CI.
Add to .pre-commit-config.yaml (create the file if it does not exist):
repos:
- repo: https://github.com/renovatebot/pre-commit-hooks
rev: 43.150.0
hooks:
- id: renovate-config-validatorInstall and activate:
pip install pre-commit # or: brew install pre-commit
pre-commit install
pre-commit run renovate-config-validator --all-filesThe rev pin tracks the Renovate release version. Once Renovate is running on the repo, it updates the rev automatically via a PR — the same way it updates any other dependency. No manual maintenance needed.
To pin with the digest for maximum supply chain security:
pre-commit autoupdate --freezeThis replaces the semver tag with the commit SHA:
rev: 43.150.0 # frozen: sha256:<digest>SKIP=renovate-config-validator git commit -m "wip: draft config".claude-plugin
.github
commands
docs
examples
agent-self-improve
argocd
awesome-docs
aws
cloudfront
functions
lambda-edge
functions
azure
compliance
conventional-commits
datadog
llm-observability
demo
documentation
dora
dynatrace
fluxcd
github-actions
composite-actions
configure-cloud
db-migrate
docker-build-push
k8s-deploy
notify-slack
pr-comment
release-tag
security-scan
setup-env
setup-terraform
terraform-plan
helm
web-service
templates
kubernetes
kyverno
mcp
observability
openshift
pr-review
ownership
runtime-security
supply-chain
terraform
references
scripts
skills
platform-skills
tests