From 57ffd26d2ca29fbbd30e536fec4c08559b7901e5 Mon Sep 17 00:00:00 2001 From: Declan Carroll Date: Fri, 4 Sep 2026 14:12:54 +0100 Subject: [PATCH] build: speed up task runner deployment --- docker/images/runners/Dockerfile | 21 ++++---------- docker/images/runners/Dockerfile.distroless | 32 --------------------- packages/@n8n/task-runner/package.json | 1 + pnpm-lock.yaml | 3 ++ scripts/build-n8n.mjs | 2 +- 5 files changed, 10 insertions(+), 49 deletions(-) diff --git a/docker/images/runners/Dockerfile b/docker/images/runners/Dockerfile index c3d9509feaa..3c00963f8ea 100644 --- a/docker/images/runners/Dockerfile +++ b/docker/images/runners/Dockerfile @@ -10,34 +10,23 @@ COPY ./dist/task-runner-javascript /app/task-runner-javascript WORKDIR /app/task-runner-javascript -# Pin pnpm to the repo's packageManager version. `pnpm deploy` drops the -# packageManager field from the deployed package.json, so nothing else here pins it. -ARG PNPM_VERSION -RUN npm i -g "pnpm@${PNPM_VERSION}" - -# Remove `catalog` and `workspace` references from package.json to allow `pnpm add` in extended images +# Remove workspace-only references from package.json to allow `pnpm add` in extended images. RUN node -e "const pkg = require('./package.json'); \ Object.keys(pkg.dependencies || {}).forEach(k => { \ const val = pkg.dependencies[k]; \ - if (val === 'catalog:' || val.startsWith('catalog:') || val.startsWith('workspace:')) \ + if (val === 'catalog:' || val.startsWith('catalog:') || val.startsWith('workspace:') || val.startsWith('file:') || val.includes('@file:')) \ delete pkg.dependencies[k]; \ }); \ Object.keys(pkg.devDependencies || {}).forEach(k => { \ const val = pkg.devDependencies[k]; \ - if (val === 'catalog:' || val.startsWith('catalog:') || val.startsWith('workspace:')) \ + if (val === 'catalog:' || val.startsWith('catalog:') || val.startsWith('workspace:') || val.startsWith('file:') || val.includes('@file:')) \ delete pkg.devDependencies[k]; \ }); \ delete pkg.devDependencies; \ require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2));" -# `--config.minimum-release-age=0`: pnpm 11 defaults it to 1440 (24h), which would make -# this reject any dependency published less than a day ago — e.g. a first-party -# @n8n_io/ai-assistant-sdk bump landed within hours of its npm publish. This step only -# re-resolves an already-vetted, already-pinned dependency set; the real supply-chain age -# policy is enforced by the host install (pnpm-workspace.yaml + SafeChain). -# Install moment (special case for backwards compatibility) -RUN rm -f node_modules/.modules.yaml && \ - pnpm add moment@2.30.1 --prod --no-lockfile --config.minimum-release-age=0 +# Remove deploy metadata that points to workspace-only settings and patch files. +RUN rm -f pnpm-lock.yaml node_modules/.modules.yaml node_modules/.pnpm/lock.yaml node_modules/.pnpm-workspace-state-v1.json # ============================================================================== # STAGE 2: Python runner build (@n8n/task-runner-python) with uv diff --git a/docker/images/runners/Dockerfile.distroless b/docker/images/runners/Dockerfile.distroless index 6fec659658e..41024711b23 100644 --- a/docker/images/runners/Dockerfile.distroless +++ b/docker/images/runners/Dockerfile.distroless @@ -13,7 +13,6 @@ # ============================================================================== ARG NODE_VERSION=26.7.0 -ARG PNPM_VERSION=11.25.0 ARG PYTHON_VERSION=3.13 @@ -23,37 +22,6 @@ ARG PYTHON_VERSION=3.13 FROM node:${NODE_VERSION}-bookworm-slim AS javascript-runner-builder COPY ./dist/task-runner-javascript /app/task-runner-javascript -WORKDIR /app/task-runner-javascript - -# Pin pnpm to the repo's packageManager version. `pnpm deploy` drops the -# packageManager field from the deployed package.json, so nothing else here pins it. -ARG PNPM_VERSION -RUN npm i -g "pnpm@${PNPM_VERSION}" - -# Remove `catalog` and `workspace` references from package.json to allow `pnpm add` -RUN node -e "const pkg = require('./package.json'); \ - Object.keys(pkg.dependencies || {}).forEach(k => { \ - const val = pkg.dependencies[k]; \ - if (val === 'catalog:' || val.startsWith('catalog:') || val.startsWith('workspace:')) \ - delete pkg.dependencies[k]; \ - }); \ - Object.keys(pkg.devDependencies || {}).forEach(k => { \ - const val = pkg.devDependencies[k]; \ - if (val === 'catalog:' || val.startsWith('catalog:') || val.startsWith('workspace:')) \ - delete pkg.devDependencies[k]; \ - }); \ - delete pkg.devDependencies; \ - require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2));" - -# `--config.minimum-release-age=0`: pnpm 11 defaults it to 1440 (24h), which would make -# this reject any dependency published less than a day ago — e.g. a first-party -# @n8n_io/ai-assistant-sdk bump landed within hours of its npm publish. This step only -# re-resolves an already-vetted, already-pinned dependency set; the real supply-chain age -# policy is enforced by the host install (pnpm-workspace.yaml + SafeChain). -# Install moment by default (special case for n8n cloud) -RUN rm -f node_modules/.modules.yaml && \ - pnpm add moment@2.30.1 --prod --no-lockfile --config.minimum-release-age=0 - # Rebuild isolated-vm for the container platform. Install build tools as # fallback in case prebuild-install cannot find a prebuilt binary. RUN apt-get update && apt-get install -y --no-install-recommends python3 make g++ && \ diff --git a/packages/@n8n/task-runner/package.json b/packages/@n8n/task-runner/package.json index c262140d79c..6c7b401d5fb 100644 --- a/packages/@n8n/task-runner/package.json +++ b/packages/@n8n/task-runner/package.json @@ -47,6 +47,7 @@ "acorn-walk": "8.3.4", "lodash": "catalog:", "luxon": "catalog:", + "moment": "2.30.1", "n8n-core": "workspace:*", "n8n-workflow": "workspace:*", "nanoid": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c5fa7412326..03a00586daa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3784,6 +3784,9 @@ importers: luxon: specifier: 'catalog:' version: 3.7.2 + moment: + specifier: 2.30.1 + version: 2.30.1 n8n-core: specifier: workspace:* version: link:../../core diff --git a/scripts/build-n8n.mjs b/scripts/build-n8n.mjs index 91cc24e3491..a09aeb361c3 100755 --- a/scripts/build-n8n.mjs +++ b/scripts/build-n8n.mjs @@ -423,7 +423,7 @@ try { // orders of magnitude smaller than the n8n one, so the strips above are not // worth duplicating here — the closure figure they report covers the n8n image // only, not the shipped total. - await $`cd ${config.rootDir} && NODE_ENV=production DOCKER_BUILD=true pnpm --filter=@n8n/task-runner --prod --legacy deploy --no-optional ${config.compiledTaskRunnerDir}`; + await $`cd ${config.rootDir} && NODE_ENV=production DOCKER_BUILD=true pnpm --filter=@n8n/task-runner --prod --config.inject-workspace-packages=true deploy --no-optional ${config.compiledTaskRunnerDir}`; // Check the production closure for single-instance dependency duplication. A curated // library resolving to more than one physical copy silently breaks instanceof /