| Filename | Latest commit message | Latest commit date |
|---|---|---|
Single Docker Compose service: renovate/renovate pinned to 44.65.5, an in-container sequential hourly loop as the scheduler, env-only config, no volume and no config file. The loop echoes Renovate's exit code rather than swallowing it, so a container that is up but failing every run is visible in Coolify's logs. Claude-Session: https://claude.ai/code/session_01GTfxG8EtHxqmahpcVXfr3y |
||
| docs/plans | ||
| compose.yaml | ||
| README.md | ||
coolify-renovate
Self-hosted Renovate bot for git.esle.fi (Forgejo), deployed on Coolify as a single Docker Compose resource.
Renovate is a one-shot CLI, not a server: it scans every repository it can see,
opens pull requests, and exits. compose.yaml wraps it in a while loop that
re-runs it hourly. The loop is sequential on purpose — two overlapping Renovate
runs race on branch creation, which a cron cannot prevent.
Design decisions and the risks accepted along the way live in
docs/plans/2026-09-05-coolify-renovate-forgejo-runner.md.
1. Create the Forgejo bot account
Use a dedicated account, not your own. It will hold a token with write
access to every repository it can reach, and Renovate executes package managers
(npm, yarn, …) against repository content while holding it.
The account needs a full name and an email address set — Forgejo's Renovate integration requires both.
Then create a Personal Access Token with exactly these scopes:
| Scope | Permission |
|---|---|
repo |
Read and Write |
user |
Read |
issue |
Read and Write |
organization |
Read |
Add read:packages as well if you use the Forgejo package registry.
Finally, grant the account access to the repositories it should manage — as a collaborator, or as a member of the owning organisation.
Check what "all repos" actually means
Renovate discovers exactly what the token can see. There is no server-wide "every repository" mode for a non-admin token, so verify the list before you deploy anything:
curl -H "Authorization: token $PAT" \
'https://git.esle.fi/api/v1/user/repos?limit=50'
Whatever this prints is what Renovate will manage. A repository missing here is missing from the bot's world; fix the access now rather than wondering later.
2. Create the github.com token
Create a read-only classic PAT on any github.com account — an empty one is fine. Renovate uses it to fetch changelogs for open-source dependencies.
This is not optional. Without it Renovate hits the unauthenticated github.com rate limit and starts closing and reopening pull requests, because it cannot get reliable information about the updates it already proposed.
3. Deploy on Coolify
Create a Docker Compose resource pointing at this repository. Coolify reads
compose.yaml from the root and surfaces every ${VAR} in it as an editable
environment variable.
| Variable | Required | Example |
|---|---|---|
RENOVATE_TOKEN |
yes | the Forgejo PAT from step 1 |
RENOVATE_GITHUB_COM_TOKEN |
yes | the github.com PAT from step 2 |
RENOVATE_GIT_AUTHOR |
yes | Renovate Bot <renovate@esle.fi> |
LOG_LEVEL |
no (info) |
leave at info; debug prints resolved host rules |
RUN_INTERVAL_SECONDS |
no (3600) |
seconds between runs |
RENOVATE_GIT_AUTHOR is mandatory — Renovate refuses to start without it — and
its email should match the bot account's Forgejo email, or its commits show up
unattributed.
Do not put any of these values in compose.yaml. It is version-controlled.
4. First boot: dry run
Before the first deploy, add one more variable in the Coolify UI only:
RENOVATE_DRY_RUN=full
It is deliberately absent from compose.yaml: its allowed values are
extract, lookup and full, and there is no valid string meaning "off", so
it has to be added and removed rather than toggled.
Deploy, then read the container logs and confirm all three:
Autodiscovered repositorieslists the same repositories as thecurlin step 1.- Per-repository
DRY-RUN: Would create PRlines appear. [runner] renovate exited 0— the run finished cleanly.
Then check git.esle.fi: there should be zero new pull requests.
5. Go live
Delete RENOVATE_DRY_RUN from Coolify and redeploy.
Each discovered repository gets one "Configure Renovate" onboarding pull
request. A repository stays silent until that PR is merged — that is
RENOVATE_REQUIRE_CONFIG=required doing its job, and it means the first live
run opens one PR per repository at once.
Wait for a second [runner] line roughly an hour later. The first run only
proves Renovate works; the second proves the scheduler does.
6. Let the bot update itself
Renovate does not update itself. It updates the file that describes itself —
this repository's compose.yaml — and only if three links all hold:
- Discovery. This repository is autodiscovered like any other, and
compose.yamlmatches the docker-compose manager's default file pattern. Nothing to do. - A pull request that can exist. The image is pinned to a full
44.65.5, not a bare44. A bare major tag floats: its content changes but the string never does, so Renovate would raise nothing and the bot would age silently forever. This repository also needs its own onboarding PR merged —requireConfig=requiredapplies to the bot's own repository too. - A merge that restarts the container. Enable automatic deployment for this resource in Coolify and add the matching webhook in the Forgejo repository settings (Forgejo speaks the Gitea webhook format). Without this the merged PR changes the file while the container keeps running the old image.
Verify link 3 on its own by pushing a trivial commit to main and confirming
Coolify redeploys from the webhook alone. The full chain can only be proven when
a real Renovate release lands: PR appears → merge → redeploy → new version in
the logs.
Merging its own bump restarts the container, possibly mid-run. That is fine — Renovate re-reads branch and PR state from Forgejo every run, so an interrupted run costs time, not correctness.
Operations
Is it working? Coolify's log view. Every run ends with
[runner] renovate exited N. A non-zero N every hour means a container that
looks healthy but is doing nothing — there is no alerting, so this is found by
looking.
Change the schedule. Edit RUN_INTERVAL_SECONDS in Coolify and restart. The
value is read by the shell at runtime, so no rebuild is needed.
Runs feel slow. The default image installs toolchains on every run. Switch
the image to renovate/renovate:44.65.5-full, which ships them pre-installed,
before reaching for a cache volume — a fresh Docker named volume is root-owned
and the Renovate image does not run as root, so the volume buys a permission bug
before it buys a cache.
A bad Renovate release got merged. The container crash-loops, and the thing
that would open the PR to escape is the thing that is broken. Pin the previous
version in compose.yaml and push.
Rollback
- Stop the Coolify resource — the loop dies, no further runs.
- Revoke the Forgejo PAT — severs all write access immediately, even if a container survives somewhere.
- Close unwanted pull requests. They are all on
renovate/-prefixed branches, so they close in bulk.
Nothing is written outside Forgejo branches and pull requests. There is no state to migrate back.