Skip to content

RFC: Automated Docker Image Updates

Status: Draft Date: 2026-04-27

Problem

With 54 Docker images across 35 services (now all pinned per ADR-017), manually discovering and applying version updates is unsustainable. Updates fall behind, security patches go unnoticed, and there's no structured process for testing before deploying.

Requirements

  1. Detect new upstream image versions automatically
  2. Present changes for human review before anything deploys
  3. Approve/reject from mobile (ntfy notification with action buttons)
  4. Full audit trail of every version change in git
  5. Safety controls: stability delay, major version locks, grouped updates for related images
  6. Fit the IaC pattern: everything through Forgejo Actions, Vault, Caddy, Alloy

Nice-to-haves

  • Dashboard widget showing pending updates (Glance)
  • Changelog summary in notifications
  • Automatic image mirroring to Forgejo registry before deploy
  • Open WebUI conversational interface for querying update status

Architecture

Components

All components already exist in the homelab except Renovate:

Component Role Status
Renovate Scans docker-compose files, creates PRs with version bumps New — needs deployment
Forgejo Hosts PRs, triggers workflows on merge Existing (LXC 100)
n8n Orchestrates approval flow: receives webhooks, sends notifications, merges PRs Existing (LXC 120)
ntfy Push notifications with approve/reject action buttons Existing (LXC 119)
Glance Dashboard widget showing pending Renovate PRs Existing (LXC 119)
Forgejo Actions Deploys on merge via Ansible playbooks Existing (LXC 101)

Flow

Renovate (hourly cron)
    │
    ▼  scans docker-compose files in chizuru-v2
    │  detects: immich v2.7.5 → v2.8.0 available
    │  waits 3 days (stabilityDays)
    │
    ▼  creates PR in Forgejo
    │  title: "chore(deps): update immich to v2.8.0"
    │  body: version diff, release notes link
    │
    ▼  Forgejo webhook → n8n workflow
    │
    ▼  n8n sends ntfy notification:
    │  ┌──────────────────────────────────┐
    │  │  Update: immich v2.7.5 → v2.8.0 │
    │  │  2 images, 1 compose file        │
    │  │                                  │
    │  │  [Approve] [Reject] [View PR]    │
    │  └──────────────────────────────────┘
    │
    ▼  user taps "Approve"
    │
    ▼  ntfy POSTs to n8n approval webhook
    │
    ▼  n8n merges PR via Forgejo API
    │  POST /api/v1/repos/holo/chizuru-v2/pulls/{id}/merge
    │
    ▼  merge triggers Forgejo Actions workflow
    │  (paths: services/immich/**)
    │
    ▼  workflow deploys via Ansible
    │
    ▼  n8n sends confirmation ntfy:
       "immich v2.8.0 deployed successfully"

Renovate Configuration

Deployment

Run Renovate as a scheduled Forgejo Actions workflow in a dedicated repo (e.g., holo/renovate-config). No new LXC needed.

# .forgejo/workflows/renovate.yml
name: Renovate
on:
  schedule:
    - cron: '0 */6 * * *'  # every 6 hours
  workflow_dispatch:

jobs:
  renovate:
    runs-on: ubuntu-latest
    steps:
      - name: Run Renovate
        uses: renovatebot/[email protected]
        env:
          RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
        with:
          configurationFile: config.js

Global Config (config.js)

module.exports = {
  platform: 'forgejo',
  endpoint: 'https://git.eva-00.network/api/v1/',
  token: process.env.RENOVATE_TOKEN,
  gitAuthor: 'Renovate Bot <[email protected]>',
  repositories: ['holo/chizuru-v2'],
  automerge: false,
  platformAutomerge: false,
  labels: ['renovate'],
  packageRules: [
    // Wait 3 days before proposing any update
    {
      matchDatasources: ['docker'],
      stabilityDays: 3
    },
    // Lock major versions for critical infra
    {
      matchPackageNames: [
        'netbirdio/management', 'netbirdio/signal', 'netbirdio/relay',
        'qmcgaw/gluetun', 'postgres'
      ],
      matchUpdateTypes: ['major'],
      enabled: false
    },
    // Group Netbird images into one PR
    {
      groupName: 'netbird',
      matchPackagePatterns: ['^netbirdio/', '^coturn/']
    },
    // Group Immich images into one PR
    {
      groupName: 'immich',
      matchPackagePatterns: ['^ghcr.io/immich-app/']
    },
    // Group Gluetun + qBittorrent (dlbox)
    {
      groupName: 'dlbox-vpn',
      matchPackageNames: ['qmcgaw/gluetun', 'lscr.io/linuxserver/qbittorrent'],
      matchFileNames: ['services/gluetun/**']
    },
    // Group Tube Archivist stack
    {
      groupName: 'tube-archivist',
      matchPackagePatterns: ['^bbilly1/tubearchivist', '^redis/redis-stack']
    }
  ]
};

Repository Config (renovate.json in chizuru-v2)

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:recommended"]
}

