Loading...
Eine sich selbst aktualisierende n8n Instanz mit n8n Workflows

Eine sich selbst aktualisierende n8n Instanz mit n8n Workflows

Jonas Scholz - Co-Founder von sliplane.ioJonas Scholz
15 min

Selbst n8n zu hosten ist wirklich fantastisch. Du hast die vollständige Kontrolle über Deine Daten, Deine Workflows und Deine Integrationen. Aber weißt Du, was echt nervig ist? Deine n8n-Instanz jedes Mal zu aktualisieren, wenn eine neue Version veröffentlicht wird, genau dann, wenn Du eigentlich etwas erledigen willst. Okay, ich bin hier ein bisschen dramatisch, aber ich möchte meine Software nicht manuell aktualisieren - wir sind schließlich nicht mehr im Jahr 2005!

In diesem Blogpost zeige ich Dir, wie Du den Aktualisierungsprozess Deiner n8n-Instanz mit einem n8n-Workflow automatisieren kannst! Ich schlage vor, dass Du Dir das Video unten ansiehst und dann zurückkommst, um den Workflow/Code zu kopieren:

Der Workflow

Der grundlegende Ablauf des Workflows ist:

  1. Prüfen der aktuellen n8n-Version
  2. Prüfen der neuesten n8n-Version auf Docker Hub
  3. Vergleich der beiden Versionen
  4. Wenn es eine neue Version gibt, eine neue Bereitstellung auslösen

Das sieht ungefähr so aus:

n8n

Lass uns den Workflow aufschlüsseln:

Schritt 1. Prüfen der aktuellen Version

n8n hat einen öffentlichen API-Endpunkt unter /metrics, der Metriken (wer hätte das gedacht?) über die Instanz zurückgibt. Du musst die API zuerst in den Einstellungen aktivieren. Das machst Du, indem Du die Umgebungsvariable N8N_METRICS auf true setzt. Eine Anfrage an den /metrics-Endpunkt gibt eine prometheus-formatierte Antwort zurück, die in etwa so aussieht:

# HELP process_cpu_user_seconds_total Total user CPU time spent in seconds.
# TYPE process_cpu_user_seconds_total counter
n8n_process_cpu_user_seconds_total 28.518922

# HELP nodejs_heap_space_size_total_bytes Process heap space size total from Node.js in bytes.
# TYPE nodejs_heap_space_size_total_bytes gauge
n8n_nodejs_heap_space_size_total_bytes{space="read_only"} 0
n8n_nodejs_heap_space_size_total_bytes{space="new"} 1048576
n8n_nodejs_heap_space_size_total_bytes{space="old"} 122646528
n8n_nodejs_heap_space_size_total_bytes{space="code"} 6553600
n8n_nodejs_heap_space_size_total_bytes{space="shared"} 0
n8n_nodejs_heap_space_size_total_bytes{space="new_large_object"} 0
n8n_nodejs_heap_space_size_total_bytes{space="large_object"} 7479296
n8n_nodejs_heap_space_size_total_bytes{space="code_large_object"} 155648
n8n_nodejs_heap_space_size_total_bytes{space="shared_large_object"} 0

# HELP nodejs_heap_space_size_used_bytes Process heap space size used from Node.js in bytes.
# TYPE nodejs_heap_space_size_used_bytes gauge
n8n_nodejs_heap_space_size_used_bytes{space="read_only"} 0
n8n_nodejs_heap_space_size_used_bytes{space="new"} 194144
n8n_nodejs_heap_space_size_used_bytes{space="old"} 112432088
n8n_nodejs_heap_space_size_used_bytes{space="code"} 5740432
n8n_nodejs_heap_space_size_used_bytes{space="shared"} 0

# HELP n8n_version_info n8n version info.
# TYPE n8n_version_info gauge
n8n_version_info{version="v1.88.0",major="1",minor="88",patch="0"} 1

# HELP n8n_active_workflow_count Total number of active workflows.
# TYPE n8n_active_workflow_count gauge
n8n_active_workflow_count 1

Ich habe die meisten Metriken entfernt, aber die wichtige Zeile ist:

n8n_version_info{version="v1.88.0",major="1",minor="88",patch="0"} 1

Diese Daten erhalten wir, indem wir einen HTTP-Request-Node verwenden und dann die Antwort mit einem JavaScript-Code-Node parsen.

Der Code, um die Antwort zu parsen, lautet:

const match = $input.first().json['data'].match(/n8n_version_info\{[^}]*version="(v[\d.]+)"/);
const version = match ? match[1] : '';

