[READ-ONLY] Mirror of https://github.com/FoxxMD/blog. blog.foxxmd.dev
0

Configure Feed

Select the types of activity you want to include in your feed.

feat(renovate): Add footnotes and extras

FoxxMD (Apr 20, 2026, 5:16 PM UTC) 08eb9e7c a5cdecf6

+164 -6
+164 -6
_posts/2026-04-20-scaling-renovate.md
··· 480 480 ``` 481 481 {: file='docker-compose.packageRules in renovate.json'} 482 482 483 - 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. 483 + 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. 484 484 485 485 > 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. 486 486 {: .prompt-tip} ··· 508 508 509 509 EX `"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. 510 510 511 - Add add the top-level: 511 + Add to the top-level: 512 512 513 513 ```json 514 514 "minimumReleaseAge": "4 day" ··· 528 528 > 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. 529 529 {: .prompt-tip} 530 530 531 - 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. 531 + 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. 532 532 533 - 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. 533 + [^no-docker-daemon-proxy]: IE It will not use the Docker Daemon API so we can't use existing docker registry proxies transparently, unfortunately. 534 534 535 535 In 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: 536 536 ··· 549 549 # URL to be used for dockerhub registry mirror 550 550 traefik.http.routers.distribution-docker.rule: Host(`registry-docker.example.com`) 551 551 traefik.http.services.distribution-docker.loadbalancer.server.port: 5000 552 - traefik.docker.network: internal_overlay 552 + traefik.docker.network: traefik_overlay 553 553 environment: 554 554 REGISTRY_PROXY_REMOTEURL: https://registry-1.docker.io # the upstream registry to cache 555 555 REGISTRY_PROXY_USERNAME: foxxmd ··· 581 581 582 582 EX: For `image: docker.io/library/redis:7` it should instead use `registry-docker.example.com/library.redis:7` 583 583 584 - 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. 584 + 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] 585 + 586 + [^registryAliasExplicit]: The `registryAliases` config above only tells it what to do when the prefix is *explicitly* in the image. 585 587 586 588 Add to **the beginning** of `packageRules`: 587 589 ··· 600 602 > * Add an additional compose service for each registry 601 603 > * Add a new mapping to `registryAliases` 602 604 {: .prompt-tip} 605 + 606 + ## Extras 607 + 608 + ### Unlimited PRs 609 + 610 + ![Unlimited Power](https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExdXpqZ2dmeTA2Nm5ueHQ4a3Vpd3F3N2JpODQ2Y2R4bXlmZXFzaWVvciZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/hokMyu1PAKfJK/giphy-downsized.gif){: height="100" } 611 + 612 + 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`: 613 + 614 + ```json 615 + "prHourlyLimit": 0, 616 + "prConcurrentLimit": 0 617 + ``` 618 + {: file='top-level in renovate.json'} 619 + 620 + ### Uncommon Version Pattern Detection 621 + 622 + Add the [`workarounds:bitnamiDockerImageVersioning`](https://docs.renovatebot.com/presets-workarounds/#workaroundsbitnamidockerimageversioning) preset to `extends` to help with versioning for bitnami images. 623 + 624 + [bpbradley](https://github.com/bpbradley) contributed this extremely useful version detection for [linuxserver.io](https://www.linuxserver.io/) images. Add to `packageRules`: 625 + 626 + ```json 627 + { 628 + "description": "Linuxserver tag parsing", 629 + "versioning": "regex:^(?<compatibility>.*?)-(?<major>v?\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)[\\.-]*r?(?<build>\\d+)*-*r?(?<release>\\w+)*", 630 + "matchPackageNames": [ 631 + "/^(ghcr.io\\/linuxserver\\/|lscr.io\\/linuxserver\\/).*/" 632 + ] 633 + } 634 + ``` 635 + {: file='docker-compose.packageRules in renovate.json'} 636 + 637 + ### Full Renovate Config 638 + 639 + This is the *full* `renovate.json` config I am using on my Komodo monorepo. It includes everything discussed in this post. 640 + 641 + **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. 642 + 643 + <details markdown="1"> 644 + 645 + <summary>renovate.json</summary> 646 + 647 + {% raw %} 648 + ```json 649 + { 650 + "$schema": "https://docs.renovatebot.com/renovate-schema.json", 651 + "extends": [ 652 + "config:recommended", 653 + "workarounds:bitnamiDockerImageVersioning", 654 + "workarounds:doNotUpgradeFromAlpineStableToEdge" 655 + ], 656 + "dependencyDashboard": true, 657 + "dependencyDashboardTitle": "Renovate Dashboard", 658 + "assignees": [ 659 + "foxxmd" 660 + ], 661 + "labels": [ 662 + "renovate" 663 + ], 664 + "configMigration": true, 665 + "prHourlyLimit": 0, 666 + "prConcurrentLimit": 0, 667 + "minimumReleaseAge": "4 day", 668 + "docker-compose": { 669 + "major": { 670 + "dependencyDashboardApproval": true 671 + }, 672 + "pinDigests": true, 673 + "vulnerabilityAlerts": { 674 + "addLabels": [ 675 + "security" 676 + ] 677 + }, 678 + "hostRules": [ 679 + { "matchHost": "docker.io", "concurrentRequestLimit": 2 }, 680 + { "matchHost": "ghcr.io", "concurrentRequestLimit": 2 }, 681 + { "matchHost": "gcr.io", "concurrentRequestLimit": 2 }, 682 + { "matchHost": "lscr.io", "concurrentRequestLimit": 2 } 683 + ], 684 + "prBodyNotes": [ 685 + "Updates for stacks in `{{packageFileDir}}`." 686 + ], 687 + "registryAliases": { 688 + "index.docker.io": "registry-docker.example.com", 689 + "docker.io": "registry-docker.example.com" 690 + }, 691 + "packageRules": [ 692 + { 693 + "matchDatasources": ["docker"], 694 + "registryUrls": [ 695 + "https://registry-docker.example.com" 696 + ] 697 + }, 698 + { 699 + "matchPackageNames": [ 700 + "/.*/" 701 + ], 702 + "addLabels": [ 703 + "{{updateType}}" 704 + ], 705 + "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}}", 706 + "enabled": true 707 + }, 708 + { 709 + "description": "Common images that may have breaking changes between any non-patch versions (will only open patch PRs)", 710 + "matchPackageNames": [ 711 + "/influxdb/", 712 + "/mysql/", 713 + "/mongo/", 714 + "/elasticsearch/", 715 + "/keydb/", 716 + "/rabbitmq/", 717 + "/mariadb/", 718 + "/etcd/" 719 + ], 720 + "matchUpdateTypes": [ 721 + "major", 722 + "minor" 723 + ], 724 + "enabled": false 725 + }, 726 + { 727 + "description": "Common images that may have breaking changes between major versions (will only open patch/minor PRs)", 728 + "matchPackageNames": [ 729 + "/couchdb/", 730 + "/redis/", 731 + "/valkey/", 732 + "/postgres/", 733 + "/postgis/", 734 + "/pgadmin/", 735 + "/clickhouse/", 736 + "/grafana/" 737 + ], 738 + "matchUpdateTypes": [ 739 + "major" 740 + ], 741 + "enabled": false 742 + }, 743 + { 744 + "description": "Linuxserver tag parsing", 745 + "versioning": "regex:^(?<compatibility>.*?)-(?<major>v?\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)[\\.-]*r?(?<build>\\d+)*-*r?(?<release>\\w+)*", 746 + "matchPackageNames": [ 747 + "/^(ghcr.io\\/linuxserver\\/|lscr.io\\/linuxserver\\/).*/" 748 + ] 749 + } 750 + ] 751 + } 752 + } 753 + ``` 754 + {% endraw %} 755 + 756 + </details> 757 + 758 + ___ 759 + 760 + ## Footnotes