orvintax schreef op dinsdag 1 april 2025 @ 14:57:
[...]
Interessante post om te lezen. Coole setup! Ik was in eerste instantie wel verward hoe je Watchtower had "vervangen" door Renovate. Maar als ik het goed begrijp kickt je pipeline een Ansible playbook af die de daadwerkelijke update doet op je Docker host. Dus het is meer de samenwerking tussen Renovate en Ansible die dan het stukje Watchtower vervangt.
Misschien omdat ik wat teveel in de materie zit, dat ik wellicht over wat details heen ben gevlogen

. Eerder controleerde Watchtower (bij mij) iedere dag of er updates voor containers waren. Ik had per Docker Compose stack een aparte Watchtower container draaien zodat dit proces nog enigszins beheersbaar was. Echter gaf het me totaal geen inzicht welke versie nu waar draaide.
Tegelijkertijd beheer(de) ik mijn Docker Compose stacks met
Dockge. De Docker Compose stacks kopieerde ik bij aanpassingen handmatig in Dockge. De eerste stap die ik heb gemaakt is om deze met Ansible automatisch over te zetten. Ik gebruikte hiervoor de onderstaande Ansible playbook:
YAML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| - name: Deploy Docker Compose
hosts: docker
tasks:
- name: Ensure remote directories
ansible.builtin.file:
path: "/opt/appdata/dockge/stacks/{{ item.path }}"
state: directory
mode: "0755"
owner: "nobody"
group: "nogroup"
with_community.general.filetree: "{{ inventory_dir }}/compose/{{ inventory_hostname }}/"
when: item.state == 'directory'
- name: Copy regular files
ansible.builtin.copy:
src: "{{ item.src }}"
dest: "/opt/appdata/dockge/stacks/{{ item.path }}"
mode: "0644"
owner: "nobody"
group: "nogroup"
with_community.general.filetree: "{{ inventory_dir }}/compose/{{ inventory_hostname }}/"
when: item.state == 'file' and (item.src | splitext | last) != '.j2'
- name: Template Jinja .j2 files
ansible.builtin.template:
src: "{{ item.src }}"
dest: "/opt/appdata/dockge/stacks/{{ item.path | splitext | first }}"
mode: "0644"
owner: "nobody"
group: "nogroup"
with_community.general.filetree: "{{ inventory_dir }}/compose/{{ inventory_hostname }}/"
when: item.state == 'file' and (item.src | splitext | last) == '.j2' |
Voor dit voorbeeld en de leesbaarheid heb ik zoveel mogelijk Ansible-eigen constructies eruit gehaald, maar er zijn slimmere manieren om bijvoorbeeld paden aan elkaar te plakken.
De directory layout die ik dan gebruikte was:
┌── compose
│ └── hostnameA/
│ └── adguard_home/
│ └── compose.yaml
│ └── home_assistant/
│ └── compose.yaml
│ └── .env
│ └── hostnameB/
│ └── adguard_home/
│ └── compose.yaml
│ └── zigbee2mqtt/
│ └── compose.yaml
│ └── .env.j2
Dat was voor mij al een enorme verbetering omdat ik hiermee in ieder geval de Compose bestanden niet meer handmatig - en dus foutgevoelig - in hoefde te voeren. Je zou zelfs de onderste stap weg kunnen laten en alleen kopiëren. Ook dat verminderd de complexiteit aanzienlijk.
Je zou dit ook met Forgejo, Gitea of GitLab CI en zoiets als scp kunnen doen. Ik heb vooral Ansible gekozen vanwege het Jinja templaten. En vanaf hier was Renovate eigenlijk een paar kleine stappen.
1. Als eerste heb ik een Renovate user aangemaakt genaamd
renovate-bot met hetzelfde e-mailadres als in de configuratie hieronder. Je mag dit zelf kiezen

.
2. Voor deze user heb ik een repo aangemaakt genaamd
renovate-config.
In die repo zit een secret genaamd
RENOVATE_TOKEN en dat is een
Personal Access Token met de permissies conform de
Renovate documentatie.
3. Ik heb een standaard Renovate config aangemaakt genaamd
config.js met uiteraard de juiste gegevens / parameters.
JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
| module.exports = {
platform: 'gitea',
endpoint: 'https://git.example.com/api/v1/',
gitAuthor: 'Renovate Bot <renovate@users.noreply.github.com>',
username: 'renovate-bot',
autodiscover: true,
onboardingConfig: {
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
extends: ['config:recommended']
},
optimizeForDisabled: true,
persistRepoData: true,
} |
4. Vervolgens heb ik een workflow aangemaakt in
.forgejo/workflows in dezelfde repository met een hele basic workflow:
YAML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| ---
name: Renovate
# yamllint disable-line rule:truthy
on:
schedule:
- cron: "@daily"
workflow_dispatch:
jobs:
renovate:
runs-on: ubuntu-latest
container: ghcr.io/renovatebot/renovate:39.229.0
steps:
- uses: actions/checkout@v4
- run: renovate
env:
RENOVATE_CONFIG_FILE: "config.js"
LOG_LEVEL: "debug"
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }} |
5. En als laatste stap heb ik de renovate-bot toegevoegd aan de repositories waarin ik Renovate wil hebben.
Daarna kon ik een run starten en werkte het... Natuurlijk pakt Renovate geen latest op en moet je zelf eerst de juiste versies intikken. En daarnaast heb ik uiteraard mijn configuratie verder uitgebreid met bijvoorbeeld een
dependency dashboard en custom matchers, maar dat hoeft allemaal niet. Daar ben ik ook pas na een maand of anderhalf mee gestart. In ieder geval was ik nu wel af van alle
:latest-tags, Watchtower containers
(laatste update meer dan 1 jaar geleden!) en had ik transparantie van updates.
Ik ben zelf tegenwoordig wel meer van het eenvoudig te houden. Dus ik laat Renovate lekker gaan, Gitlab CI/CD publisht de image dan naar een container registry. Vervolgens draait Watchtower om de zoveel uur om de update binnen te trekken. Good ol' polling.
Ieder z'n ding

. Ik zeg ook niet dat mijn oplossing het einddoel is, maar ik vind het zo verdomd makkelijk werken. En zoals je misschien merkt heb ik me echt verbaasd over de enorm eenvoudige setup.