n8n Approval Workflow

Trigger: Forgejo PR webhook

Forgejo sends a webhook on PR create/update to n8n.

n8n workflow steps:

  1. Receive webhook — filter for PRs with renovate label
  2. Extract details — PR title, body, changed files, URL
  3. Send ntfy notification with action buttons:
    Title: {{ pr.title }}
    Body: {{ pr.body | truncate(200) }}
    Actions:
      http, Approve, https://n8n.eva-00.network/webhook/renovate-approve,
        method=POST, headers.Authorization=Bearer {{ token }},
        body={"pr_number": {{ pr.number }}, "repo": "{{ pr.repo }}"}
      http, Reject, https://n8n.eva-00.network/webhook/renovate-reject,
        method=POST, headers.Authorization=Bearer {{ token }},
        body={"pr_number": {{ pr.number }}}
      view, View PR, {{ pr.url }}
    
  4. Wait for approval webhook (n8n Wait node)
  5. On approve: merge PR via Forgejo API
  6. On reject: close PR via Forgejo API, add comment
  7. Send confirmation ntfy notification with result

Glance Dashboard Widget

Add a custom-api widget to Glance showing pending Renovate PRs:

- type: custom-api
  title: Pending Updates
  cache: 5m
  url: https://git.eva-00.network/api/v1/repos/holo/chizuru-v2/pulls?state=open&labels=renovate&token={FORGEJO_TOKEN}
  template: |
    {{ range .JSON.Array }}
      {{ .title }} — {{ .created_at | parseTime "2006-01-02T15:04:05Z07:00" | toRelativeTime }}
    {{ end }}

Open WebUI Integration (Future)

Once the core flow is working, add a Forgejo tool to Open WebUI that allows conversational queries:

  • "What Renovate PRs are pending?"
  • "Approve the Jellyfin update"
  • "Show me the changelog for the Immich update"

This leverages the existing Forgejo MCP server or a custom Open WebUI tool calling the Forgejo API directly. Non-blocking for the initial deployment.


Rollout Plan

Phase 1: Renovate (Week 1)

  1. Create renovate bot user on Forgejo with repo read/write permissions
  2. Store PAT in Vault at secret/renovate
  3. Add renovate.json to chizuru-v2
  4. Create holo/renovate-config repo with workflow + config.js
  5. Deploy and verify PRs are created correctly

Phase 2: n8n + ntfy approval flow (Week 2)

  1. Build n8n workflow: webhook receiver → ntfy notification → wait → merge/close
  2. Configure Forgejo webhook for PR events → n8n
  3. Test end-to-end: Renovate PR → ntfy notification → tap approve → PR merged

Phase 3: Glance + polish (Week 3)

  1. Add Glance custom-api widget for pending PRs
  2. Add ntfy confirmation on successful deploy
  3. Monitor for false positives, tune packageRules

Phase 4: Open WebUI + registry mirror (Future)

  1. Open WebUI tool for conversational PR management
  2. Skopeo mirror step in deploy workflow (pull from Forgejo registry, not upstream)

Alternatives Considered

Approach Why not
Diun + manual Too many services (54 images) for manual management
Watchtower Bypasses IaC, auto-updates without review
Dependabot No Forgejo support
FluxCD Image Automation Requires Kubernetes
Open WebUI as approval UI Chat interface, no push notifications or structured approve/reject flow

Risks

  • Renovate resource usage — runs periodically, bursty CPU/memory. Mitigated by running as a Forgejo Actions job (no persistent container).
  • Too many PRs — 54 images could generate noise. Mitigated by stabilityDays: 3, grouped updates, and locked major versions.
  • Accidental approval — fat-fingering approve on ntfy. Mitigated by n8n workflow requiring explicit PR merge via API (not just a notification dismissal).
  • Forgejo API rate limits — Renovate scans may hit limits. Monitor and adjust cron frequency if needed.