Files
renovate/docs/usage/key-concepts/minimum-release-age.md
Morre 41820f7301 docs: point to logLevelRemap for minimumReleaseAgeBehaviour=timestamp-optional warnings (#45498)
* docs: point to logLevelRemap for minimumReleaseAgeBehaviour=timestamp-optional warnings

* Update docs/usage/key-concepts/minimum-release-age.md

Co-authored-by: Jamie Tanna <github@jamietanna.co.uk>

* Update docs/usage/key-concepts/minimum-release-age.md

Co-authored-by: Jamie Tanna <github@jamietanna.co.uk>

* Update docs/usage/key-concepts/minimum-release-age.md

Co-authored-by: Jamie Tanna <github@jamietanna.co.uk>

* improve wording

---------

Co-authored-by: Jamie Tanna <github@jamietanna.co.uk>
2026-08-27 10:01:49 +00:00

34 KiB

title, description
title description
Minimum Release Age Requires Renovate to wait for a specified amount of time before suggesting a dependency update.

Minimum Release Age

What is Minimum Release Age?

minimumReleaseAge is a feature that requires Renovate to wait for a specified amount of time before suggesting a dependency update.

The use of minimumReleaseAge is not to slow down fast releasing project updates, but to provide a means to reduce risk supply chain security risks.

In other ecosystems and package managers, this may be referred to as a "dependency cooldown".

For example, minimumReleaseAge=14 days would ensure that a package update is not suggested by Renovate until 14 days after its release, which allows plenty of time to allow security researchers and automated security tools to catch malicious intent in packages.

Note: Renovate will wait for the set duration to pass for each separate version. Renovate does not wait until the package has seen no releases for x time-duration(minimumReleaseAge).

When the time passed since the release is less than the set minimumReleaseAge: Renovate adds a "pending" status check to that update's branch. After enough days have passed: Renovate replaces the "pending" status with a "passing" status check.

Configuration options

The following configuration options can be used to enable and tune the functionality of Minimum Release Age:

minimumReleaseAgeBehaviour=timestamp-optional warning

When Renovate runs with minimumReleaseAgeBehaviour=timestamp-optional, Renovate will log a warning, which is then also shown on the Dependency Dashboard.

This warning serves to inform users about at least one dependency not being able to adhere to the minimum release age due to the configuration.

In cases where this is not desired, you can remap the warning to a lower log level with logLevelRemap:

{
  "logLevelRemap": [
    {
      "matchMessage": "/Some .+ did not have a releaseTimestamp, but as we're running with minimumReleaseAgeBehaviour=timestamp-optional, proceeding/",
      "newLogLevel": "info"
    }
  ]
}

FAQs

Where does the release timestamp need to be set?

To prevent supply-chain attacks, Renovate requires that the registry/datasource provides the timestamp.

This ensures that a package maintainer cannot specify (maliciously or accidentally) a different timestamp to when the package was actually published.

!!! warning For instance, a suggestion for the Docker ecosystem is to use the org.opencontainers.image.created image annotation.
Unfortunately, the publisher controls this annotation, so could allow a maintainer to say that their image was published earlier than it had been to bypass Minimum Release Age restrictions.

As long as the registry/datasource provides the release timestamp, Renovate will add support for it.

My package manager has support for minimum release age, how does Renovate work with that? Do I need to set it in both?

We recommend specifying minimum release age in both your Renovate and package manager configuration.

Renovate's concept of minimumReleaseAge is set independently to your package manager's configuration, and is used by Renovate to decide whether to suggest dependency updates. Renovate does not currently have the ability to determine the minimumReleaseAge from your package manager's configuration.

When Renovate performs a dependency update, it may delegate to your package manager to update artifacts, such as the lockfile. In some cases, this can lead to a transitive dependency being introduced, which Renovate is not aware of, and so could lead to a dependency being introduced without Renovate's minimumReleaseAge check being followed.

To protect against this, it's recommended to ensure that your package manager configuration includes the relevant minimum release age checks, too.

There is ongoing work to integrate more closely with package manager checks to make sure that Renovate's minimum release age configuration is specified when calling package managers that support it. If you have a package manager you'd like supported, please raise a Suggest an Idea Discussion.

npm

When minimumReleaseAge is configured, Renovate passes --before=<date> to npm commands during lock file generation. This ensures that npm only resolves package versions that were available before the cooldown threshold, protecting against newly published (and potentially malicious) transitive dependencies.

The --before date is calculated as now - minimumReleaseAge. If a before=<date> or min-release-age=<days> setting already exists in the project's .npmrc, Renovate uses the stricter (older) of the two dates.

If the existing lock file contains packages published after the --before cutoff (for example, from dependencies merged before minimumReleaseAge was configured), npm will fail with an ETARGET error. In this case, Renovate automatically retries without --before and logs a warning. This ensures existing lock files are never broken by the --before flag.

After the next lock file maintenance run (which regenerates the lock file from scratch with --before), subsequent updates will fully enforce the minimumReleaseAge constraint.

Poetry

When minimumReleaseAge is configured, Renovate sets the POETRY_SOLVER_MIN_RELEASE_AGE environment variable when running poetry update --lock. This ensures that Poetry only resolves package versions that are at least as old as the cooldown threshold.

The value is calculated as ceil(minimumReleaseAge / 1 day) and passed as an integer number of days.

What happens if the datasource and/or registry does not provide a release timestamp, when using minimumReleaseAge?

!!! warning Renovate 42 changed the behaviour detailed below. In Renovate 42, the absence of a release timestamp will be treated as if the release is not yet past the timestamp, which provides a safer default. Prior to Renovate 42, we would treat the dependency without a release timestamp as if it has passed the minimumReleaseAge, and will immediately suggest that dependency update. If using Renovate prior you can opt into the more secure behaviour (which is default in Renovate 42) by using minimumReleaseAgeBehaviour=timestamp-required (added in 41.150.0)

Consider that:

  • we have set minimumReleaseAge to apply to a given dependency
  • that dependency has 4 updates available
    • 1 of which has a release timestamp that has passed
    • 2 of which have a release timestamp that has not yet passed
    • 1 of which does not have a release timestamp

Renovate will create a PR for the 1 dependency update that has passed the release timestamp, and the others will be marked as "Pending Status Checks" on the Dependency Dashboard. As time goes on, if the 2 updates with a release timestamp are now passed the minimum release age, Renovate will add them to the PR (or create a new one).

What happens when an update is not yet passing the minimum release age checks?

Renovate will decide whether it will create a branch for a dependency update using internalChecksFilter.

internalChecksFilter=strict

If you have not configured internalChecksFilter, Renovate will use internalChecksFilter=strict as the default.

This will make sure that branches are not created if the minimumReleaseAge status check, renovate/stability-days, does not pass.

Debug logs example when internalChecksFilter=strict
DEBUG: Branch renovate/actions-checkout-5.x creation is disabled because internalChecksFilter was not met (repository=..., branch=renovate/actions-checkout-5.x)

In this case, no branch is created.

If you have a Dependency Dashboard enabled, it will be found in the Dependency Dashboard in the "Pending Status Checks".

You can force the dependency update by requesting it via the Dependency Dashboard, or if you are self-hosting, you can use the checkedBranches to force the branch creation.

!!! note A previous version of the documentation (up until Renovate 42.19.9) recommended configuring prCreation. This is no longer the case.

If no branch is created, Renovate will not raise a PR, regardless of prCreation's settings.

The recommendation is to set internalChecksFilter=strict when using minimumReleaseAge, so Renovate will create neither branches (nor PRs) on updates that haven't yet met minimum release age checks.

Which update types take minimumReleaseAge into account?

Depending on your manager, datasource and the given package(s), it may be that some updates provide a release timestamp that can have minimumReleaseAge enforced.

Update Type Supports minimumReleaseAge? Notes
major ✅ Depends on the Manager, Datasource, and package(s)
minor ✅ Depends on the Manager, Datasource, and package(s)
patch ✅ Depends on the Manager, Datasource, and package(s)
pin ❌ Not yet supported
digest 🟡 Depends on the Manager, Datasource, and package(s). See below for more info.
pinDigest 🟡 Depends on the Manager, Datasource, and package(s). See below for more info.
lockFileMaintenance ❌ Not possible, as we delegate to the package manager to perform the required changes to update package(s).
lockfileUpdate ❌
rollback ❌
bump ❌
replacement ❌ Not yet supported

You can validate which update types may have release timestamps by following something similar to how verify if the registry you're using.

digest updates

A digest update repoints an existing digest - usually a sha256:... Docker image digest, or the commit a floating tag like v7 points to - at whatever the current version resolves to now.

For instance:

   steps:
-    - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+    - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

In this case, the version v7 is not updated, but the digest is. When Renovate resolves this update, it looks for the newest version matching the current value (which would be the v7.0.1 version in this case), and then ages the update against the release timestamp for this new version.

!!! warning Most registries do not expose when a digest itself was published, so to determine the Minimum Release Age cutoff, we age against the release timestamp of the matched version. The strength of the guarantee depends on what that timestamp means for your datasource.

What this means in practice:

  • On Docker Hub, the release timestamp is the tag's tag_last_pushed field, so re-pushing an existing tag restarts the delay and the digest update is held.
  • Registries which do not return release timestamps (such as GHCR, Quay or ECR) cannot be aged, and the same applies to unversioned tags like latest, where no matching version exists to age against. With the default minimumReleaseAgeBehaviour=timestamp-required, these digest updates are held indefinitely - configure minimumReleaseAgeBehaviour=timestamp-optional to raise them without an age check instead.
  • Datasources whose timestamps reflect when the matched version was first released (such as github-tags for digest-pinned GitHub Actions) cannot detect re-published content. If an existing tag is force-pushed to new commits, the digest update ages against the original release date, so it may pass Minimum Release Age immediately.
    • Note that this is not true when using github-releases, which has metadata to indicate when the GitHub Release was created and published, whereas github-tags uses the committedDate, which is separate from when it was pushed. GitHub no longer provides that metadata in their API.

pinDigest updates

A pinDigest update (enabled via pinDigests) pins an existing versioned tag to its currently resolved digest, without changing the tag itself.

For instance:

-    - uses: actions/checkout@v7
+    - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7

Unlike a digest update, pinDigest doesn't repoint the reference to a different value - it freezes whatever the current value already resolves to. The reference itself (e.g. the v7 major tag above) is floating though, so what it resolves to right now may not be what currentVersion would resolve to under your configured rangeStrategy (for example, rangeStrategy=bump resolves currentVersion to the oldest matching release). So, like digest, a pinDigest update is aged against the release timestamp of the newest version matching the current value, not whatever currentVersion resolves to.

Unlike digest updates, an unversioned current value (such as latest) is exempt from minimumReleaseAge entirely, rather than being held indefinitely: pinning a reference which already floats to the latest content is strictly safer than leaving it unpinned, so holding it back would only prolong the less-safe, unpinned state.

If the current value does resolve to a version, but that version's datasource doesn't expose a release timestamp (for example GHCR, Quay or ECR), the same caveats as digest updates apply: the update is held indefinitely under the default minimumReleaseAgeBehaviour=timestamp-required, and needs minimumReleaseAgeBehaviour=timestamp-optional configured to raise it without an age check.

Avoiding pinDigest updates not being raised due to frequently-releasing packages

Because a pinDigest update ages against the newest matching version, a reference that releases faster than your configured minimumReleaseAge may never clear the check - leaving it perpetually unpinned, which is less safe than pinning to a slightly-stale release.

In particular, because Renovate raises a pinDigest PR across all applicable dependencies (by grouping them together), this can lead to the whole pinDigest update group being delayed.

Renovate does not fall back to pinning an older, already-aged release instead: doing so would mean the pin no longer reflects what the reference currently, genuinely points to, which is the property pinDigest exists to preserve.

If you want Renovate to raise pinDigest updates sooner than your regular version/digest updates, scope a shorter (or disabled) minimumReleaseAge to it specifically, using packageRules and matchUpdateTypes:

{
  "packageRules": [
    {
      "matchUpdateTypes": ["pinDigest"],
      "minimumReleaseAge": null
    }
  ]
}

This decouples "how fresh must a release be before I trust it enough to upgrade to it" (your regular minimumReleaseAge) from "how long to wait before freezing whatever's already there" (the pinDigest-scoped override) - the same idea as using digest.minimumReleaseAge:

{
  "digest": {
    "minimumReleaseAge": null
  }
}

These are functionally identical options.

What happens to security updates?

Security updates bypass any minimumReleaseAge checks, and so will be raised as soon as Renovate detects them.

What happens if a package has multiple updates available?

!!! note This is based on the recommended settings above

Renovate waits for the set duration to pass for each separate version.

If Renovate sees that a package has multiple updates available, it will only raise update(s) that are passing the minimumReleaseAge check.

Let us consider a repository with minimumReleaseAge=1 hour, and with the following timeline:

  • 0000: Renovate runs, and sees no updates
  • 0010: Package releases 1.1.0
  • 0030: Renovate runs, and sees 1.1.0 and marks it as pending
  • 0100: Renovate runs, still sees 1.1.0 as pending
  • 0110: Package releases 1.1.1
  • 0130: Renovate runs, and sees 1.1.0 and 1.1.1 releases. As 1.1.0 is now past the minimumReleaseAge, Renovate raises a PR, and marks 1.1.1 as pending
  • 0200: Renovate runs, still sees 1.1.1 as pending
  • 0230: No humans have merged the PR for 1.1.0, so when Renovate runs, it sees 1.1.1 is now past the minimumReleaseAge, so updates the existing PR to bump the version to 1.1.1

What happens to transitive dependencies?

Renovate does not currently manage any transitive dependencies - instead leaving that to package managers and lockFileMaintenance.

How do I opt out dependencies from minimum release age checks?

To opt out a dependency from minimum release age checks, create a package rule with minimumReleaseAge=null:

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    // for instance
    "security:minimumReleaseAgeNpm",
  ],
  "packageRules": [
    {
      "description": "Disable minimum release age checks for internal dependencies",
      "matchPackageNames": ["@super-secret-organisation/*"],
      "minimumReleaseAge": null,
    },
  ],
}

