No description
This repository has been archived on 2026-09-18. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Ismo Vuorinen 4fcae3cbc8 feat: Coolify-hosted Renovate runner for git.esle.fi
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
2026-09-05 14:49:37 +02:00
docs/plans docs: add plan for Coolify-hosted Renovate runner 2026-09-05 14:47:35 +02:00
compose.yaml feat: Coolify-hosted Renovate runner for git.esle.fi 2026-09-05 14:49:37 +02:00
README.md feat: Coolify-hosted Renovate runner for git.esle.fi 2026-09-05 14:49:37 +02:00

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:

  1. Autodiscovered repositories lists the same repositories as the curl in step 1.
  2. Per-repository DRY-RUN: Would create PR lines appear.
  3. [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:

  1. Discovery. This repository is autodiscovered like any other, and compose.yaml matches the docker-compose manager's default file pattern. Nothing to do.
  2. A pull request that can exist. The image is pinned to a full 44.65.5, not a bare 44. 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=required applies to the bot's own repository too.
  3. 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

  1. Stop the Coolify resource — the loop dies, no further runs.
  2. Revoke the Forgejo PAT — severs all write access immediately, even if a container survives somewhere.
  3. 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.