{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreiehl7qcf52h7kt44fzxe2zvcb4xbl5ifn56cazrqwah4gvdrb25gq",
"uri": "at://did:plc:wszrgoqdwy3i2dfeub2mt3wf/app.bsky.feed.post/3mgksfqabr4s2"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreibk7ilcb5quri2t525amggpz4athfmo466xujf7qb2bl5tiggd75m"
},
"mimeType": "image/jpeg",
"size": 26144
},
"description": "A runthrough of my process for testing more complex Renovate config changes where I want confidence up-front.",
"path": "/posts/2026/03/08/renovate-test-config/",
"publishedAt": "2026-03-08T16:34:52.000Z",
"site": "https://www.jvt.me",
"tags": [
"blogumentation",
"renovate",
"last time in 2023",
"internal mirror of the docs site",
"new role",
"writing it as a form of blogumentation",
"for an agent to then use",
"in `dryRun` mode",
"the repo config",
"via",
"the global self-hosted config",
"`enabledManagers`",
"`includePaths`",
"Local Platform",
"the benefits before",
"we still have some gaps",
"`dryRun`",
"we didn't yet have the ability to log the commit's changes",
"global self-hosted",
"a tool that I wrote for Renovate's debug logs in particular",
"`renovate-packagedata-diff`",
"\"reconfigure via PR\" flow",
"Custom Managers",
"Custom Datasources",
"more structured feedback for specific things in Renovate",
"@typedef",
"@type"
],
"textContent": "Over the years, I've been a heavy user of Renovate, and I think it's fair to argue I can describe myself as a \"power user\".\n\nAs someone who enjoys the faster feedback of tests, I've spent a good chunk of time honing the process of validating more complex configuration changes before they're merged - especially on repositories where CI builds can take some time, or (understandably) not everyone in the team (including me!) are happy re-reviewing \"maybe this will work?\" PRs.\n\nI've written about how I do this, the last time in 2023, but I've learned a number of things since then.\n\nWhile I was at Elastic, part of my role was supporting folks with their Renovate questions. As well as pointing folks to our internal mirror of the docs site, and some pre-baked \"How do I...?\" on our internal Engineering Productivity docs, I also wrote a more in-depth version of my post above. Given it was for internal use only, I was able to tailor it more closely to our self-hosted usage, and make it more appropriate to the needs of my colleagues.\n\nAs part of my new role as a maintainer on Renovate and the Renovate community manager, I end up going through this workflow _a lot_ while working to reproduce issues for Mend's customers or community members.\n\nIn my ever present spirit of writing it as a form of blogumentation, I wanted to capture the latest setup I have.\n\nThis isn't only useful for myself, and since November I've been promising a few users of ours that \"I'll be done writing it soon!\", but it always takes a little longer to do a post like this - especially when we have lots of other things to do on the project!\n\nDocumenting this process is also for me to be able to distill my process for an agent to then use, so it approaches problems in the same way that I'd want to - or so it could find that actually there's a better way than I've been using!\n\nI'll note that this is how it personally works for me, and this isn't _official_ guidance of the Renovate project - if it were, it'd be in our docs 🤓\n\n## TL;DR\n\nI use the following `config.js` as my starting point:\n\n\n /**\n * @typedef {import('renovate/dist/config/types').AllConfig} AllConfig\n */\n\n const fs = require('fs')\n\n // NOTE that filename would need to be changed based on the repo's config filename, or adapted to handle JSONC or JSON5\n let repoConfig = JSON.parse(fs.readFileSync('renovate.json'))\n\n /** @type {AllConfig} */\n let globalConfig = {\n // for instance, to simulate how the Mend Developer Platform is configured\n allowedUnsafeExecutions: [\n 'gradleWrapper',\n ],\n }\n\n /** @type {AllConfig} */\n let config = {\n ...globalConfig,\n\n // don't require repositories to have a `renovate.json` - i.e. if we're testing how a configuration will affect a new repo\n onboarding: false,\n // if there is config, ignore it, and use our local copy\n requireConfig: 'ignored',\n\n // add our repo config\n ...repoConfig,\n\n /*\n * These settings are only for repositories where you're running Renovate without dry-run, /and/ you're expecting to get many branches/PRs created\n *\n * NOTE that this ordering allows these to take precedence over repo config\n */\n prHourlyLimit: 100,\n // allow lots of branches/PRs to be created\n branchConcurrentLimit: 100,\n prConcurrentLimit: 100,\n // and separate to these settings, we also want to allow all PRs to be created at a given time\n }\n\n // NOTE that this isn't inlined, because it can be handy to do conditional checks\n module.exports = config\n\n\nThis provides a strong basis for testing, and allows me to keep my repo configuration as it should be.\n\nI'll then invoke Renovate like so:\n\n\n # whitespace + comments added for readability\n env\n # ensure that we have GitHub authentication at least, so we can fetch changelogs, and access GitHub-only dependencies\n RENOVATE_GITHUB_COM_TOKEN=$(gh auth token)\n # make sure we have the right level of information\n LOG_LEVEL=debug\n # also capture the logs in JSONL (newline-delimited JSON) format\n RENOVATE_LOG_FILE=debug-$(date +%s).jsonl\n # use our Open Telemetry support (https://docs.renovatebot.com/opentelemetry/) to get additional insight into time taken / the flow of function calls\n OTEL_EXPORTED_OTLP_ENDPOINT=http://localhost:4318\n # if I'm running from source code\n ## node lib/renovate.ts\n # or, more likely, running for a given version:\n pnpx renovate@$version\n # a lot of the time, I'll use the local platform for ease, but often will run against a real Platform\n --platform local\n\n\nDepending on whether I'm running against a real platform or not, I'll wire in the `--platform` argument and any other necessary authentication.\n\n## Why does this work for me?\n\nThe below setup is useful because:\n\n * I can run the Renovate CLI locally against a repo\n * I can mostly run in `dryRun` mode\n * I can either use a local directory (with the `platform=local`) for speed\n * ... or I can use a GitHub/GitLab repo, for full confidence that i.e. the right PRs get raised\n * I can continue to tweak the repo config in the `renovate.json` (or any of the other filenames)\n * We get some editor completion from the JSDoc type hints, via, but it's not as good as the JSON Schema's autocomplete\n * I can tune the global self-hosted config to allow me to test what I need, as well as mould to the organisation I'm running in\n * For instance at Elastic, I'd wire in some secrets for authenticating to private registries, authenticate via my `~/.docker/config.json` and inherit from our \"base\" configuration (that every repo was required to use), as well as override a couple of global settings\n * Or with the Mend-hosted Renovate app, I can use the same configuration we're using there in my local copy\n\n\n\nMost importantly - it gives me fast feedback and fairly high confidence that my changes will work (even more so if use a real repo, instead of `dryRun`).\n\n## Surely there's an easier way?\n\nRenovate works to fit within _your_ workflow and how your dependencies are set up, rather than vice versa.\n\nFor most changes, you can \"YOLO\" the change and see what happens, and/or iterate over it - but as mentioned above, where it makes sense, I prefer to have maximal confidence in these changes.\n\nThis also gives you an opportunity to learn more about Renovate and how it works - but I appreciate it's not for everyone.\n\nI will also say that at least Renovate does provide you the opportunity to test things out, compared to some of our competitors, where you do very much have to \"see what happens\" after you change some configuration.\n\n## Getting started\n\nThere are a few steps to get set up and running for running Renovate against a repo.\n\n### Are we using an existing repo?\n\nMost of the time, I'll test against the actual repository that we're working with (and will create a feature branch locally to commit any `renovate.json` changes to).\n\nIf there are lots of dependencies or files I want to ignore, I'll use `enabledManagers` or `includePaths` to hone what Renovate will process.\n\nBut there are cases where you're working with a large monorepo, and it can be really beneficial to performance to reduce the overall size of the changes Renovate is processing, even when excluding them using `enabledManagers` and `includePaths` while using `platform=local`.\n\nIn these cases, I'll:\n\n * create a new directory i.e. `tmp/renovate-testing`\n * in that directory, `git init`\n * add the `renovate.json` (or as a symlink)\n * symlink file(s) that I need to test from the monorepo, in the same directory structure as they exist\n * `git add .`\n\n\n\nBy keeping the existing directory structure, we can continue to use the existing `renovate.json`'s paths for Custom Managers, allowing us the maximum confidence in being able to use this with the real repo.\n\n### Using the Local Platform\n\nRenovate's Local Platform is a great way to get faster feedback on changes, without needing a full repository pushed to a Platform (like GitHub or GitLab).\n\nI'll prefer to use `platform=local` where possible when testing changes.\n\nI've written about the benefits before and still recommend it, even if we still have some gaps.\n\n### Confirming Renovate version\n\nOne of the things I love about Renovate is how we're constantly shipping new releases 🚀\n\nWhen trying to test/reproduce an issue, it's worth making sure that you're locally running the version of Renovate that is being used by your deployment, rather than the latest version of Renovate at a given time.\n\n(That is, unless you've been asked by one of the folks helping debug your issue in a Discussions)\n\nYou can see the version of Renovate being used as part of Renovate's logs:\n\n\n INFO: Renovate started\n {\n \"renovateVersion\": \"43.56.0\"\n }\n ...\n INFO: Repository started\n {\n \"renovateVersion\": \"43.56.0\"\n }\n\n\nOnce we've determined this, we can use `pnpx renovate@43.56.0`.\n\n(`pnpx` because `pnpm` is what we use for Renovate, but `npx` works too)\n\n### How dry a dry run are we running?\n\nRenovate's `dryRun` functionality is useful to be able to scope the work that Renovate will do, especially while testing.\n\nDepending on work I'm doing, I'll use different dry run settings.\n\n#### `dryRun=extract`\n\nI'll use `dryRun=extract` if I'm only testing Custom Managers, so I can validate that the right dependencies are detected.\n\n#### `dryRun=lookup`\n\nIn the case that I'm testing with Datasources, or need to see what will happen with how branches are going to be created (i.e. if changing how grouping works), then I'll need to use `dryRun=lookup`, or if I'm testing how Versioning works.\n\n#### `dryRun=full`\n\nI'll very infrequently use this mode, largely because we didn't yet have the ability to log the commit's changes, so there's no straightforward way to determine exactly what changes Renovate will make without running it.\n\nIf I'm needing to determine what changes will be made against a repo, I'll end up running Renovate against the repo in non-dry-run mode (the default) which will start processing the repo.\n\nThen, I'll review the branches/PRs created, and make sure they're generated correctly.\n\n### Setting up my `config.js`\n\nI'll then copy in my `config.js` (for a mix of global self-hosted and repo config):\n\n\n /**\n * @typedef {import('renovate/dist/config/types').AllConfig} AllConfig\n */\n\n const fs = require('fs')\n\n // NOTE that filename would need to be changed based on the repo's config filename, or adapted to handle JSONC or JSON5\n let repoConfig = JSON.parse(fs.readFileSync('renovate.json'))\n\n /** @type {AllConfig} */\n let globalConfig = {\n // for instance, to simulate how the Mend Developer Platform is configured\n allowedUnsafeExecutions: [\n 'gradleWrapper',\n ],\n }\n\n /** @type {AllConfig} */\n let config = {\n ...globalConfig,\n\n // don't require repositories to have a `renovate.json` - i.e. if we're testing how a configuration will affect a new repo\n onboarding: false,\n // if there is config, ignore it, and use our local copy\n requireConfig: 'ignored',\n\n // add our repo config\n ...repoConfig,\n\n /*\n * These settings are only for repositories where you're running Renovate without dry-run, /and/ you're expecting to get many branches/PRs created\n *\n * NOTE that this ordering allows these to take precedence over repo config\n */\n prHourlyLimit: 100,\n // allow lots of branches/PRs to be created\n branchConcurrentLimit: 100,\n prConcurrentLimit: 100,\n // and separate to these settings, we also want to allow all PRs to be created at a given time\n }\n\n // NOTE that this isn't inlined, because it can be handy to do conditional checks\n module.exports = config\n\n\n## Running Renovate + analysing the debug logs\n\nOnce this is all set up, it's time to run Renovate against the repo:\n\n\n # whitespace + comments added for readability\n env\n # ensure that we have GitHub authentication at least, so we can fetch changelogs, and access GitHub-only dependencies\n RENOVATE_GITHUB_COM_TOKEN=$(gh auth token)\n # make sure we have the right level of information\n LOG_LEVEL=debug\n # also capture the logs in JSONL (newline-delimited JSON) format\n RENOVATE_LOG_FILE=debug-$(date +%s).jsonl\n # use our Open Telemetry support (https://docs.renovatebot.com/opentelemetry/) to get additional insight into time taken / the flow of function calls\n OTEL_EXPORTED_OTLP_ENDPOINT=http://localhost:4318\n # if I'm running from source code\n ## node lib/renovate.ts\n # or, more likely, running for a given version:\n pnpx renovate@$version\n # a lot of the time, I'll use the local platform for ease, but often will run against a real Platform\n --platform local\n\n\nFor most use-cases, I'll read the debug logs as they appear on the console or in the Mend Developer Portal (if I'm testing with a real repo, but on our Cloud offering), searching for specific log lines I know are useful (more on that in a later post).\n\nI'll also load them into a tool that I wrote for Renovate's debug logs in particular, which allows me to read them in a similar means to what you see on the Mend Developer Platform, but for any Renovate logs.\n\nIn future versions of `renovate-pretty-log-tui`, I'll also surface \"interesting log lines\" and provide a little more information that will be useful to get at-glance view for interesting metadata.\n\nIf I'm doing more significant changes around how packages are detected or what version(s) will be available as part of changes, I'll use `renovate-packagedata-diff` to diff the `packageFiles with updates` log line.\n\n## Alternatives\n\n * Merge config changes and see what happens (more iterative, depends on code review + CI time\n * Using Renovate's \"reconfigure via PR\" flow\n * This gives you a summary of the changes via the PR - not quite the same level of detail as we're looking at with my approach above, but it's a great start, and is slightly more user-friendly\n * Writing unit tests for Custom Managers and Custom Datasources\n\n\n\n## Feedback wanted\n\nHow does this sound? Is there anything you'd like to hear me go into, other than \"interesting log lines\"? Any improvements you'd suggest based on your own experiences\n\nWould a \"worked example\" be useful to see the full process? (Perhaps even in a video form?)\n\nNote that we're also looking for more structured feedback for specific things in Renovate that would be appreciated if you have anything to share!",
"title": "My workflow for testing Renovate config changes (2026 edition)",
"updatedAt": "2026-03-08T16:38:29.000Z"
}