$input.first().json.version = version.slice(1);

return $input.all();

Wenn Du eine Erklärung für den Code brauchst, würde ich empfehlen, ihn an ChatGPT zu übergeben :)

Schritt 2: Prüfen der neuesten Version

Jetzt, da wir die aktuelle Version haben, können wir die neueste Version auf Docker Hub prüfen.

Dazu nutzen wir die öffentliche API von Docker Hub, die unter https://hub.docker.com/v2/repositories/n8nio/n8n/tags?ordering=last_updated erreichbar ist.

Die Ausgabe sieht in etwa so aus:

{
  "count": 2013,
  "next": "https://hub.docker.com/v2/repositories/n8nio/n8n/tags?ordering=last_updated&page=2&page_size=10",
  "previous": null,
  "results": [
    {
      "creator": 6760745,
      "id": 877879608,
      "images": [
        {
          "architecture": "amd64",
          "features": "",
          "variant": null,
          "digest": "sha256:409baee827dee86faf5d7bda3caa0d2e534b1d4b21ecd67e8bd93e46ed750a83",
          "os": "linux",
          "os_features": "",
          "os_version": null,
          "size": 205454040,
          "status": "active",
          "last_pulled": "2025-04-11T19:42:09.702608133Z",
          "last_pushed": "2025-04-07T14:02:23.547489138Z"
        },
        {
          "architecture": "arm64",
          "features": "",
          "variant": null,
          "digest": "sha256:5b0d2604164885591c84aec73dc369d27f48d22e3afa6effbdce71a43058c8dd",
          "os": "linux",
          "os_features": "",
          "os_version": null,
          "size": 206102002,
          "status": "active",
          "last_pulled": "2025-04-11T15:28:05.725345972Z",
          "last_pushed": "2025-04-07T14:02:24.035538759Z"
        }
      ],
      "last_updated": "2025-04-07T14:02:24.799601Z",
      "last_updater": 6760745,
      "last_updater_username": "n8nio",
      "name": "1.87.0",
      "repository": 7303950,
      "full_size": 205454040,
      "v2": true,
      "tag_status": "active",
      "tag_last_pulled": "2025-04-11T21:11:11.70474394Z",
      "tag_last_pushed": "2025-04-07T14:02:24.799601Z",
      "media_type": "application/vnd.docker.distribution.manifest.list.v2+json",
      "content_type": "image",
      "digest": "sha256:55f76b8f0007ef6a73f909a5dcc0e79ffeae19ffa35cbe4bb117d87ae6771096"
    }
  ]
}

Ich habe die meisten Daten entfernt, aber für jede Version erhältst Du ein Objekt mit einer name-Eigenschaft, die die Lizenz ist. Diese Daten nehmen wir und parsen sie, um sie mit unserer aktuellen Version zu vergleichen. Wenn es eine neue Version gibt, speichern wir sie und leiten sie an den nächsten Node weiter:

const data = $input.first();
const dockerData = data.json.results;
const currentVersion = $input.last().json.version;

function parseVersion(str) {
  const match = str.match(/^(\d+)\.(\d+)\.(\d+)$/);
  return match ? match.slice(1).map(Number) : null;
}

function isNewer(a, b) {
  for (let i = 0; i < 3; i++) {
    if (a[i] > b[i]) return true;
    if (a[i] < b[i]) return false;
  }
  return false;
}

const currentParsed = parseVersion(currentVersion);
let newestParsed = null;
let newVersion = '';

for (const tag of dockerData) {
  const parsed = parseVersion(tag.name);
  if (!parsed) continue;

  if (isNewer(parsed, currentParsed)) {
    if (!newestParsed || isNewer(parsed, newestParsed)) {
      newestParsed = parsed;
      newVersion = tag.name;
    }
  }
}

return [{ newVersion }];

Schritt 3: Eine neue Bereitstellung auslösen

In meinem Fall hoste ich auf sliplane.io, wo ich eine neue Bereitstellung auslösen kann, indem ich einen HTTP-Request an einen geheimen Deploy-Hook-Endpunkt sende.

Die URL sieht in etwa so aus: https://api.sliplane.io/deploy/service_v9w2dkz11xv3/long-secret-value?tag=1.88.0, wobei das Tag durch die neue Version ersetzt werden kann!

Selbst wenn Du nicht sliplane verwendest, bieten viele andere Hosting-Anbieter eine ähnliche Funktion. Im Fall von Sliplane wird dadurch das Docker-Image, das Deinem Dienst zugrunde liegt, aktualisiert und neu gestartet.

