AutomationFlowsWeb Scraping › Self Update Docker-based N8n with Email Approval and SSH

Self Update Docker-based N8n with Email Approval and SSH

ByMuhammad Anas Farooq @anasn-farooq on n8n.io

> An automated n8n workflow originally built for DigitalOcean-based n8n deployments, but fully compatible with any VPS or cloud hosting (e.g., AWS, Google Cloud, Hetzner, Linode, etc.) where n8n runs via Docker.

Cron / scheduled trigger★★★★☆ complexity27 nodesSshEmail SendHTTP Request
Web Scraping Trigger: Cron / scheduled Nodes: 27 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #10471 — we link there as the canonical source.

This workflow follows the Emailsend → HTTP Request recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "6486fadd-3584-4544-9a02-57ede53e836b",
      "name": "Sticky Note 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3456,
        -448
      ],
      "parameters": {
        "color": 4,
        "width": 280,
        "height": 288,
        "content": "## \ud83d\ude80 WORKFLOW START\n\n**Triggers:**\n- Manual execution (test runs)\n- Automated every 3 days at 4 PM UTC\n\nThis workflow checks for n8n updates and sends an approval email, to automatically update the n8n instance."
      },
      "typeVersion": 1
    },
    {
      "id": "683461fc-856d-41e8-9d49-9b725fa3a2fa",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -3120,
        -368
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "daysInterval": 3,
              "triggerAtHour": 16
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "ab507d4a-1a4c-49de-9b32-07f60969b0e7",
      "name": "If No Changes",
      "type": "n8n-nodes-base.if",
      "position": [
        -2000,
        -368
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "47b24202-69ea-481a-8848-67de206ea3c0",
              "operator": {
                "type": "boolean",
                "operation": "false",
                "singleValue": true
              },
              "leftValue": "={{ $json.update_available }}",
              "rightValue": "=Download complete"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "bcdcdc19-e21c-48fb-80bb-014144c6fa6a",
      "name": "Check Existence of Update Script",
      "type": "n8n-nodes-base.ssh",
      "position": [
        -1328,
        -384
      ],
      "parameters": {
        "cwd": "/root",
        "command": "=sh -c \"if [ -f update_docker.sh ]; then echo true; else echo false; fi\""
      },
      "credentials": {
        "sshPassword": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a9cbbad8-5f2f-411f-bc4a-62230e93c0e2",
      "name": "If File Exists",
      "type": "n8n-nodes-base.if",
      "position": [
        -1104,
        -384
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "1cf2c480-a309-46bc-a0c4-c2cf9827822d",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.stdout }}",
              "rightValue": "true"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "ba972070-a78a-4982-975f-7219f26571e3",
      "name": "Create Update Script",
      "type": "n8n-nodes-base.ssh",
      "position": [
        -880,
        -304
      ],
      "parameters": {
        "cwd": "/root",
        "command": "=sh -c \"printf '%s\\n' 'sleep 30s' 'cd /opt/n8n-docker-caddy' 'docker compose pull' 'docker compose down' 'docker compose up -d' > update_docker.sh; chmod +x update_docker.sh\""
      },
      "credentials": {
        "sshPassword": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "12f9fcab-8bcb-4bea-8e66-69f5227fb7bf",
      "name": "If Approved",
      "type": "n8n-nodes-base.if",
      "position": [
        -1552,
        -272
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "32c6b0f0-0747-47de-90e4-56ded46d3d34",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.data.approved }}",
              "rightValue": "true"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "f3a6c807-0f12-45b6-bed7-ce4cbda4e42b",
      "name": "No Updates",
      "type": "n8n-nodes-base.noOp",
      "position": [
        -1776,
        -464
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "3a90a6d9-b1b1-4f9b-8194-676dccbdb5a5",
      "name": "Do Nothing",
      "type": "n8n-nodes-base.noOp",
      "position": [
        -1328,
        -160
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "c1dbc70c-db44-4f5c-9854-64d34d9f114f",
      "name": "Ask For Approval to Update",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        -1776,
        -272
      ],
      "parameters": {
        "message": "=<h2>\ud83d\udd14 n8n Update Available</h2>\n\n<p>A new version of n8n is available on Docker Hub!</p>\n\n<table style=\"border-collapse: collapse; width: 100%; margin: 20px 0;\">\n  <tr style=\"background-color: #f8f9fa;\">\n    <td style=\"padding: 12px; border: 1px solid #dee2e6; font-weight: bold;\">Current Version:</td>\n    <td style=\"padding: 12px; border: 1px solid #dee2e6;\">{{ $json.current_version }}</td>\n  </tr>\n  <tr>\n    <td style=\"padding: 12px; border: 1px solid #dee2e6; font-weight: bold;\">New Version Available:</td>\n    <td style=\"padding: 12px; border: 1px solid #dee2e6; color: #28a745; font-weight: bold;\">Latest from Docker Hub</td>\n  </tr>\n  <tr style=\"background-color: #f8f9fa;\">\n    <td style=\"padding: 12px; border: 1px solid #dee2e6; font-weight: bold;\">Local Digest:</td>\n    <td style=\"padding: 12px; border: 1px solid #dee2e6; font-family: monospace; font-size: 11px;\">{{ $json.local_digest }}</td>\n  </tr>\n  <tr>\n    <td style=\"padding: 12px; border: 1px solid #dee2e6; font-weight: bold;\">Remote Digest:</td>\n    <td style=\"padding: 12px; border: 1px solid #dee2e6; font-family: monospace; font-size: 11px;\">{{ $json.remote_digest }}</td>\n  </tr>\n</table>\n\n<h3>\ud83d\udccb What happens if you approve?</h3>\n<ol>\n  <li>Pull the new n8n Docker image from Docker Hub</li>\n  <li>Restart the n8n container with the new version</li>\n</ol>\n\n<h3>\ud83d\udd27 Manual Update Option</h3>\n<p>If you prefer to update manually later, run these commands on your server:</p>\n<pre style=\"background-color: #f8f8f8; padding: 10px; border-radius: 4px; border: 1px solid #ddd;\">\ncd /opt/n8n-docker-caddy\ndocker compose pull\ndocker compose down\ndocker compose up -d\n</pre>\n\n<p><strong>Do you want to proceed with the automatic update?</strong></p>",
        "options": {
          "limitWaitTime": {
            "values": {
              "resumeAmount": 3
            }
          },
          "appendAttribution": false
        },
        "subject": "Approval Required for Updating n8n!",
        "toEmail": "user@example.com",
        "fromEmail": "user@example.com",
        "operation": "sendAndWait",
        "approvalOptions": {
          "values": {
            "approvalType": "double"
          }
        }
      },
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "0829b238-0c14-4be8-bbf1-edaa63016b51",
      "name": "Sticky Note - Auto Update",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -992,
        -112
      ],
      "parameters": {
        "color": 6,
        "width": 320,
        "height": 432,
        "content": "## \ud83d\udd27 AUTOMATED UPDATE PROCESS\n\n**Three-step update execution:**\n\n1. **Check Script**: Verify if update script exists\n2. **Create Script** (if needed): Generate update script with:\n   - 30-second delay\n   - Navigate to docker directory\n   - Execute update commands\n3. **Execute**: Run script in background via `nohup`\n\n**The 60-second delay ensures this workflow completes before container restarts!**"
      },
      "typeVersion": 1
    },
    {
      "id": "c68450b6-ff97-4be4-932f-42dbabdab811",
      "name": "Sticky Note - Script Logic",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1392,
        -672
      ],
      "parameters": {
        "color": 7,
        "width": 280,
        "height": 248,
        "content": "## \ud83c\udfaf SCRIPT LOGIC\n\n**Optimizes script creation:**\n\n- If script exists \u2192 Use it directly\n- If script missing \u2192 Create new one\n\nPrevents unnecessary file operations and ensures idempotency."
      },
      "typeVersion": 1
    },
    {
      "id": "277ab82a-701a-4edc-8def-1dab9165ebe2",
      "name": "Sticky Note - Final Execution",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -976,
        -752
      ],
      "parameters": {
        "color": 3,
        "width": 320,
        "height": 328,
        "content": "## \ud83d\ude80 FINAL EXECUTION\n\n**Runs in background:**\n\nExecutes `update_docker.sh` with:\n- `nohup`: Runs independently of this session\n- `> update.log`: Logs output for debugging\n- `2>&1`: Captures both stdout and stderr\n- `&`: Background execution\n\n**Workflow completes immediately, update happens 30 seconds later!**"
      },
      "typeVersion": 1
    },
    {
      "id": "5a44fe76-5ca3-4477-9f5d-486c8abec4ab",
      "name": "Execute Update Script",
      "type": "n8n-nodes-base.ssh",
      "position": [
        -656,
        -400
      ],
      "parameters": {
        "cwd": "/root",
        "command": "=sh -c \"exec >/root/update.log 2>&1; nohup /root/update_docker.sh &\""
      },
      "credentials": {
        "sshPassword": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "9a65a79e-eb2e-4ad2-8920-fe6a4a8053c6",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3536,
        -928
      ],
      "parameters": {
        "color": 5,
        "width": 3104,
        "height": 1520,
        "content": "# n8n-Self-Updater"
      },
      "typeVersion": 1
    },
    {
      "id": "9b195a5d-3057-4690-9a7f-63947349df7b",
      "name": "Get Local Image Digest",
      "type": "n8n-nodes-base.ssh",
      "position": [
        -2672,
        -368
      ],
      "parameters": {
        "cwd": "/root",
        "command": "sh -c \"docker inspect n8n-docker-caddy-n8n-1 --format='{{index .Image}}' | xargs docker inspect --format='{{index .RepoDigests 0}}'\""
      },
      "credentials": {
        "sshPassword": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "9f46efa6-bfa4-43cc-bc8b-50f4789bfc3f",
      "name": "Get Remote Image Digest",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2448,
        -368
      ],
      "parameters": {
        "url": "https://hub.docker.com/v2/repositories/n8nio/n8n/tags/latest",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "b06e54b7-3760-46b6-b35b-95f74ea19ada",
      "name": "Sticky Note 9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2224,
        -672
      ],
      "parameters": {
        "width": 320,
        "height": 264,
        "content": "## \ud83d\udd00 UPDATE CHECK\n\n**Checks if update exists:**\n\nCompares local vs remote digest:\n- If digests match \u2192 No update (top path)\n- If digests differ \u2192 Update available (bottom path)\n\n**Prevents unnecessary email notifications!**"
      },
      "typeVersion": 1
    },
    {
      "id": "ef630305-91e7-4d78-9a8d-a2729063ddb0",
      "name": "Prepare Update Data",
      "type": "n8n-nodes-base.set",
      "position": [
        -2224,
        -368
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "832662b6-8fe8-4849-870b-71d32bf88a46",
              "name": "current_version",
              "type": "string",
              "value": "={{ $('Get Current n8n Version').first().json.stdout }}"
            },
            {
              "id": "local-digest-assign",
              "name": "local_digest",
              "type": "string",
              "value": "={{ $('Get Local Image Digest').first().json.stdout.split('@')[1] }}"
            },
            {
              "id": "remote-digest-assign",
              "name": "remote_digest",
              "type": "string",
              "value": "={{ $('Get Remote Image Digest').first().json.digest }}"
            },
            {
              "id": "new-version-assign",
              "name": "update_available",
              "type": "boolean",
              "value": "={{ $('Get Local Image Digest').first().json.stdout.split('@')[1] !== $('Get Remote Image Digest').first().json.digest }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "71409d64-8391-4f08-8359-223728303c20",
      "name": "Sticky Note ",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1840,
        -736
      ],
      "parameters": {
        "color": 4,
        "width": 320,
        "height": 224,
        "content": "## \u26d4 NO UPDATES FOUND\n\nWorkflow ends when:\n- Local and remote digests match\n- Already running latest version\n\n**No unnecessary emails!**\n\nWorkflow completes silently."
      },
      "typeVersion": 1
    },
    {
      "id": "1c01f1f9-5c06-41fd-a130-44c43f9a5c11",
      "name": "Sticky Note 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2864,
        -656
      ],
      "parameters": {
        "color": 5,
        "width": 280,
        "height": 248,
        "content": "## \ud83d\udd0d GET CURRENT INFO\n\nRetrieves:\n1. Current n8n version\n2. Local image digest (RepoDigest)\n\nThis tells us what we're currently running."
      },
      "typeVersion": 1
    },
    {
      "id": "09ef5f83-d20d-489f-9199-aa1ce267427b",
      "name": "Sticky Note 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2528,
        -144
      ],
      "parameters": {
        "color": 6,
        "width": 280,
        "height": 236,
        "content": "## \ud83d\udd0e CHECK REMOTE DIGEST\n\nQueries Docker Hub registry to get:\n- Remote image digest for :latest tag\n- WITHOUT pulling the image!\n\nUses Docker Registry HTTP API v2."
      },
      "typeVersion": 1
    },
    {
      "id": "0aaada39-31ce-49b8-8526-b22264de5092",
      "name": "Get Current n8n Version",
      "type": "n8n-nodes-base.ssh",
      "position": [
        -2896,
        -368
      ],
      "parameters": {
        "cwd": "/root",
        "command": "=sh -c \"docker exec n8n-docker-caddy-n8n-1 n8n --version\""
      },
      "credentials": {
        "sshPassword": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "9982be9b-af55-4364-a29a-d70dca2af86e",
      "name": "Sticky Note 5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1888,
        -64
      ],
      "parameters": {
        "color": 3,
        "width": 320,
        "height": 232,
        "content": "## \ud83d\udce7 APPROVAL REQUEST\n\nSends email with:\n- Current version\n- Digest comparison\n- **Approve/Disapprove buttons**\n\nn8n stays exactly as-is until you click APPROVE!"
      },
      "typeVersion": 1
    },
    {
      "id": "4cf50320-6d4a-4af1-ac91-941f8f2c7851",
      "name": "Sticky Note - Declined1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1408,
        0
      ],
      "parameters": {
        "color": 4,
        "width": 280,
        "height": 248,
        "content": "## \ud83d\uded1 UPDATE DECLINED\n\nWorkflow ends when:\n- User clicks **Decline**\n- Update will wait for next check or manual execution\n\n**Manual option always available!**"
      },
      "typeVersion": 1
    },
    {
      "id": "69673ede-0abc-43ec-86ea-5ae72a6289d2",
      "name": "Sticky Note 6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4080,
        -160
      ],
      "parameters": {
        "color": 2,
        "width": 496,
        "height": 408,
        "content": "## \ud83d\udcca WORKFLOW SUMMARY\n\n**Flow:**\n1. Get current version + local digest\n2. Get remote digest from Docker Hub\n3. Compare digests\n4. If different \u2192 Request approval with version info\n5. If approved \u2192 Pull and restart\n6. If same \u2192 Silent skip\n\n**Benefits:**\n- Bandwidth efficient\n- Clear version comparison\n- Manual approval control\n- No spam when no updates"
      },
      "typeVersion": 1
    },
    {
      "id": "b1613e0c-a14f-4903-96c3-dedd46423a25",
      "name": "Setup Requirements",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4080,
        -928
      ],
      "parameters": {
        "width": 500,
        "height": 704,
        "content": "## \ud83e\udde9 SETUP REQUIREMENTS\n\n**1\ufe0f\u20e3 SSH Credentials**\n- Go to *Credentials \u2192 New \u2192 SSH Password*\n- Enter:\n  - Host: your VPS IP (e.g., 165.22.xx.xx)\n  - Port: 22\n  - Username: root / ubuntu\n  - Password or Private Key\n\n\n**2\ufe0f\u20e3 SMTP Credentials (Email)**\n- Go to *Credentials \u2192 New \u2192 SMTP*\n- Fill in:\n  - Host: smtp.yourprovider.com\n  - Port: 465 (SSL) / 587 (TLS)\n  - User: your email\n  - Password: email password or app password\n\n\n**3\ufe0f\u20e3 Docker Server Setup**\n- Ensure Docker + docker-compose are installed\n- n8n is deployed via docker-compose (e.g., /opt/n8n-docker-caddy)\n\n\n**4\ufe0f\u20e3 Workflow Setup**\n- Import the JSON workflow\n- Edit Email Node \u2192 update `fromEmail` & `toEmail`\n- Connect correct SSH + SMTP credentials\n- Enable Schedule Trigger (optional)\n\n\u2705 Once complete, test manually before activating auto checks."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "If Approved": {
      "main": [
        [
          {
            "node": "Check Existence of Update Script",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Do Nothing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If No Changes": {
      "main": [
        [
          {
            "node": "No Updates",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Ask For Approval to Update",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If File Exists": {
      "main": [
        [
          {
            "node": "Execute Update Script",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create Update Script",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Get Current n8n Version",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Update Data": {
      "main": [
        [
          {
            "node": "If No Changes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Update Script": {
      "main": [
        [
          {
            "node": "Execute Update Script",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Local Image Digest": {
      "main": [
        [
          {
            "node": "Get Remote Image Digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Current n8n Version": {
      "main": [
        [
          {
            "node": "Get Local Image Digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Remote Image Digest": {
      "main": [
        [
          {
            "node": "Prepare Update Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ask For Approval to Update": {
      "main": [
        [
          {
            "node": "If Approved",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Existence of Update Script": {
      "main": [
        [
          {
            "node": "If File Exists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

&gt; An automated n8n workflow originally built for DigitalOcean-based n8n deployments, but fully compatible with any VPS or cloud hosting (e.g., AWS, Google Cloud, Hetzner, Linode, etc.) where n8n runs via Docker.

Source: https://n8n.io/workflows/10471/ — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

N8N-Self-Updater. Uses ssh, emailSend, httpRequest. Scheduled trigger; 27 nodes.

Ssh, Email Send, HTTP Request
Web Scraping

This template is designed to automatically clean up old image tags in the Docker registry and perform garbage collection. List all images in the registry Preserve the last 10 tags for each image (late

HTTP Request, Email Send, Ssh
Web Scraping

This workflow is an improvement of this workflow by Greg Brzezinka.

HTTP Request, Email Send, XML +1
Web Scraping

What if you could spot a major sales problem—or a winning campaign—the very next morning, instead of weeks later? Imagine receiving a beautiful, data-rich alert directly in your inbox the moment your

QuickBooks, HTTP Request, Email Send
Web Scraping

Track Changes Of Product Prices. Uses htmlExtract, functionItem, httpRequest, writeBinaryFile. Scheduled trigger; 25 nodes.

Html Extract, Function Item, HTTP Request +5