Building a self-updating n8n instance with n8n workflows
Self-hosting n8n is absolutely fantastic. You have full control over your data, your workflows, and your integrations. BUT you know what is really annoying? Updating your n8n instance everytime a new version is released, right when you want to get some work done. Okay I'm a bit dramatic here, but I don't want to manually update my software, we're not in 2005 anymore! In this blog post I will show you how to automate the process of updating your n8n instance with a n8n workflow! I'd suggest watching the video below and then come back to copy the workflow/code: The workflow So the gist of the workflow is: Check the current version of n8n Check the latest version of n8n on Docker Hub Compare the two versions If there is a new version, trigger a new deployment That looks kinda like this: Let's break down the workflow: Step 1. Check current version n8n has a public API endpoint at /metrics that returns metrics (duh) about the instance. You will have to enable the API in the settings first. Do that by setting the N8N_METRICS environment variable to true. Requesting the /metrics endpoint will return a prometheus formatted response that looks something like this: # 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 I removed most of the metrics, but the important line is: n8n_version_info{version="v1.88.0",major="1",minor="88",patch="0"} 1 We can get that data by making a HTTP request node, and then parsing the response with a javascript code node. The code to parse the response is: 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(); If you need an explanation of the code I'd recommend giving it to ChatGPT :) Step 2: Check the latest version Now that we have the current version, we can check the latest version on Docker Hub. For that, we use the public API of Docker Hub that is reachable at https://hub.docker.com/v2/repositories/n8nio/n8n/tags?ordering=last_updated The output looks something like this: { "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": "n

Self-hosting n8n is absolutely fantastic. You have full control over your data, your workflows, and your integrations. BUT you know what is really annoying? Updating your n8n instance everytime a new version is released, right when you want to get some work done. Okay I'm a bit dramatic here, but I don't want to manually update my software, we're not in 2005 anymore!
In this blog post I will show you how to automate the process of updating your n8n instance with a n8n workflow! I'd suggest watching the video below and then come back to copy the workflow/code:
The workflow
So the gist of the workflow is:
- Check the current version of n8n
- Check the latest version of n8n on Docker Hub
- Compare the two versions
- If there is a new version, trigger a new deployment
That looks kinda like this:
Let's break down the workflow:
Step 1. Check current version
n8n has a public API endpoint at /metrics
that returns metrics (duh) about the instance. You will have to enable the API in the settings first. Do that by setting the N8N_METRICS
environment variable to true
. Requesting the /metrics
endpoint will return a prometheus formatted response that looks something like this:
# 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
I removed most of the metrics, but the important line is:
n8n_version_info{version="v1.88.0",major="1",minor="88",patch="0"} 1
We can get that data by making a HTTP request node, and then parsing the response with a javascript code node.
The code to parse the response is:
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();
If you need an explanation of the code I'd recommend giving it to ChatGPT :)
Step 2: Check the latest version
Now that we have the current version, we can check the latest version on Docker Hub.
For that, we use the public API of Docker Hub that is reachable at https://hub.docker.com/v2/repositories/n8nio/n8n/tags?ordering=last_updated
The output looks something like this:
{
"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"
}
]
}
I removed most of the data, but for each version you get one object with a name
property that is the license. We take that data and parse it to compare it with our current version. If there is a new version, we save it and forward it to the next node:
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 }];
Step 3: Trigger a new deployment
In my case I am hosting on sliplane.io where I can trigger a new deployment by making a HTTP request to a secret deploy hook endpoint.
The url looks something like this: https://api.sliplane.io/deploy/service_v9w2dkz11xv3/long-secret-value?tag=1.88.0
, where tag can be replaced with the new version!
Even if you're not using sliplane, many other hosting providers offer a similar feature. In the case of Sliplane, this will update the docker image backing your service and restart it.
If you want to host n8n on sliplane.io (9 euros per month), check out my previous blog post here. Only takes 2 minutes to get started!
The complete workflow
Now that you know how the workflow works, here is the complete workflow in JSON format. Make sure to replace the url with your own n8n instance url and the secret value with your own deploy hook secret.
{
"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": []
}
Cheers,
Jonas Co-Founder sliplane.io