The self-hosted shape

Renovate has no daemon. It's a CLI that scans a list of repos, opens / updates PRs, and exits. Run it periodically — from a GitHub Actions cron job, a self-hosted scheduler, or a dedicated server with a cron entry.

The cron-from-GitHub-Actions pattern (simplest)

# .github/workflows/renovate.yml in a dedicated "renovate-bot" repo
name: Renovate
on:
  schedule:
    - cron: '0 1,9,17 * * *'    # 3x daily
  workflow_dispatch:

jobs:
  renovate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: renovatebot/github-action@v40
        with:
          configurationFile: renovate-config.js
          token: ${{ secrets.RENOVATE_TOKEN }}
        env:
          LOG_LEVEL: info

The token is a PAT (classic, with repo + workflow + read:org) or a GitHub App credential.

Configuration

// renovate-config.js (the bot's config, applied to every scanned repo)
module.exports = {
    platform: "github",
    autodiscover: true,                       // scan all repos the token has access to
    autodiscoverFilter: ["myorg/*"],

    onboarding: true,                          // open an "Onboarding" PR in each repo first time
    onboardingConfig: { extends: ["config:recommended"] },

    dryRun: null,                              // "lookup" / "full" for testing
    repositoryCache: "enabled",

    // Per-language opt-outs / opt-ins via package rules
    packageRules: [
        {
            description: "Group minor + patch npm updates",
            matchManagers: ["npm"],
            matchUpdateTypes: ["minor", "patch"],
            groupName: "npm dependencies",
        },
        {
            description: "Hold major updates for manual review",
            matchUpdateTypes: ["major"],
            labels: ["major-update"],
            automerge: false,
        },
    ],
};

Per-repo customization: renovate.json

Each scanned repo can have its own renovate.json (or .json5, .yml, embedded in package.json) overriding global config. The first run opens an "Onboarding" PR with a default renovate.json proposal; merge it (or edit first) to activate Renovate for that repo.

// renovate.json
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:recommended",
    ":dependencyDashboard",
    ":semanticCommits"
  ],
  "schedule": ["after 10pm and before 5am every weekday", "every weekend"],
  "timezone": "America/Toronto",
  "labels": ["dependencies"],
  "packageRules": [
    {
      "description": "Auto-merge non-major dev deps if CI passes",
      "matchDepTypes": ["devDependencies"],
      "matchUpdateTypes": ["minor", "patch"],
      "automerge": true,
      "automergeType": "pr",
      "platformAutomerge": true
    },
    {
      "description": "Group Tailwind + PostCSS",
      "matchPackagePatterns": ["^tailwindcss", "^postcss", "^autoprefixer"],
      "groupName": "Tailwind CSS"
    }
  ],
  "vulnerabilityAlerts": {
    "labels": ["security"],
    "automerge": true,
    "schedule": "at any time"
  }
}

The Dependency Dashboard

With :dependencyDashboard in extends, Renovate opens one long-lived issue per repo summarizing every pending update: open PRs, rate-limited updates, errored manifests, plus checkboxes to manually request immediate refresh of specific deps. The repo's living source of truth for "what's pending."

Schedules + rate limiting

Renovate doesn't dump 50 PRs on you at once:

  • Schedule — only open / update PRs during specified windows.
  • prConcurrentLimit — max open PRs at any time (default: 10).
  • prHourlyLimit — max new PRs per hour (default: 2).
  • minimumReleaseAge — only open PRs for releases older than N days (avoids broken-just-released versions).

Automerge with merge-confidence

For dev-only deps with passing CI, automerge is safe enough to leave on. Renovate's merge-confidence data (free public service from Mend) augments this: each version of a package has an adoption-rate + age signal; you can require "high confidence" before auto-merging.

{
  "packageRules": [
    {
      "matchUpdateTypes": ["patch", "minor"],
      "matchCurrentVersion": "!/^0/",          // skip 0.x packages (less stable)
      "matchConfidence": ["high", "very high"],
      "automerge": true
    }
  ]
}

Self-hosted on a dedicated server

# systemd-timer-driven (see /tutorials/systemd-timers-cron-replacement.html)
# /etc/systemd/system/renovate.service
[Service]
Type=oneshot
User=renovate
Environment=RENOVATE_TOKEN=<pat>
Environment=RENOVATE_PLATFORM=github
Environment=LOG_LEVEL=info
ExecStart=/usr/local/bin/renovate

# /etc/systemd/system/renovate.timer
[Timer]
OnCalendar=*-*-* 03:00,11:00,19:00:00
Persistent=true
Unit=renovate.service

[Install]
WantedBy=timers.target

Install via npm or Docker:

npm install -g renovate
# or
docker run --rm -e RENOVATE_TOKEN renovate/renovate:latest myorg/repo

For GitLab / Gitea / Bitbucket

The platform is configurable. For Gitea:

{
    "platform": "gitea",
    "endpoint": "https://gitea.example.com/api/v1/",
    "token": "<gitea-token>"
}

Same flow: Renovate scans repos, opens MRs / PRs, auto-merges when configured. Especially useful for self-hosted Gitea where Dependabot doesn't exist.

Patterns worth knowing

Group Kubernetes / Helm / Docker into one PR

{
  "packageRules": [{
    "matchManagers": ["helm-values", "helmv3", "kubernetes", "docker-compose"],
    "groupName": "Kubernetes dependencies",
    "schedule": ["before 5am on monday"]
  }]
}

Pin GitHub Actions to digests instead of tags

{
  "extends": ["helpers:pinGitHubActionDigests"]
}

Every action gets pinned to a specific commit SHA; Renovate updates the SHA + leaves the tag as a comment for human-readability. Protects against re-tagged actions doing surprise things.

Lockfile maintenance

{
  "lockFileMaintenance": {
    "enabled": true,
    "schedule": ["before 5am on monday"]
  }
}

Periodically re-resolves lockfiles even when no manifest changes — picks up transitive dependency updates that pure manifest-scan wouldn't catch.

Renovate vs Dependabot

  • Dependabot — built into GitHub; zero-setup for public-GitHub-only workflows. Limited language coverage (npm, pip, Go modules, Docker, GitHub Actions; basic Terraform). Less configurable.
  • Renovate — 60+ ecosystems; massively more configurable; runs anywhere; Dependency Dashboard, merge-confidence, etc. The trade-off is one more thing to operate.

For "this is one or two public-GitHub repos in JS," Dependabot is the right tool. For everything else — polyglot, monorepos, self-hosted Git, Kubernetes manifest updates, Helm chart updates, image tag updates — Renovate.