Self-Host Renovate for Docker Compose Updates Without the YOLO Button
Watchtower is convenient until it updates the wrong thing at the wrong time. Here's how I use Renovate to get pull requests for Docker Compose updates instead.
I used to let Watchtower update a bunch of containers automatically.
It felt great for about two weeks. Then one of my apps shipped a breaking change, the container restarted while I was away, and I came back to a service that was technically “up” but absolutely not working. My monitoring was green. My users were annoyed. Classic homelab comedy.
I still like automatic update checks. I just do not like automatic update deployment for anything I care about.
That is where Renovate fits really nicely. Instead of silently pulling new images, Renovate opens pull requests when your Docker Compose files have updates available. You review the diff, skim the release notes, merge when ready, and deploy on your schedule.
Honestly, this is the update workflow most self-hosters should use once their server stops being a toy.
Why Renovate instead of Watchtower?
Watchtower is simple: it sees a newer image, pulls it, restarts the container. Done.
That is fine for throwaway tools. I still use that pattern for tiny internal services where breakage is mildly annoying and nothing more. But for Vaultwarden, Nextcloud, Immich, databases, reverse proxies, and anything exposed to the internet, I want a paper trail.
Renovate gives you that:
- a pull request per update, or grouped updates if you prefer
- changelog links when it can find them
- version pinning instead of floating
latestchaos - scheduled update windows
- easy rollback because every change is in Git
The big mindset shift: your Compose files become infrastructure code. If that sounds fancy, it is not. It just means “I can see what changed before my server changes.”
The setup I use
My stacks live in a private Git repository. Nothing exotic:
homelab/
apps/
uptime-kuma/docker-compose.yml
vaultwarden/docker-compose.yml
paperless/docker-compose.yml
renovate.json
Each Compose file pins image versions. This part matters.
Bad:
image: louislam/uptime-kuma:latest
Better:
image: louislam/uptime-kuma:1.23.16
I know latest feels easier. It is also a tiny trapdoor under your weekend. Pin versions, let Renovate tell you when a newer one exists, and decide like an adult with coffee.
Create the Renovate config
At the root of your repo, create renovate.json:
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"docker-compose": {
"fileMatch": ["(^|/)docker-compose\\.ya?ml$"]
},
"schedule": ["before 6am on monday"],
"timezone": "Europe/Paris",
"labels": ["dependencies", "docker"],
"packageRules": [
{
"matchDatasources": ["docker"],
"groupName": "docker image updates",
"automerge": false
},
{
"matchPackageNames": ["vaultwarden/server", "nextcloud", "ghcr.io/immich-app/immich-server"],
"groupName": "sensitive app updates",
"automerge": false,
"separateMinorPatch": true
}
]
}
A few opinions here.
I schedule updates for Monday morning because I do not want dependency PRs popping up all week. I also avoid Friday updates for important services. Breaking your server before the weekend is a hobby, not a strategy.
I group normal Docker updates together, but keep sensitive apps more visible. Password managers, photo libraries, file sync tools, and anything with a database deserve slower hands.
Run Renovate with Docker
You can use the hosted Renovate app if your repo is on GitHub or GitLab and you are comfortable with that. For a private homelab repo, I prefer running it myself.
Create a personal access token with access to the repository. On GitHub, it needs repository contents and pull request permissions. Store it somewhere boring and protected, not in the Compose file itself.
Here is a simple docker-compose.yml for Renovate:
services:
renovate:
image: renovate/renovate:39
container_name: renovate
restart: "no"
environment:
RENOVATE_PLATFORM: github
RENOVATE_TOKEN: ${RENOVATE_TOKEN}
RENOVATE_REPOSITORIES: your-user/your-homelab-repo
RENOVATE_ONBOARDING: "false"
RENOVATE_REQUIRE_CONFIG: "required"
volumes:
- ./cache:/tmp/renovate/cache
Add a .env file next to it:
RENOVATE_TOKEN=ghp_your_token_here
Lock it down:
chmod 600 .env
Then run it manually once:
docker compose run --rm renovate
If everything is wired correctly, Renovate will scan the repo and open pull requests for outdated images. The first run can be noisy if you have been living the latest life for a while. No judgment. I have been there.
Run it on a schedule
I do not keep Renovate running forever. I run it from cron once a day.
crontab -e
Add:
17 5 * * * cd /opt/renovate && docker compose run --rm renovate >> /var/log/renovate.log 2>&1
Why 05:17? Because every tutorial uses round numbers and I am weirdly superstitious about avoiding the top of the hour. Lots of scheduled jobs fire at 00. Pick a boring odd minute.
My review routine
When Renovate opens a PR, I do three quick checks before merging:
- Read the release notes if the app links them.
- Check for database migrations or backup warnings.
- Look at the version jump. Patch update? Usually fine. Major update? Slow down.
For tiny apps, I merge and deploy. For important apps, I take a backup first. If the app stores data in Postgres, I dump the database. If it uses a Docker volume, I snapshot or run my normal restic backup job.
This sounds slower than automatic updates because it is. That is the point.
Security updates still matter, though. If your server is reachable from random networks, keep your base OS patched, use a firewall, and do not treat Docker image updates as your only defense.
🚀NordVPN
Secure your server with a reliable VPN.
Affiliate link — we may earn a commission at no extra cost to you.
A few gotchas I hit
Renovate only updates what it can understand. If your image tag is hidden inside a custom script or templated in a strange way, Renovate may ignore it. Plain Compose files work best.
Some projects publish weird tags. You will see tags like stable, main, 2026.6.1, v2.4.0, or distro-specific variants. Renovate is good, not magic. Check the first few PRs carefully so you understand what it thinks an upgrade path looks like.
Private registries need extra configuration. If you pull from a private Docker registry, add host rules in Renovate instead of hardcoding credentials in ten places. Future you will appreciate not hunting secrets at midnight.
And one more thing: do not automerge everything just because Renovate can. Automerge patch updates for small stateless tools if you want. I would not automerge Vaultwarden, Immich, Nextcloud, or anything that owns data I care about.
What to do next
If you are still using latest everywhere, start with one stack. Pick something low-risk like Uptime Kuma or a dashboard. Pin the current version, add Renovate, and let it open PRs for a week.
Once you trust the workflow, move the important apps over.
My rule now is simple: if an update can break data, auth, or public access, it gets a pull request first. Watchtower had its place when my homelab was three containers and optimism. Renovate is what I use now that I want fewer surprise fires.
And fewer “why is this broken, I did nothing” mornings. Those are never as innocent as they sound.
Stay in the loop 📬
Get self-hosting tutorials, tool reviews, and infrastructure tips delivered to your inbox. No spam, unsubscribe anytime.
Join 0 self-hosters. Free forever.