--- title: Docker Auto-Updates url: https://blog.guigpap.com/en/workflows/docker-updates/ url_md: https://blog.guigpap.com/en/workflows/docker-updates.md category: automation date: '2026-01-20' maturite: production techno: - docker - n8n - telegram application: - automation - operations --- # Docker Auto-Updates > Automatic Docker image updates via DIUN, refactored into a 36-node hub + 3 sub-workflows with version tracking ## 1. What? — Definition and context The **Docker Auto-Updates** workflow automates Docker image updates on the VPS. DIUN detects new versions, the N8N hub classifies each image (critical / base / app), and a sub-workflow runs the full cycle: backup, pull/build, health check, rollback if needed, notification. > **Note - DIUN** > > **DIUN** (Docker Image Update Notifier) compares running image digests with those available on Docker registries. It does not download the full image — only the digest is compared, which makes a 6-hourly check fast and cheap. ### Refactored architecture (#275) | Workflow | ID | Nodes | Role | |----------|------|-------|------| | **Docker DIUN** (parent) | `WdSepRkceMzI0QQ5` | 36 | Triggers, per-category routing, classification | | **DIUN Update Executor** (SW-1) | `VSFJv2DbdkvQ2cl1` | 25 | Shared lifecycle: backup, update, health check, rollback, notify | | **DIUN Queue Processor** (SW-2) | `Eb3QIk8DnEQIXeSl` | 6 | 03h-05h queue processing loop | | **DIUN Approval Handler** (SW-3) | `JLsR7JSMAQDwzqEX` | 20 | Classification, approval, rejection, deferred Telegram report | Before #275, this workflow had 101 monolithic nodes. Splitting into hub + 3 sub-workflows lets each cycle be tested in isolation and lets the DIUN Update Executor be reused from other triggers (manual rebuild, file provider). ### Architecture diagram ```mermaid flowchart TD subgraph Triggers["Triggers"] direction TB DIUNHook["DIUN webhook · 6h scan"] FileProvider["File Provider · base images"] Cron3h["Cron 03h · queue processor"] Manual["Manual rebuild"] end subgraph Hub["Docker DIUN · 36 nodes"] direction TB Receive["Webhook"] Lookup["Lookup image_policies"] Classify["Classify · critical / base / app"] end subgraph SW3["SW-3 Approval Handler · 20 nodes"] direction TB Notify["Notification Hub · approval"] Wait["Wait for callback · approve/reject/later"] Decision["Switch decision"] end subgraph SW1["SW-1 Update Executor · 25 nodes"] direction TB Backup["Backup if needed"] Pull["Pull or Build"] Up["compose up -d"] Health["Health check 2 min"] Rollback["Rollback if KO"] NotifyOK["Notification Hub · success/failure"] end subgraph SW2["SW-2 Queue Processor · 6 nodes"] direction TB LoopQueue["Loop pending_updates"] CallExec["Call Update Executor"] end Triggers --> Receive --> Lookup --> Classify Classify -->|critical| SW1 Classify -->|base| Queue["Insert pending_updates"] Classify -->|app| SW3 Queue -.->|03h-05h| Cron3h Cron3h --> SW2 --> CallExec --> SW1 SW3 -->|approved| SW1 SW3 -->|rejected/later| Notify SW1 --> NotifyOK ``` ### Image categories | Category | Behaviour | Examples | |----------|-----------|----------| | **critical** | Immediate update + health check | caddy, crowdsec, security-stack | | **base** | Queued for the 03h-05h window | postgres, redis, prometheus | | **app** | Admin approval required via Telegram | n8n, odoo, grafana, ai-stack | --- ## 2. Why? — Stakes and motivations ### Problems solved | Problem | Without this workflow | With this workflow | |---------|-----------------------|--------------------| | **Outdated images** | Unpatched vulnerabilities | Policy-driven automatic updates | | **User downtime** | Updates during business hours | Nightly maintenance window | | **Risky updates** | No prior validation | Approval for critical apps | | **Manual rollback** | Late intervention | Health check + automatic rollback | | **No version visibility** | "Something changed" | Before/after version tracking in the notification | ### Why three categories? | Category | Justification | |----------|---------------| | **critical** | Security priority, immediate update (Caddy = entry point) | | **base** | Stable infrastructure, nightly update to minimise impact | | **app** | Business-critical, human validation required | > **Tip - Maintenance window** > > "Base" images are updated between 3am and 5am to minimise impact. The Queue Processor workflow handles the `pending_updates` table in that window, sequentially, to avoid saturating the VPS. ### Why a shared Update Executor sub-workflow? The update cycle (backup → pull/build → up → health check → rollback / notify) is identical regardless of trigger. Extracting it into SW-1 enables: | Benefit | Detail | |---------|--------| | **Reuse** | Same guarantees for DIUN, file provider, manual rebuild | | **Isolated tests** | Mockable independently from the trigger | | **Maintenance** | Single place to change the health-check strategy | --- ## 3. How? — Technical implementation ### Data Tables #### `image_policies` — Per-image policy | Column | Type | Description | |--------|------|-------------| | `image_key` | Text | Unique key: `project/service` | | `category` | Text | critical / base / app | | `backup` | Boolean | Backup required before update | | `custom_build` | Boolean | Custom image (build vs pull) | | `github_repo` | Text | owner/repo for changelog | | `compose_dir` | Text | Absolute path of the docker-compose | #### `pending_updates` — 03h-05h queue | Column | Type | Description | |--------|------|-------------| | `image_key` | Text | Unique key | | `image` | Text | Full image name | | `project` | Text | Project name | | `service` | Text | Service name | | `custom_build` | Boolean | Build instead of pull | | `created_at` | Text | ISO timestamp | #### `pending_updates_approvals` — Pending approvals App approvals waiting for a Telegram answer. TTL 7d; beyond that, the approval is automatically re-requested at the next scan. ### Generated Docker commands ```bash # Standard image docker compose -f /path/to/stack/docker-compose.yaml pull docker compose -f /path/to/stack/docker-compose.yaml up -d # Custom image (custom_build = true) docker compose -f /path/to/stack/docker-compose.yaml pull --ignore-buildable docker compose -f /path/to/stack/docker-compose.yaml build --no-cache docker compose -f /path/to/stack/docker-compose.yaml up -d ``` ### Approval flow (app images) ```mermaid flowchart TD Detect["App image detected"] Notif["Notification Hub · approval_request"] Buttons["Inline buttons · Approve / Reject / Later"] CB["NH Callback Handler"] ApprovalSW["SW-3 Approval Handler"] Decision["Switch decision"] Approve["approved"] Reject["rejected"] Later["later → pending_updates"] Run["SW-1 Update Executor"] Skip["Notify · Skipped"] Queue["Queue 03h-05h"] Detect --> Notif --> Buttons Buttons --> CB --> ApprovalSW --> Decision Decision --> Approve --> Run Decision --> Reject --> Skip Decision --> Later --> Queue ``` ### Post-update health check After every update, SW-1 verifies for 2 minutes that containers are healthy: ```bash docker compose -f /path/to/stack/docker-compose.yaml ps --format json ``` If a container stays `unhealthy` after 120 s, a rollback is triggered: `docker compose pull ` then `up -d`. A critical notification is sent to the Hub with the diagnostic details. ### Self-update Docker (n8n-stack) Updating **N8N itself** is tricky: the update workflow runs in the container it updates. The self-restart pattern in use: | Step | Action | |------|--------| | 1 | Insert maintenance flag in `error_handling_config` (suppress notifications) | | 2 | Trigger an external script via `nohup` that waits 10s then `docker compose up -d --force-recreate` | | 3 | The N8N workflow stops (the container restarts) | | 4 | On restart, a cleanup workflow removes the maintenance flag and notifies success | > **Caution - Why `nohup`** > > Without `nohup`, `compose up -d` would be killed when the N8N container stops, leaving the service in a partial state. `nohup` detaches the process from the container, allowing a clean restart. ### Version tracking Every update captures before/after versions for traceability. The format uses SSH markers to extract versions from the Dockerfile or `image:tag`: ``` n8n-custom:latest 2.4.8 → 2.5.0 caddy-crowdsec:latest 2.8.4 → 2.8.5 ``` Telegram notifications display the version transition, so the nature of the change (patch, minor, major) is visible at a glance. ### File Provider Auto-Rebuild When a **base image** changes (`node:20-slim`, `caddy:builder`...), custom images that depend on it must be rebuilt. DIUN watches those images via its `file` provider (reading `base-images.yml`), and the workflow detects the dependency. | Base image | Custom service | Category | |------------|----------------|----------| | `caddy:builder`, `caddy:latest` | security-stack/caddy | critical (immediate rebuild) | | `n8nio/n8n:latest` | n8n-stack/n8n | app (approval required) | | `node:20-slim`, `ghcr.io/astral-sh/uv` | ai-stack/cli-ollama | app (approval required) | > **Caution - N8N rebuild** > > An N8N rebuild also rebuilds the 5 workers. The workflow uses the self-restart pattern with a maintenance flag in `error_handling_config` to avoid false alerts from the Global Error Handler during the restart. ### Incident Response (AI-Assisted) When Prometheus Alertmanager signals a problem (container down, CPU spike, disk full), an Incident Response workflow triggers a Claude diagnostic: | Severity | Behaviour | |----------|-----------| | **TRIVIAL** | Auto-remediation (restart) + health check + notification | | **MODERATE** | Claude proposes + applies the fix, monitoring for 5 min | | **COMPLEX** | Claude produces a plan, waits for human approval (30-min timeout) | For COMPLEX cases, Claude produces a detailed plan sent over Telegram with `[Execute]` `[Edit]` `[Ignore]` buttons. Same Plan Engine pattern as the Conversational system. ### Useful commands ```bash # Force a DIUN check docker exec diun diun --test # DIUN logs docker logs diun --tail 50 # View configured policies # N8N → Data → Tables → image_policies (26 current entries) # View pending updates # N8N → Data → Tables → pending_updates ``` --- ## 4. What if? — Outlook and limits ### Current limits | Limit | Impact | Mitigation | |-------|--------|------------| | **No automated tests** | Risk of undetected regressions | Basic health check only | | **Manual rollback possible** | If rollback fails, intervention required | Immediate notification with context | | **DIUN dependency** | No detection if DIUN is down | Container monitoring via Health Check | | **No canary** | Direct update, no progressive rollout | Acceptable on a single-VPS setup | ### Evolution scenarios **If an update causes a regression**: - Health check detects the problem - Automatic rollback to the previous image - Notification with details for investigation **If updates are too frequent**: - Tune the DIUN polling (currently 6h) - Create a "stable" category with monthly updates - Filter by semantic versioning (major only) **If deeper testing is needed**: - Post-update smoke tests via the `monitoring-stack` - Observation delay before success notification - Dedicated staging environment for prior tests (extra VPS cost) **If a tighter CVE coupling is wanted**: - Reject an update that would introduce a new uncorrected CRITICAL - Use the [Security CVE Watch](/en/workflows/security-cve-watch/) feed as gating --- ## Related pages ### Infrastructure - [VPS Architecture](/en/infrastructure/architecture-vps/) — Big picture - [Notify Stack](/en/infrastructure/notify-stack/) — DIUN that triggers updates - [Security Stack](/en/infrastructure/security-stack/) — Caddy protected against accidental restart ### Workflows - [Telegram Orchestrator](/en/workflows/telegram-orchestrator/) — Receives approvals - [Notification Hub](/en/workflows/notification-hub/) — Success/failure routing - [Security CVE Watch](/en/workflows/security-cve-watch/) — CVE scan coupled to pre-update gating - [Error Handler](/en/workflows/error-handler/) — DIUN error capture ### Reference - [Glossary](/en/reference/glossary/) — DIUN, Health Check, Self-restart, File Provider ## Metadonnees agent - Cet article est issu du blog GuiGPaP Lab. - Contexte global du blog: https://blog.guigpap.com/llms.txt - Contact auteur: https://odoo.guigpap.com/mon-cv - Licence: CC-BY-SA 4.0