Wenn Du n8n auf sliplane.io hosten möchtest (9 Euro pro Monat), schau Dir meinen vorherigen Blogpost hier an. Es dauert nur 2 Minuten, um loszulegen!

Der komplette Workflow

Jetzt, da Du weißt, wie der Workflow funktioniert, hier der komplette Workflow im JSON-Format. Stelle sicher, dass Du die URL mit Deiner eigenen n8n-Instanz-URL und den geheimen Wert mit Deinem eigenen Deploy-Hook-Geheimnis ersetzt.

{
  "name": "Update n8n",
  "nodes": [
    {
      "parameters": {
        "url": "https://your-n8n-instance.sliplane.app/metrics",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        80,
        200
      ],
      "id": "dd3ee216-c720-4a0a-b291-b6e067370658",
      "name": "Request Metrics"
    },
    {
      "parameters": {
        "jsCode": "const match = $input.first().json['data'].match(/n8n_version_info\\{[^}]*version=\"(v[\\d.]+)\"/);\nconst version = match ? match[1] : '';\n\n$input.first().json.version = version.slice(1);\n\nreturn $input.all();"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        300,
        200
      ],
      "id": "833a502c-d90d-4623-808c-06b7ff1361db",
      "name": "Parse Version"
    },
    {
      "parameters": {
        "url": "https://hub.docker.com/v2/repositories/n8nio/n8n/tags?ordering=last_updated",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        300,
        0
      ],
      "id": "4166069d-c969-4f90-897b-39637822c971",
      "name": "Request DockerHub"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.1,
      "position": [
        520,
        100
      ],
      "id": "362ccb3b-f370-44f7-b8b4-84870d002e45",
      "name": "Merge"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "e4c8b5e2-f4fa-4b83-b22e-0ffbf3ae5ccd",
              "leftValue": "={{ $json.newVersion }}",
              "rightValue": "\"\"",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        960,
        100
      ],
      "id": "83295a47-3411-4fa6-8bbc-9a4e22dec9c3",
      "name": "Has New Version"
    },
    {
      "parameters": {
        "url": "https://api.sliplane.io/health",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "tag",
              "value": "={{ $json.newVersion }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1180,
        100
      ],
      "id": "c0687380-0190-40dc-bb11-402384e289ef",
      "name": "Trigger Redeploy"
    },
    {
      "parameters": {
        "jsCode": "const data = $input.first();\nconst dockerData = data.json.results;\nconst currentVersion = $input.last().json.version;\n\nfunction parseVersion(str) {\n  const match = str.match(/^(\\d+)\\.(\\d+)\\.(\\d+)$/);\n  return match ? match.slice(1).map(Number) : null;\n}\n\nfunction isNewer(a, b) {\n  for (let i = 0; i < 3; i++) {\n    if (a[i] > b[i]) return true;\n    if (a[i] < b[i]) return false;\n  }\n  return false;\n}\n\nconst currentParsed = parseVersion(currentVersion);\nlet newestParsed = null;\nlet newVersion = '';\n\nfor (const tag of dockerData) {\n  const parsed = parseVersion(tag.name);\n  if (!parsed) continue;\n\n  if (isNewer(parsed, currentParsed)) {\n    if (!newestParsed || isNewer(parsed, newestParsed)) {\n      newestParsed = parsed;\n      newVersion = tag.name;\n    }\n  }\n}\n\nreturn [{ newVersion }];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        740,
        100
      ],
      "id": "d0c734c2-11e5-4048-9cc7-23ff11678502",
      "name": "Find New Version"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 6
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -140,
        100
      ],
      "id": "cd017a87-0524-49c0-943f-2f03ed904e7b",
      "name": "Schedule Trigger"
    }
  ],
  "pinData": {},
  "connections": {
    "Request Metrics": {
      "main": [
        [
          {
            "node": "Parse Version",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request DockerHub": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Version": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Find New Version",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has New Version": {
      "main": [
        [
          {
            "node": "Trigger Redeploy",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find New Version": {
      "main": [
        [
          {
            "node": "Has New Version",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Request Metrics",
            "type": "main",
            "index": 0
          },
          {
            "node": "Request DockerHub",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "1ab693ad-d248-4658-90a1-2414bfac0b02",
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "d27af88f79cf5acec70fbd96be12ba4f4c37c61aa5c8774eab25678db572187b"
  },
  "id": "aZJ6zGrX9fPOM0RM",
  "tags": []
}

Willkommen in der Container-Cloud

Sliplane macht es einfach, Container in der Cloud zu deployen und bei Bedarf zu skalieren. Probier es jetzt aus!