Reacts to GitHub tag events and runs Renovate on the repositories that consume
the newly-tagged code. When a dependency repo publishes a tag, the service
reads that repo's renovate.trigger.json to find its dependents, batches them
over a short window, and creates a one-off Kubernetes Job cloned from an
existing Renovate CronJob — injecting the dependents via RENOVATE_REPOSITORIES.
A single GitHub App both delivers the webhooks and grants read access to the trigger files.
- Domain vocabulary →
CONTEXT.md - Requirements & config reference →
REQUIREMENTS.md - Diagrams →
WORKFLOWS.md - Design decisions →
docs/adr/
A new tag on a dependency repo flows through renovate-trigger — batched over a
tumbling window, gated so runs never overlap, and resolved via
renovate.trigger.json — into a one-off Renovate Job that opens a PR in the
consuming Argo CD (dependent) repo.
Full step-by-step diagrams are in WORKFLOWS.md; the animation
source lives in docs/animation/.
Add renovate.trigger.json to the default branch of a repository whose tags
should trigger Renovate on its consumers:
{ "tags": ["org/dependent-a", "org/dependent-b"] }A tag on a repo with no such file (or without the App installed) is ignored.
- A Renovate
CronJobalready deployed in the cluster — itsjobTemplateis what this service clones for each run. - Cluster access (
kubectl/helm) to the CronJob's namespace. - A way to expose the service's
/webhookendpoint to GitHub (an Ingress). - An existing Kubernetes Secret with the App credentials — the chart does
not create one; provision it out of band (e.g. with External Secrets
Operator, or by rendering the Secret / an
ExternalSecretthrough the chart'sextraObjects:value). A single Secret with three keys: the App client ID, the App private key (PEM), and the webhook secret.
In Settings → Developer settings → GitHub Apps → New GitHub App (org- or user-owned):
- Permissions → Repository → Contents:
Read-only. This is used both to read each repo'srenovate.trigger.jsonand to unlock theCreateevent below —Createonly becomes selectable once Contents is granted. - Subscribe to events → check
Create. This is GitHub's event for tag (and branch) creation; the service ignores branches and acts only on tags. There is one App-level webhook — you never configure per-repo webhooks. Note the three-tag delivery limit in Limitations. - Webhook: set Active, URL =
https://<your-host>/webhook, and a strong random Secret. - Generate a private key and download the
.pem. - Note the App's Client ID.
Store these three — client ID, private key (PEM), and webhook secret — in the existing credentials Secret (see step 3).
Install the App on the dependency repos whose tags should trigger Renovate.
Only installed repos deliver events and are readable — App-installed + a
renovate.trigger.json present is the opt-in.
Put your settings in a values file — renovate-trigger.values.yaml. Install into
the same namespace as the Renovate CronJob; the service is co-located with
it, so the CronJob namespace is just the release namespace.
config:
cronjob:
name: renovate # source Renovate CronJob (its jobTemplate is cloned)
# namespace: ... # only if it differs from the release namespace
# Existing Secret holding the App credentials (created out of band, e.g. via
# External Secrets Operator) — one Secret, three keys. Override the key names to
# match yours.
existingSecret:
name: renovate-trigger
clientIdKey: github-client-id
privateKeyKey: github-app-private-key
webhookSecretKey: webhook-secret
# Expose /webhook at the host used for the App's webhook URL (step 1).
# Enable ONE of the following, or wire your own routing instead.
ingress:
enabled: true
host: renovate-trigger.example.com
className: nginx
tls:
enabled: true
# Traefik alternative (leave `ingress` disabled if you use this):
# ingressRoute:
# enabled: true
# host: renovate-trigger.example.com
# entryPoint: websecure
# labels:
# nobi.life/traefik-scope: externalThen install — the chart and image are published to GHCR:
helm install renovate-trigger oci://ghcr.io/caseycs/charts/renovate-trigger \
--version 0.3.1 --namespace renovate \
-f renovate-trigger.values.yamlTo install from a local checkout instead, swap the chart reference for ./chart.
The service starts only if the config is valid — a missing key in the Secret or an unreachable CronJob crash-loops the pod at boot (fail-loud by design).
For each dependency repo, add a renovate.trigger.json on its default branch —
see Opting a repository in above.
GitHub does not deliver tag webhooks when a single push creates four or more tags:
This event will not occur when more than three tags are created at once. —
createevent
The same threshold applies to the push event — "Events will not be created for
tags when more than three tags are pushed at once" — so there is no alternative
event to fall back on. Nothing arrives: the delivery does not even appear in the
App's webhook deliveries UI, and the service never learns those tags exist.
In practice this affects a git push --tags that creates several tags at once, a
tag backfill, or monorepo release tooling that tags four or more packages in one
run. Three or fewer tags in one push is fine — GitHub sends one create per
tag and the batch collector dedupes them into a single Renovate run.
There is no server-side workaround; the limit is enforced before delivery. When
it happens the scheduled Renovate CronJob is the backstop — triggers are lossy
by design and never retried (see
NFR-4) — so dependents pick up the new
version on the next scheduled run rather than within the batch window. To keep
the fast path, have dependency repos push tags in batches of three or fewer.
All operational config is via RT_* environment variables (set by the chart);
the dependency graph itself lives in per-repo renovate.trigger.json files. See
the configuration reference.