!!! note As of Renovate 42.19.5, using minimumReleaseAge=0 days is treated the same as minimumReleaseAge=null.

Which datasources support release timestamps?

The datasource that Renovate uses must have a release timestamp for the minimumReleaseAge config option to work. Some datasources may have a release timestamp, but in a format Renovate does not support. In those cases a feature request needs to be implemented.

The table below lists every datasource Renovate supports, and whether it provides a release timestamp.

Which registries support release timestamps?

The below is a non-exhaustive list of public registries which support release timestamps:

Datasource Registry URL Supported Notes
crate https://crates.io ✅
rubygems https://rubygems.org ✅
docker https://index.docker.io ✅
docker (not Docker Hub) ❌ Issue
docker https://ghcr.io ❌ Issue
docker https://quay.io ❌ Issue
github-releases https://github.com ✅
terraform-module https://registry.terraform.io ✅
terraform-module https://registry.opentofu.org ✅ Queries api.opentofu.org for release timestamps
terraform-provider https://registry.terraform.io ✅
terraform-provider https://registry.opentofu.org ✅ Queries api.opentofu.org for release timestamps
github-tags https://github.com ✅
go https://proxy.golang.org, ✅
golang-version https://raw.githubusercontent.com/golang/website ✅
maven https://repo1.maven.org/maven2 ✅
node-version https://nodejs.org/dist ✅
npm https://registry.npmjs.org ✅
nuget https://api.nuget.org/v3/index.json ✅
pypi https://pypi.org/pypi/ ✅
ruby-version https://www.ruby-lang.org ✅
jsr https://jsr.io ✅ For packages without explicit timestamps, defaults to 2025-09-18

