---
name: "Renovate"
description: "Run self-hosted Renovate in a Buildkite pipeline."
author: "altana-ai"
repo: "renovate-buildkite-plugin"
stars: 0
official: false
version: "v0.1.3"
lastUpdated: "2026-08-11T22:33:39.000Z"
---

# Renovate Buildkite Plugin

A [Buildkite plugin](https://buildkite.com/docs/agent/v3/plugins) that runs
[self-hosted Renovate](https://docs.renovatebot.com/self-hosted-configuration/)
in a Buildkite pipeline.

It is a port of the [`renovatebot/github-action`](https://github.com/renovatebot/github-action)
GitHub Action, with the parts of GitLab's
[`renovate-runner`](https://gitlab.com/renovate-bot/renovate-runner) templates
that a scheduled pipeline needs: an ndjson log kept as an artifact, and a
config-validator mode.

- [Quick start](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/#quick-start)
- [Scheduling the run](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/#scheduling-the-run)
- [Configuration](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/#configuration)
- [Execution modes](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/#execution-modes)
- [Configuring Renovate itself](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/#configuring-renovate-itself)
- [Passing environment variables to Renovate](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/#passing-environment-variables-to-renovate)
- [Persisting the repository cache](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/#persisting-the-repository-cache)
- [GitHub App permissions](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/#github-app-permissions)
- [Hardening the app credentials](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/#hardening-the-app-credentials)
- [Validating Renovate configuration](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/#validating-renovate-configuration)
- [How it works](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/#how-it-works)
- [Requirements](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/#requirements)
- [Troubleshooting](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/#troubleshooting)
- [Development](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/#development)

## Quick start

Pair this plugin with
[`altana-ai/create-github-app-token`](https://github.com/altana-ai/create-github-app-token-buildkite-plugin):
it mints a GitHub App installation token, exports it as `GITHUB_TOKEN`, and
revokes it when the step finishes. This plugin picks that token up with no extra
configuration.

```yaml
steps:
  - label: ":robot_face: renovate"
    # Renovate is a scheduled job; see Scheduling the run below.
    if: build.source == "schedule"
    concurrency: 1
    concurrency_group: renovate
    # Buildkite secrets, mapped to the environment variables the plugins read.
    # Requires agent 3.106.0 or later; values are redacted from build logs.
    secrets:
      GITHUB_APP_ID: renovate_github_app_id
      GITHUB_APP_PRIVATE_KEY: renovate_github_app_private_key
    plugins:
      # Must be listed first: its pre-command hook runs before this plugin's
      # command hook, which is what puts GITHUB_TOKEN in scope.
      - altana-ai/create-github-app-token#v0.6.0:
          app-id: "$$GITHUB_APP_ID"
          private-key: "$$GITHUB_APP_PRIVATE_KEY"
          # With no permission-* options the token inherits the app's own
          # permissions. See GitHub App permissions below for the explicit form.
      - altana-ai/renovate#v0.1.3:
          configuration-file: .buildkite/renovate-config.js
```

The step needs no `command` — this plugin provides it. The `$$` keeps Buildkite
from interpolating the variable at pipeline-upload time, so the auth plugin
resolves it on the agent instead.

[`examples/pipeline.yml`](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/examples/pipeline.yml) is the full production pipeline:
a scheduled run, a dry run for manual builds, and config validation on pull
requests.

> [!IMPORTANT]
> Install the GitHub App on the **runner repository** as well as the repositories
> Renovate manages. `create-github-app-token` resolves the installation from the
> repository the build is running in, so a dedicated runner repo without the app
> installed fails with `GitHub App is not installed on …` before Renovate starts.

Renovate's own [`buildkite` manager](https://docs.renovatebot.com/modules/manager/buildkite/)
updates plugin references in `.buildkite/*.yml`, so once this is running it keeps
its own version current.

## Scheduling the run

Renovate is a scheduled job, not a per-commit one. Create a dedicated pipeline
for it, then add a schedule under **Pipeline Settings → Schedules**:

| Field  | Value       |
| ------ | ----------- |
| Cron   | `3 * * * *` |
| Branch | `main`      |

Give the step `concurrency: 1` with a `concurrency_group`, so two scheduled
builds can never run Renovate against the same repositories at once.

Then guard the step, or every push to the runner repository starts a real run —
including the push that edits the pipeline:

```yaml
if: build.source == "schedule"
```

That leaves the other build sources free for safer work, which is how
[`examples/pipeline.yml`](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/examples/pipeline.yml) is arranged:

| Build source | Step |
| ------------ | ---- |
| `schedule`   | Renovate runs for real |
| `ui`         | Renovate dry-runs (`dry-run: full`), so a config change can be checked by hand |
| push / PR    | `validate-config` only — no token is granted to that step |

> [!NOTE]
> An installation access token is valid for one hour and Renovate will not renew
> it, so a run that outgrows that window starts failing partway through. Keep
> `--autodiscover-filter` narrow, or split wide estates across several steps.

## Configuration

### Renovate

#### `configuration-file` (string)

Path to Renovate's global configuration file, in JS or JSON format, relative to
the checkout. Sets [`RENOVATE_CONFIG_FILE`](https://docs.renovatebot.com/self-hosted-configuration/#configfile);
in `docker` mode the file is mounted into the container. See
[`examples/renovate-config.js`](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/examples/renovate-config.js).

Avoid naming it something Renovate would also read as *repository* config
(`renovate.json`, `.renovaterc`, ...).

#### `token` (string)

The token Renovate authenticates to the platform with. Either a literal value or
a reference to an environment variable — `"$MY_TOKEN"`, quoted as `"$$MY_TOKEN"`
in the pipeline YAML so Buildkite does not interpolate it first.

Resolution order, when the option is not set:

1. `$RENOVATE_TOKEN`
2. `$GITHUB_TOKEN` — what `create-github-app-token` leaves behind

The resolved token is registered with the agent's redactor and reaches Renovate
through the environment, never as a command-line argument.

For the [`github-actions` manager](https://docs.renovatebot.com/modules/manager/github-actions/)
to update workflow files, the token needs the app's **Workflows: write**
permission (or the `workflow` scope, for a PAT) — that is
`permission-workflows: write` on `create-github-app-token`, which is a different
permission from `permission-actions`. Without it GitHub rejects the push with
`refusing to allow a GitHub App to create or update workflow`, and Renovate
cannot raise PRs for `actions/*` updates.

#### `github-com-token` (string)

Token used for github.com changelog and release lookups, so those requests are
not rate-limited. Sets `GITHUB_COM_TOKEN`. Also accepts an environment variable
reference.

#### `extra-flags` (string or list)

Extra CLI arguments for `renovate`, the equivalent of the GitLab runner's
`RENOVATE_EXTRA_FLAGS`:

```yaml
extra-flags:
  - "--autodiscover=true"
  - "--autodiscover-filter=altana-ai/*"
```

#### `dry-run` (string)

`extract`, `lookup` or `full`. Sets `RENOVATE_DRY_RUN` — useful for a
non-scheduled build that should look but not touch.

#### `log-level` (string)

Console log level: `trace`, `debug`, `info`, `warn`, `error`, `fatal`. Sets
`LOG_LEVEL`.

#### `log-file` (string), `log-file-level` (string), `upload-log-artifact` (boolean)

Write Renovate's machine-readable ndjson log to `log-file` (relative to the
checkout) at `log-file-level` (default `debug`), and upload it as a Buildkite
artifact unless `upload-log-artifact` is `false`. The log is kept and uploaded
even when Renovate fails, which is when it is most wanted.

#### `base-dir` (string)

Directory Renovate clones repositories and stores its cache in. Sets
`RENOVATE_BASE_DIR`. In `docker` mode the path must be inside one of
`docker-volumes` — the default `/tmp:/tmp` already covers Renovate's own default
of `/tmp/renovate`.

#### `print-version` (boolean, default `true`)

Log the Renovate version before running it.

### Config validation

#### `validate-config` (boolean, default `false`)

Run `renovate-config-validator` instead of `renovate`. No token is required. See
[Validating Renovate configuration](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/#validating-renovate-configuration).

#### `validator-flags` (string or list)

Files to validate. Defaults to `configuration-file` when not set.

### Execution

#### `mode` (string, default `docker`)

`docker` or `native`. See [Execution modes](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/#execution-modes).

#### `renovate-image` (string, default `ghcr.io/renovatebot/renovate`)

Image to run — for example a pull-through cache such as
`proxy.example.com/renovate/renovate`.

#### `renovate-version` (string, default `44`)

Image tag, optionally with a digest. Use `full` for the image with every
lock-file tool preinstalled (much larger), or pin it:

```yaml
renovate-version: "44.24.2@sha256:90759c92793143ae7db68cd14e690109d8ace3527e17c7d188c96a1ad628282e"
```

#### `env-regex` (string)

Which of the agent's environment variables are passed into the container. See
[Passing environment variables to Renovate](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/#passing-environment-variables-to-renovate).

#### `docker-pull` (string, default `always`)

`always`, `missing` or `never`, passed to `docker run --pull`.

#### `docker-tty` (boolean, default `true`)

Allocate a pseudo-TTY, which keeps Renovate's output coloured in the Buildkite
log.

#### `docker-volumes` (string or list, default `/tmp:/tmp`)

Volume mounts. A string may hold several, separated by `;`:

```yaml
docker-volumes: "/tmp:/tmp ; /opt/cache:/opt/cache"
```

```yaml
docker-volumes:
  - /tmp:/tmp
  - /opt/cache:/opt/cache
```

#### `docker-user` (string)

User or UID the container runs as. Defaults to the image's own unprivileged
user; only set it (usually to `root`) alongside `docker-cmd-file`.

#### `docker-network` (string)

Docker network the container joins, e.g. `host`.

#### `docker-cmd-file` (string)

An executable file in the checkout to run inside the container instead of
`renovate`, for installing extra tooling first. It is mounted at the container
root. See [`examples/renovate-entrypoint.sh`](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/examples/renovate-entrypoint.sh).

```yaml
plugins:
  - altana-ai/renovate#v0.1.3:
      docker-cmd-file: .buildkite/renovate-entrypoint.sh
      docker-user: root
```

The script is responsible for dropping privileges and for forwarding `"$@"` if
`extra-flags` are also in play.

#### `mount-docker-socket` (boolean, default `false`), `docker-socket-host-path` (string)

Mount the agent's Docker socket into the container and add the container's user
to the socket's group, so Renovate's
[`postUpgradeTasks`](https://docs.renovatebot.com/configuration-options/#postupgradetasks)
can use Docker. `docker-socket-host-path` overrides the host path
(`/var/run/docker.sock`).

## Execution modes

**`docker` (default)** — the hook runs `docker run ghcr.io/renovatebot/renovate`
on the agent host. This is how the GitHub Action works, and it needs nothing of
the step but a Docker socket.

**`native`** — the hook runs the `renovate` binary already on `PATH`, for steps
that are themselves executed inside the Renovate image. This is how the GitLab
runner templates work, and it is the right mode for the Kubernetes agent stack
or the `docker-compose` plugin. The `docker-*` options are ignored.

```yaml
steps:
  - label: ":robot_face: renovate"
    agents:
      queue: kubernetes
    plugins:
      - kubernetes:
          podSpec:
            containers:
              - image: ghcr.io/renovatebot/renovate:44
      - altana-ai/renovate#v0.1.3:
          mode: native
```

## Configuring Renovate itself

Only the options above belong to this plugin. Everything else is Renovate's own
configuration, and there are two ways to give it:

1. **`configuration-file`** — global config as JS or JSON.
2. **`RENOVATE_*` environment variables** — every
   [self-hosted option](https://docs.renovatebot.com/self-hosted-configuration/)
   has one, and they are forwarded to Renovate automatically.

```yaml
steps:
  - label: ":robot_face: renovate"
    env:
      RENOVATE_REPOSITORY_CACHE: enabled
      RENOVATE_ONBOARDING: "false"
      RENOVATE_PLATFORM_COMMIT: enabled
    plugins:
      - altana-ai/renovate#v0.1.3: ~
```

For a GitHub Enterprise Server instance, set `RENOVATE_ENDPOINT` to
`https://git.example.com/api/v3`.

## Passing environment variables to Renovate

In `docker` mode only variables matching `env-regex` cross into the container.
The default is the GitHub Action's:

```text
^(RENOVATE_[[:alnum:]_]+|LOG_LEVEL|LOG_FILE|LOG_FILE_LEVEL|GITHUB_COM_TOKEN|NODE_OPTIONS|NO_COLOR|(HTTPS?|NO)_PROXY|(https?|no)_proxy)$
```

To pass something else — credentials for a
[host rule](https://docs.renovatebot.com/configuration-options/#hostrules), say
— widen the regex. Prefer a distinct prefix over `RENOVATE_`, which is reserved
for Renovate's own options:

```yaml
steps:
  - label: ":robot_face: renovate"
    env:
      CUSTOM_TFE_TOKEN: "$$MY_TFE_TOKEN"
    plugins:
      - altana-ai/renovate#v0.1.3:
          configuration-file: .buildkite/renovate-config.js
          env-regex: "^(RENOVATE_[A-Za-z0-9_]+|LOG_LEVEL|CUSTOM_TFE_TOKEN)$"
```

```js
module.exports = {
  hostRules: [
    {
      hostType: 'terraform-module',
      matchHost: 'app.terraform.io',
      token: process.env.CUSTOM_TFE_TOKEN,
    },
  ],
};
```

Bash matches POSIX extended regular expressions, so `\w` and `(?:...)` are not
native — but because they are what the GitHub Action's docs use, a regex
containing them is translated rather than rejected.

Variables the plugin sets itself (the token, the config file path, the log
settings) are always passed, even if `env-regex` would not have matched them.
Values are passed by name (`docker run --env NAME`), so no secret appears in the
host's process list.

## Persisting the repository cache

Renovate's [repository cache](https://docs.renovatebot.com/self-hosted-configuration/#repositorycache)
stops it re-proposing work it already decided about. It lives under
`<base-dir>/cache/renovate/repository`, i.e. `/tmp/renovate/cache/renovate/repository`
by default, which the default `/tmp:/tmp` mount already exposes to the host.

```yaml
steps:
  - label: ":robot_face: renovate"
    env:
      RENOVATE_REPOSITORY_CACHE: enabled
    cache:
      # Requires an agent with a cache backend configured.
      paths:
        - /tmp/renovate/cache/renovate/repository
    plugins:
      - altana-ai/renovate#v0.1.3: ~
```

Renovate reads the cache as an unprivileged user inside the container (UID
`12021`), so a cache restored by the agent user may need
`sudo chown -R 12021:0 /tmp/renovate` in a `pre-command` hook first. On an
ephemeral agent without a cache backend, leave it off: the cache is an
optimisation, not state Renovate depends on.

## GitHub App permissions

Renovate's [platform documentation](https://github.com/renovatebot/renovate/blob/main/lib/modules/platform/github/readme.md)
gives the permission set a self-hosted app needs, and the `permission-*` option
that requests each one:

| GitHub App permission | Scope | `create-github-app-token` option | What loses if it is missing |
| --------------------- | ----- | -------------------------------- | --------------------------- |
| Contents | read + write | `permission-contents: write` | Everything — branches and commits |
| Pull requests | read + write | `permission-pull-requests: write` | Everything — the PRs themselves |
| Issues | read + write | `permission-issues: write` | Dependency Dashboard, config-error issues |
| Checks | read + write | `permission-checks: write` | Branch status, so automerge cannot tell whether CI passed |
| Commit statuses | read + write | `permission-statuses: write` | Renovate's own statuses, e.g. `renovate/artifacts` |
| Workflows | read + write | `permission-workflows: write` | Updates to files under `.github/workflows` |
| Administration | read | `permission-administration: read` | Branch-protection awareness for automerge decisions |
| Members | read | `permission-members: read` | Team reviewers and assignees |
| Dependabot alerts | read | `permission-vulnerability-alerts: read` | Vulnerability-remediation PRs |
| Metadata | read | — | Mandatory, granted implicitly |

The stock [Renovate app](https://github.com/apps/renovate) also holds **Packages:
read**, which Renovate's table omits. Add `permission-packages: read` only if your
dependencies resolve from GitHub Packages or `ghcr.io`.

Two things to get right:

- **The app's grants and the token's request are separate.** The app has to hold a
  permission, *and* the step has to ask for it. `create-github-app-token` sends an
  explicit `permissions` object as soon as one `permission-*` option is set, which
  narrows the token to exactly what is listed — so a partial list silently drops
  the rest. Requesting more than the app holds is a hard failure: GitHub answers
  `422`, `The permissions requested are not granted to this installation`.
  Omitting every `permission-*` option makes the token inherit the app's own set,
  which is the simplest correct choice for an app dedicated to Renovate.
- **An installation token covers at most one organization.** Renovate documents
  this directly: running across several orgs needs a separate installation, token
  and step for each, with `--autodiscover-filter` scoped accordingly.

## Hardening the app credentials

The private key is the one long-lived secret in this setup: anything that can read
it can mint tokens for every repository the app is installed on. Two controls are
worth applying.

**Scope the token, not just the key.** Grant the app only what
[GitHub App permissions](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/#github-app-permissions) lists — drop `Workflows` if
Renovate should not touch `.github/workflows` — and add `repositories:` to
`create-github-app-token` to scope the token to a subset of the installation. Tokens are revoked when the step ends
and expire within the hour regardless.

**Restrict which builds can read the key.** A Buildkite secret is readable by
every agent in its cluster by default. Attach an access policy to the private key
so only this pipeline can fetch it — under the cluster's secret, **Access** tab →
*Restrict access to agents matching a policy*:

```yaml
# Only the Renovate runner pipeline, and only when Buildkite itself started the
# build (a schedule, or someone clicking New Build).
- pipeline_id:
    - "01234567-89ab-cdef-0123-456789abcdef"
  build_source:
    - "schedule"
    - "ui"
```

Policy semantics: claims within a rule are ANDed, values within a claim are ORed,
and rules are ORed — if no rule matches, access is denied. Prefer the first-party
claims (`pipeline_id`, `build_source`, `cluster_queue_id`), which Buildkite
generates itself; the third-party ones (`pipeline_slug`, `build_branch`,
`build_creator`) are convenient but derive from user- or tool-supplied values, so
they are weaker ground for a security boundary. `pipeline_id` is on the pipeline's
settings page.

Adding `build_source` matters here: it is what stops a pull request against the
runner repository from reading the key, so an untrusted branch cannot mint a token
even if it edits the pipeline. Combine it with the `if:` guard above — the guard
decides what runs, the policy decides what can be read.

Confirm the accepted `build_source` values against your own pipeline before
relying on the policy; the same vocabulary as `build.source` in step conditionals
applies, and Buildkite's docs enumerate only examples.

## Validating Renovate configuration

`validate-config` runs `renovate-config-validator` instead of Renovate, which is
worth a pull-request step in every repository that has Renovate config:

```yaml
steps:
  - label: ":mag: renovate config"
    plugins:
      - altana-ai/renovate#v0.1.3:
          validate-config: true
          validator-flags:
            - renovate.json
            - .buildkite/renovate-config.js
```

In `docker` mode the checkout is mounted read-only and the paths are resolved
inside it, so the files must live in the repository.

## How it works

1. Resolves the token from the `token` option, `$RENOVATE_TOKEN` or
   `$GITHUB_TOKEN`, and registers it with the agent's redactor.
2. Translates the plugin's options into the `RENOVATE_*` / `LOG_*` environment
   variables Renovate reads.
3. Logs the Renovate version.
4. Runs Renovate — `docker run` with the environment, mounts and flags above, or
   the `renovate` binary on `PATH` in `native` mode.
5. Moves the ndjson log into the checkout and uploads it as an artifact.
6. Fails the step with Renovate's exit code.

Because the plugin provides the step's `command` hook, a `command` on the step
itself is ignored (the hook warns when it finds one). Use `docker-cmd-file`, or a
separate step, for work that has to happen around the Renovate run.

## Requirements

- `docker` on the agent (`docker` mode), or a step that runs inside the Renovate
  image (`native` mode)
- `buildkite-agent` on `PATH`, for token redaction and artifact upload
- A token with access to the repositories Renovate should manage

## Troubleshooting

**`No token available`** — nothing supplied a token. Either set the `token`
option, or list `create-github-app-token` *before* this plugin so its
`pre-command` hook has exported `GITHUB_TOKEN` by the time this plugin's
`command` hook runs.

**`GitHub App is not installed on …`** — from the auth plugin, before Renovate
starts. It resolves the installation from the repository the build is running in,
so the app has to be installed on the runner repository too, not only on the
repositories Renovate manages.

**`Authentication failure` from Renovate** — the token itself was rejected.
Check the GitHub App is installed on the target repositories and that
`username`/`gitAuthor` in the config file match the app.

**Renovate fails partway through a long run** — the installation token expired.
They last one hour and are not renewed; narrow `--autodiscover-filter` or split
the estate across steps.

**Renovate opens PRs it already opened** — the repository cache is not
persisting; see [Persisting the repository cache](https://github.com/altana-ai/renovate-buildkite-plugin/blob/HEAD/#persisting-the-repository-cache).

**Nothing happens, no repositories processed** — Renovate does not discover
repositories by default. Give it `repositories` in the config file, or
`--autodiscover=true` with an `--autodiscover-filter` in `extra-flags`.

**Debugging a run** — start with `log-level: debug`, and add
`log-file: renovate-log.ndjson` to get the full structured log as an artifact.

## Development

```bash
bats tests                     # hook tests, with docker and the agent stubbed
shellcheck hooks/* lib/*.bash tests/test_helper.bash tests/stubs/*
```

The tests replace `docker`, `renovate` and `buildkite-agent` with stubs on
`PATH` that record their argument vectors, and assert on the command the hook
builds — that command *is* what this plugin produces. Both commands, plus
`buildkite/plugin-linter`, run in CI on every pull request.

## Credits

Ported from [`renovatebot/github-action`](https://github.com/renovatebot/github-action)
(GPL-3.0-or-later), with ideas from
[`renovate-bot/renovate-runner`](https://gitlab.com/renovate-bot/renovate-runner)
(MIT).