···480480```
481481{: file='docker-compose.packageRules in renovate.json'}
482482483483-This will ensure that PRs are only opened for these images if the version update is both backwards compatible and unlikely to break the main service's usage of the dependency. I assigned these by going to each depedency's website and verifying their version update policy, or defaulting to patch-only if no policy was found.
483483+This will ensure that PRs are only opened for these images if the version update is both backwards compatible and unlikely to break the main service's usage of the dependency. I assigned these by going to each dependency's website and verifying their version update policy, or defaulting to patch-only if no policy was found.
484484485485> If you have specific versions of any of these you want to override per project then add another entry to `packageRules` **after** the above entries and use either [`matchPackageNames`](https://docs.renovatebot.com/configuration-options/#packagerulesmatchpackagenames) or [`matchFileNames`](https://docs.renovatebot.com/configuration-options/#packagerulesmatchfilenames) to match your specific scenario.
486486{: .prompt-tip}
···508508509509EX `"minimumReleaseAge": "4 day"` means if the digest for `redis:9` was created less than 4 days ago then PR will be opened and instead it will be shown on the Dashboard as a Pending Update.
510510511511-Add add the top-level:
511511+Add to the top-level:
512512513513```json
514514"minimumReleaseAge": "4 day"
···528528> If you don't want to go to the trouble of caching during initial renovate config iteration/setup then I would suggest creating a *testing* repository to have Renovate run on. Include only a few stacks with all the image update scenarios you want to detect and use that to iterate on config building, rather than using your entire homelab monorepo as the testing grounds.
529529{: .prompt-tip}
530530531531-When Renovate is fetching updates it is making plain HTTP/S calls to the upstream registries *and not* using the Docker Daemon API. Therefore, we can't use existing docker registry proxies transparently.
531531+When Renovate is fetching updates it is making plain HTTP/S calls to the upstream registries[^no-docker-daemon-proxy] so we will use [CNCF's `distribution`](https://distribution.github.io/distribution/) image as a [pull through cache](https://distribution.github.io/distribution/recipes/mirror) in order to cache image manifest information from [each upstream registry](https://distribution.github.io/distribution/recipes/mirror/#gotcha) we want to cache for.
532532533533-Instead, we will use [CNCF's `distribution`](https://distribution.github.io/distribution/) image as a [pull through cache](https://distribution.github.io/distribution/recipes/mirror) in order to cache image manifest information from [each upstream registry](https://distribution.github.io/distribution/recipes/mirror/#gotcha) we want to cache for.
533533+[^no-docker-daemon-proxy]: IE It will not use the Docker Daemon API so we can't use existing docker registry proxies transparently, unfortunately.
534534535535In this example I am using `distribution` as a cache for Dockerhub and setting up the container behind Traefik as the reverse proxy. In a docker compose stack:
536536···549549 # URL to be used for dockerhub registry mirror
550550 traefik.http.routers.distribution-docker.rule: Host(`registry-docker.example.com`)
551551 traefik.http.services.distribution-docker.loadbalancer.server.port: 5000
552552- traefik.docker.network: internal_overlay
552552+ traefik.docker.network: traefik_overlay
553553 environment:
554554 REGISTRY_PROXY_REMOTEURL: https://registry-1.docker.io # the upstream registry to cache
555555 REGISTRY_PROXY_USERNAME: foxxmd
···581581582582EX: For `image: docker.io/library/redis:7` it should instead use `registry-docker.example.com/library.redis:7`
583583584584-Finally, we give an explicit hint to Renovate what the default registry is by configuring [`registryUrls`](https://docs.renovatebot.com/configuration-options/#registryurls). Without this config Renovate will still try to use `docker.io/...` when no registry prefix is present. The `registryAliases` config above only tells it what to do when the prefix is *explicitly* in the image.
584584+Finally, we give an explicit hint to Renovate what the default registry is by configuring [`registryUrls`](https://docs.renovatebot.com/configuration-options/#registryurls). Without this config Renovate will still try to use `docker.io/...` when no registry prefix is present.[^registryAliasExplicit]
585585+586586+[^registryAliasExplicit]: The `registryAliases` config above only tells it what to do when the prefix is *explicitly* in the image.
585587586588Add to **the beginning** of `packageRules`:
587589···600602> * Add an additional compose service for each registry
601603> * Add a new mapping to `registryAliases`
602604{: .prompt-tip}
605605+606606+## Extras
607607+608608+### Unlimited PRs
609609+610610+{: height="100" }
611611+612612+To get Renovate to open all valid PRs (PRs not filtered by things like [major updates](#major-dependency-approval) or [minimum age](#minimum-release-age)) you need to add both `prHourlyLimit` *and* `prConcurrentLimit` to `renovate.json`:
613613+614614+```json
615615+"prHourlyLimit": 0,
616616+"prConcurrentLimit": 0
617617+```
618618+{: file='top-level in renovate.json'}
619619+620620+### Uncommon Version Pattern Detection
621621+622622+Add the [`workarounds:bitnamiDockerImageVersioning`](https://docs.renovatebot.com/presets-workarounds/#workaroundsbitnamidockerimageversioning) preset to `extends` to help with versioning for bitnami images.
623623+624624+[bpbradley](https://github.com/bpbradley) contributed this extremely useful version detection for [linuxserver.io](https://www.linuxserver.io/) images. Add to `packageRules`:
625625+626626+```json
627627+{
628628+ "description": "Linuxserver tag parsing",
629629+ "versioning": "regex:^(?<compatibility>.*?)-(?<major>v?\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)[\\.-]*r?(?<build>\\d+)*-*r?(?<release>\\w+)*",
630630+ "matchPackageNames": [
631631+ "/^(ghcr.io\\/linuxserver\\/|lscr.io\\/linuxserver\\/).*/"
632632+ ]
633633+}
634634+```
635635+{: file='docker-compose.packageRules in renovate.json'}
636636+637637+### Full Renovate Config
638638+639639+This is the *full* `renovate.json` config I am using on my Komodo monorepo. It includes everything discussed in this post.
640640+641641+**You will need to modify it for your repository before use** (things like `assignee` and updating/removing the [cache rules](#optional-reducing-registry-api-calls-with-caching)) but it can be used as a reference point.
642642+643643+<details markdown="1">
644644+645645+<summary>renovate.json</summary>
646646+647647+{% raw %}
648648+```json
649649+{
650650+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
651651+ "extends": [
652652+ "config:recommended",
653653+ "workarounds:bitnamiDockerImageVersioning",
654654+ "workarounds:doNotUpgradeFromAlpineStableToEdge"
655655+ ],
656656+ "dependencyDashboard": true,
657657+ "dependencyDashboardTitle": "Renovate Dashboard",
658658+ "assignees": [
659659+ "foxxmd"
660660+ ],
661661+ "labels": [
662662+ "renovate"
663663+ ],
664664+ "configMigration": true,
665665+ "prHourlyLimit": 0,
666666+ "prConcurrentLimit": 0,
667667+ "minimumReleaseAge": "4 day",
668668+ "docker-compose": {
669669+ "major": {
670670+ "dependencyDashboardApproval": true
671671+ },
672672+ "pinDigests": true,
673673+ "vulnerabilityAlerts": {
674674+ "addLabels": [
675675+ "security"
676676+ ]
677677+ },
678678+ "hostRules": [
679679+ { "matchHost": "docker.io", "concurrentRequestLimit": 2 },
680680+ { "matchHost": "ghcr.io", "concurrentRequestLimit": 2 },
681681+ { "matchHost": "gcr.io", "concurrentRequestLimit": 2 },
682682+ { "matchHost": "lscr.io", "concurrentRequestLimit": 2 }
683683+ ],
684684+ "prBodyNotes": [
685685+ "Updates for stacks in `{{packageFileDir}}`."
686686+ ],
687687+ "registryAliases": {
688688+ "index.docker.io": "registry-docker.example.com",
689689+ "docker.io": "registry-docker.example.com"
690690+ },
691691+ "packageRules": [
692692+ {
693693+ "matchDatasources": ["docker"],
694694+ "registryUrls": [
695695+ "https://registry-docker.example.com"
696696+ ]
697697+ },
698698+ {
699699+ "matchPackageNames": [
700700+ "/.*/"
701701+ ],
702702+ "addLabels": [
703703+ "{{updateType}}"
704704+ ],
705705+ "commitMessageExtra": "in stack {{packageFileDir}} from {{currentVersion}} to {{#if isPinDigest}}{{{newDigestShort}}}{{else}}{{#if isMajor}}{{prettyNewMajor}}{{else}}{{#if isSingleVersion}}{{prettyNewVersion}}{{else}}{{#if newValue}}{{{newValue}}}{{else}}{{{newDigestShort}}}{{/if}}{{/if}}{{/if}}{{/if}}",
706706+ "enabled": true
707707+ },
708708+ {
709709+ "description": "Common images that may have breaking changes between any non-patch versions (will only open patch PRs)",
710710+ "matchPackageNames": [
711711+ "/influxdb/",
712712+ "/mysql/",
713713+ "/mongo/",
714714+ "/elasticsearch/",
715715+ "/keydb/",
716716+ "/rabbitmq/",
717717+ "/mariadb/",
718718+ "/etcd/"
719719+ ],
720720+ "matchUpdateTypes": [
721721+ "major",
722722+ "minor"
723723+ ],
724724+ "enabled": false
725725+ },
726726+ {
727727+ "description": "Common images that may have breaking changes between major versions (will only open patch/minor PRs)",
728728+ "matchPackageNames": [
729729+ "/couchdb/",
730730+ "/redis/",
731731+ "/valkey/",
732732+ "/postgres/",
733733+ "/postgis/",
734734+ "/pgadmin/",
735735+ "/clickhouse/",
736736+ "/grafana/"
737737+ ],
738738+ "matchUpdateTypes": [
739739+ "major"
740740+ ],
741741+ "enabled": false
742742+ },
743743+ {
744744+ "description": "Linuxserver tag parsing",
745745+ "versioning": "regex:^(?<compatibility>.*?)-(?<major>v?\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)[\\.-]*r?(?<build>\\d+)*-*r?(?<release>\\w+)*",
746746+ "matchPackageNames": [
747747+ "/^(ghcr.io\\/linuxserver\\/|lscr.io\\/linuxserver\\/).*/"
748748+ ]
749749+ }
750750+ ]
751751+ }
752752+}
753753+```
754754+{% endraw %}
755755+756756+</details>
757757+758758+___
759759+760760+## Footnotes