It is likely that if you are using a public registry (i.e. registry.npmjs.org, repo1.maven.org, etc) the release timestamp data will be present. We welcome user contributions to this table.

If you use a custom registry, for instance as a pull-through cache, additional configuration may be required.

If you are using a custom registry, or unsure about a public registry, you can confirm this using Renovate's debug logs by looking for the packageFiles with updates debug log line, which may contain a releaseTimestamp field in dependency updates:

packageFiles with updates debug log example
DEBUG: packageFiles with updates
{
  "baseBranch": "main",
  "config": {
    "dockerfile": [
      {
        "deps": [
          // NOTE that we're not seeing a release timestamp for this Docker digest
          {
            "depName": "ghcr.io/renovatebot/base-image",
            "packageName": "ghcr.io/renovatebot/base-image",
            "currentValue": "10.67.5",
            "currentDigest": "sha256:d67e849707f38e11c8674a59d3fffef1ea6977757f3a65d9d1a3a198bdd160cf",
            "replaceString": "ghcr.io/renovatebot/base-image:10.67.5@sha256:d67e849707f38e11c8674a59d3fffef1ea6977757f3a65d9d1a3a198bdd160cf",
            "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}",
            "datasource": "docker",
            "depType": "stage",
            "updates": [
              {
                "bucket": "major",
                "newVersion": "11.40.5",
                "newValue": "11.40.5",
                "newMajor": 11,
                "newMinor": 40,
                "newPatch": 5,
                "updateType": "major",
                "isBreaking": true,
                "newDigest": "sha256:81bbc8c8c561f6c4c2d059a5bcdfc95ef837682a41ac45bfbc1380d8d07dc941",
                "branchName": "renovate/main-ghcr.io-renovatebot-base-image-11.x"
              }
            ],
          }
      // ...
    ],
    "github-actions": [
      {
        "deps": [
          // NOTE that we get a release timestamp for this GitHub Action major version bump, as well as the current version's timestamp
          {
            "depName": "actions/setup-node",
            "commitMessageTopic": "{{{depName}}} action",
            "datasource": "github-tags",
            "versioning": "docker",
            "depType": "action",
            "replaceString": "actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0",
            "autoReplaceStringTemplate": "{{depName}}@{{#if newDigest}}{{newDigest}}{{#if newValue}} # {{newValue}}{{/if}}{{/if}}{{#unless newDigest}}{{newValue}}{{/unless}}",
            "currentValue": "v4.4.0",
            "currentDigest": "49933ea5288caeca8642d1e84afbd3f7d6820020",
            "updates": [
              {
                "bucket": "major",
                "newVersion": "v6.0.0",
                "newValue": "v6.0.0",
                "newDigest": "2028fbc5c25fe9cf00d9f06a71cc4710d4507903",
                "releaseTimestamp": "2025-10-14T02:37:06.000Z",
                "newVersionAgeInDays": 10,
                "newMajor": 6,
                "newMinor": 0,
                "newPatch": 0,
                "updateType": "major",
                "isBreaking": true,
                "libYears": 0.5323368531202435,
                "branchName": "renovate/main-actions-setup-node-6.x"
              }
            ],
            "packageName": "actions/setup-node",
            "warnings": [],
            "sourceUrl": "https://github.com/actions/setup-node",
            "registryUrl": "https://github.com",
            "mostRecentTimestamp": "2025-10-14T02:37:06.000Z",
            "isAbandoned": false,
            "currentVersion": "v4.4.0",
            "currentVersionTimestamp": "2025-04-02T19:20:51.000Z",
            "currentVersionAgeInDays": 204,
            "isSingleVersion": true,
            "fixedVersion": "v4.4.0"
          },

Given a log line such as ☝️ you can also use the following jq query to identify any dependencies (or their updates) that are missing the currentVersionTimestamp or releaseTimestamp fields like so:

jq query
# Code snippet licensed under the Apache-2.0, and co-authored-by: gpt-oss:20b
jq '
{
  # -------- missing currentVersionTimestamp ----------
  missingCurrentVersionTimestamps: [
    .config
    | to_entries[] as $ent
    | $ent.value[] as $group
    | $group.deps[] as $dep
    | select($dep.currentVersionTimestamp == null)
    | {
        manager: $ent.key,
        depName: $dep.depName,
        packageFile: $group.packageFile,
        datasource: $dep.datasource,
        registryUrls: (
          ($dep.registryUrl? | if . != null then [.] else [] end)
          + ($dep.registryUrls // [])
        )
      }
  ],
  # -------- missing releaseTimestamp in updates ----------
  missingReleaseTimestamps: [
    .config
    | to_entries[] as $ent
    | $ent.value[] as $group
    | $group.deps[] as $dep
    | select(any($dep.updates[]?; .releaseTimestamp == null))
    | {
        manager: $ent.key,
        depName: $dep.depName,
        packageFile: $group.packageFile,
        datasource: $dep.datasource,
        registryUrls: (
          ($dep.registryUrl? | if . != null then [.] else [] end)
          + ($dep.registryUrls // [])
        ),
        missingUpdates: [
          $dep.updates[]?
          | select(.releaseTimestamp == null)
          | . + {
              dependencyCurrentVersionTimestamp: $dep.currentVersionTimestamp,
              datasource: $dep.datasource
            }
        ]
      }
  ]
}
' debug-log.txt

Will then output:

jq query output
{
  "missingCurrentVersionTimestamps": [
    {
      "manager": "dockerfile",
      "datasource": "docker",
      "depName": "ghcr.io/containerbase/devcontainer",
      "packageFile": ".devcontainer/Dockerfile",
      "registryUrls": ["https://ghcr.io"]
    },
    {
      "manager": "renovate-config",
      "datasource": null,
      "depName": "renovatebot/.github",
      "packageFile": "renovate.json",
      "registryUrls": []
    },
    {
      "manager": "regex",
      "datasource": "docker",
      "depName": "ghcr.io/renovatebot/base-image",
      "packageFile": "lib/config/options/index.ts",
      "registryUrls": ["https://ghcr.io"]
    }
  ],
  "missingReleaseTimestamps": [
    {
      "manager": "dockerfile",
      "datasource": "docker",
      "depName": "ghcr.io/renovatebot/base-image",
      "packageFile": "tools/docker/Dockerfile",
      "registryUrls": ["https://ghcr.io"],
      "missingUpdates": [
        {
          "bucket": "major",
          "newVersion": "11.40.5",
          "newValue": "11.40.5",
          "newMajor": 11,
          "newMinor": 40,
          "newPatch": 5,
          "updateType": "major",
          "isBreaking": true,
          "newDigest": "sha256:81bbc8c8c561f6c4c2d059a5bcdfc95ef837682a41ac45bfbc1380d8d07dc941",
          "branchName": "renovate/main-ghcr.io-renovatebot-base-image-11.x",
          "dependencyCurrentVersionTimestamp": null,
          "dependencyDatasource": "docker"
        }
      ]
    },
    {
      "manager": "dockerfile",
      "datasource": "docker",
      "depName": "ghcr.io/renovatebot/base-image",
      "packageFile": "tools/docker/Dockerfile",
      "registryUrls": ["https://ghcr.io"],
      "missingUpdates": [
        {
          "bucket": "major",
          "newVersion": "11.40.5",
          "newValue": "11.40.5-full",
          "newMajor": 11,
          "newMinor": 40,
          "newPatch": 5,
          "updateType": "major",
          "isBreaking": true,
          "newDigest": "sha256:824737973a79d8c280f8ab1928017780fb936396dc83075a4f7770610eda37bd",
          "branchName": "renovate/main-ghcr.io-renovatebot-base-image-11.x",
          "dependencyCurrentVersionTimestamp": null,
          "dependencyDatasource": "docker"
        }
      ]
    },
    {
      "manager": "dockerfile",
      "datasource": "docker",
      "depName": "ghcr.io/renovatebot/base-image",
      "packageFile": "tools/docker/Dockerfile",
      "registryUrls": ["https://ghcr.io"],
      "missingUpdates": [
        {
          "bucket": "major",
          "newVersion": "11.40.5",
          "newValue": "11.40.5",
          "newMajor": 11,
          "newMinor": 40,
          "newPatch": 5,
          "updateType": "major",
          "isBreaking": true,
          "newDigest": "sha256:81bbc8c8c561f6c4c2d059a5bcdfc95ef837682a41ac45bfbc1380d8d07dc941",
          "branchName": "renovate/main-ghcr.io-renovatebot-base-image-11.x",
          "dependencyCurrentVersionTimestamp": null,
          "dependencyDatasource": "docker"
        }
      ]
    }
  ]
}

Notice that this indicates that:

  • There are 3 dependencies that do not have a release timestamp, across different Managers and Datasources
  • There are 3 dependency updates, where neither the dependency nor the dependency update itself have a release timestamp

How do I add timestamp data to custom registries?

Renovate requires release timestamp to be provided by the registry.

A common solution is to point Renovate to a registry that does have the release timestamp in the form that Renovate is expecting. You can achieve this by using packageRules to prepend the public registry's URL to the registryUrls.

You can see exact examples below:

Maven datasource

For minimumReleaseAge to work, the Maven source must return reliable last-modified headers.

If your custom Maven source registry is pull-through and does not support the last-modified header, like GAR (Google Artifact Registry's Maven implementation) then you can extend the Maven source registry URL with https://repo1.maven.org/maven2 as the first item. Then the currentVersionTimestamp via last-modified will be taken from Maven central for public dependencies.

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "packageRules": [
    {
      "matchDatasources": ["maven"],
      "registryUrls": [
        "https://repo1.maven.org/maven2",
        "https://europe-maven.pkg.dev/org-artifacts/maven-virtual"
      ]
    }
  ]
}

Pypi datasource

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "packageRules": [
    {
      "matchDatasources": ["pypi"],
      "registryUrls": [
        "https://pypi.org/pypi/",
        "https://custom-registry.example.com/pypi/some-repo/simple/"
      ]
    }
  ]
}