Renovate
A Conn3x circuit that runs Renovate on a host you own, on a timer, against the repositories you list. Renovate reads each repository’s dependency files, finds what’s out of date and opens pull requests to update it. Running it yourself means it works with a Forgejo, Gitea or GitLab on your own network, and the token it pushes with never leaves your hosts.
Each run is the official image under Podman, pinned by tag and digest, started by a systemd timer: hourly unless you say otherwise. The token goes to a root-only file, written from an ephemeral value, so it’s not in the plan or state. Renovate’s own validator checks the config before it’s written. It runs on Debian, Ubuntu, Fedora and Rocky Linux.
variable "renovate_token" { type = string ephemeral = true sensitive = true}
module "renovate" { source = "github.com/conn3xorg/circuit-renovate?ref=v0.1.0"
hosts = { tools = "192.168.1.70" } ssh_private_key_file = "~/.ssh/id_ed25519"
platform = "forgejo" endpoint = "https://git.example.com/api/v1" token = var.renovate_token repositories = ["infra/homelab", "infra/website"] git_author = "Renovate Bot <renovate@example.com>"}Then run conn3x init and conn3x apply with the token in the environment:
export TF_VAR_renovate_token='...'conn3x applyA second conn3x plan says No changes. Every plan needs the token too, since Conn3x checks the file it’s kept in on the host against it.
The first run on a repository opens an onboarding pull request that adds renovate.json. Merge it and the next run starts on updates. To try it on a throwaway container with its own Forgejo first, see examples/local.
The token
Section titled “The token”Make a dedicated account for Renovate on your forge, so its pull requests and commits are plainly its own, and give it write access to the repositories you list. For Forgejo, Renovate’s docs ask for a token with repo and issue read and write, and user and organization read. For GitHub, use a fine-grained token, or better, a GitHub App; for GitLab, a token with api and write_repository. Renovate’s platform pages list each one.
When platform isn’t github, Renovate still looks things up on GitHub.com, for release notes and for dependencies that live there. Without a token, GitHub’s limit for anonymous calls stops those quickly. Give github_com_token a read-only GitHub.com token to lift it. Both tokens go in the same root-only file.
More options
Section titled “More options”config takes any of Renovate’s self-hosted and repository options, merged over the ones the circuit sets:
config = { onboarding = false requireConfig = "optional" extends = ["config:recommended"] timezone = "Europe/London" schedule = ["before 6am on monday"] }Renovate’s validator, from the same image, checks the whole config in --strict mode before it replaces the old one, so a typo or a misspelt option fails the apply instead of a run at night. Keep secrets out of config: it’s written to the plan and state as it stands.
schedule on the module decides when Renovate runs. schedule in config decides when, during those runs, it may open pull requests. Run hourly and limit pull requests to a quiet window: Renovate also rebases and closes pull requests on every run, so a run once a week leaves them stale.
What it does
Section titled “What it does”On each host, in order:
- Installs Podman and pulls
ghcr.io/renovatebot/renovate:VERSION@DIGEST. - Writes
/etc/renovate/config.json, mode0644, withplatform,endpoint,repositories,git_authorandconfig, afterrenovate-config-validator --strictin the image passes it. - Writes
/etc/renovate/token.env, mode0600, withRENOVATE_TOKENand, if set,GITHUB_COM_TOKEN. It’s written from ephemeral values: the plan and state hold an HMAC of the content, never the tokens. - Creates
/var/cache/renovate, mode0700, for clones and the tools Renovate installs as it needs them. Podman hands it to the container’s user, UID 12021, at the start of each run. - Writes
/usr/local/sbin/conn3x-renovate, which runs one Renovate pass in a container that’s removed when it ends. - Sets up
renovate.timerandrenovate.service, which runs the script. A run missed while the host was off happens at the next boot.
The container shares the host’s network, so a forge that listens only on the host’s loopback is reachable, and it listens on nothing itself. Each run’s log goes to the journal: journalctl -u renovate.service. To run now rather than wait, systemctl start renovate.service; it returns when the run ends.
Why a container
Section titled “Why a container”Renovate is a Node.js program, and the image is how its maintainers ship and test it. It’s the default image, not -full: it has Node.js and installs whatever else a repository needs, such as a Go toolchain or Poetry, into the cache the first time it’s needed, about 450 MB to start with rather than several gigabytes. renovate_version and image_digest go into the script together, so a tag that’s re-pushed upstream can’t change what runs. Renovate releases several times a week; change both inputs to upgrade.
What it doesn’t do
Section titled “What it doesn’t do”- It doesn’t run Renovate’s webhook server, or Mend’s Community Edition. Runs happen on the timer, not when someone pushes.
- It doesn’t make the account or the token. Do that on your forge first.
- It doesn’t keep a copy of the logs beyond the journal.
- Deleting the module removes the timer and its service, and leaves Podman,
/etc/renovateand the cache in place.
Inputs
Section titled “Inputs”| Name | Type | Default | Description |
|---|---|---|---|
hosts |
any | Required | Hosts to configure, by name. Each value is an address, such as "192.168.1.70", or a host object such as { address = "192.168.1.70", host_key = "ssh-ed25519 AAAA..." }. A host object can also set user, port, private_key_file and jump_hosts. The two forms can be mixed. |
ssh_user |
string | "root" |
User to log in as. |
ssh_port |
number | 22 |
SSH port. |
ssh_private_key_file |
string | null |
Path to a private key. When null, Conn3x uses the SSH agent. |
ssh_known_hosts_file |
string | null |
known_hosts file to check host keys against. When null, ~/.ssh/known_hosts. Not used for a host object with host_key. |
ssh_become |
bool | false |
Run commands as root through sudo. Set it when ssh_user isn’t root. |
platform |
string | Required | "github", "gitlab", "forgejo", "gitea", "bitbucket", "bitbucket-server" or "azure". |
endpoint |
string | null |
The platform’s API address, such as "https://git.example.com/api/v1" for Forgejo. When null, the public service, such as GitHub.com. |
repositories |
list(string) | Required | Repositories to run on, such as ["acme/api"]. |
token |
string, sensitive, ephemeral | Required | Access token for Renovate’s account. Ephemeral and sensitive: it reaches a root-only file on the host but never the plan or state. |
github_com_token |
string, sensitive, ephemeral | null |
A read-only GitHub.com token for release notes and lookups when platform isn’t github. Ephemeral and sensitive. |
git_author |
string | null |
Author of Renovate’s commits, such as "Renovate Bot <bot@example.com>". |
config |
any | {} |
More of Renovate’s options, merged over the ones above. Checked by Renovate’s validator. In the plan and state, so no secrets. |
schedule |
string | "hourly" |
When Renovate runs, as a systemd calendar expression. |
log_level |
string | "info" |
"debug", "info", "warn" or "error". |
renovate_version |
string | "44.220.0" |
Tag of Renovate’s image. Change image_digest with it. |
image_digest |
string | "sha256:71599a20..." |
Digest of the image for renovate_version. Podman pulls by the digest. Find it on the package page or with skopeo inspect docker://ghcr.io/renovatebot/renovate:TAG. |
image_repository |
string | "ghcr.io/renovatebot/renovate" |
Where to pull the image from. "docker.io/renovate/renovate" serves the same digests. |
Outputs
Section titled “Outputs”| Name | Description |
|---|---|
service |
renovate.service, which runs Renovate once. |
timer |
renovate.timer, which starts each run. |
image |
The image each run uses, with its tag and digest. |
config_file |
/etc/renovate/config.json. |
Requirements
Section titled “Requirements”Requires Conn3x v0.7.0 or later and systemd on each host. The image is built for amd64 and arm64. Each host needs to reach its package mirror, GitHub’s registry, your forge, and the registries your repositories’ dependencies come from.
Tested on
Section titled “Tested on”Rocky Linux 10, Fedora 45, Ubuntu 24.04 and Debian 13. CI starts a throwaway Forgejo 17 beside the host from examples/local, with a repository whose package.json pins lodash 4.17.20 and an access token made for the job. It applies the example with Renovate 44.219.0, upgrades to the default, and checks the next plan has no changes and the token isn’t in state. check.sh then starts renovate.service, waits for the run, and checks Renovate opened a pull request for lodash on Forgejo, and that the token is only in token.env. CI restarts the host, checks the timer is active, runs Renovate again, and plans clean once more.
The same runs passed by hand on arm64 in rootful Podman on a Mac. By hand on DigitalOcean droplets, Ubuntu 24.04 and Rocky Linux 10 with SELinux enforcing, each with its own throwaway Forgejo: Renovate opened the lodash pull request, the token wasn’t in state, and after a reboot the timer was active, a run worked and the plan was clean. The Rocky audit log had no denials. Other platforms, such as GitHub and GitLab, haven’t been tried.
License
Section titled “License”MPL-2.0. See LICENSE.
Renovate itself is under the GNU Affero General Public License v3.0. Running it on your own hosts is fine; changing it and offering it to others over a network means sharing your changes.
Source
Section titled “Source”- Repository: conn3xorg/circuit-renovate. This page covers v0.1.0; the repository’s tags list every release.
- Examples: examples/local.
- Related circuits: Java, PHP-FPM, Jenkins, GitHub Actions runner, GitLab Runner, Cinc Client, Node.js, Perl, Bun, Selenium Grid, k6, OpenVox, Rust, Go, Python, Ruby, Kotlin, .NET, C toolchain, Semaphore UI.