first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
+154
View File
@@ -0,0 +1,154 @@
ARG NODE_VERSION=24.13.1
ARG PYTHON_VERSION=3.13
# ==============================================================================
# STAGE 1: JavaScript runner (@n8n/task-runner) artifact from CI
# ==============================================================================
FROM node:${NODE_VERSION}-alpine AS javascript-runner-builder
COPY ./dist/task-runner-javascript /app/task-runner-javascript
WORKDIR /app/task-runner-javascript
RUN corepack enable pnpm
# Remove `catalog` and `workspace` 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:')) \
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));"
# Install moment (special case for backwards compatibility)
RUN rm -f node_modules/.modules.yaml && \
pnpm add moment@2.30.1 --prod --no-lockfile
# ==============================================================================
# STAGE 2: Python runner build (@n8n/task-runner-python) with uv
# Produces a relocatable venv tied to the python version used
# ==============================================================================
FROM python:${PYTHON_VERSION}-alpine AS python-runner-builder
ARG TARGETPLATFORM
ARG UV_VERSION=0.8.14
RUN set -e; \
case "$TARGETPLATFORM" in \
"linux/amd64") UV_ARCH="x86_64-unknown-linux-musl" ;; \
"linux/arm64") UV_ARCH="aarch64-unknown-linux-musl" ;; \
*) echo "Unsupported platform: $TARGETPLATFORM" >&2; exit 1 ;; \
esac; \
mkdir -p /tmp/uv && cd /tmp/uv; \
wget -q "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${UV_ARCH}.tar.gz"; \
wget -q "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${UV_ARCH}.tar.gz.sha256"; \
sha256sum -c "uv-${UV_ARCH}.tar.gz.sha256"; \
tar -xzf "uv-${UV_ARCH}.tar.gz"; \
install -m 0755 "uv-${UV_ARCH}/uv" /usr/local/bin/uv; \
cd / && rm -rf /tmp/uv
WORKDIR /app/task-runner-python
COPY packages/@n8n/task-runner-python/pyproject.toml \
packages/@n8n/task-runner-python/uv.lock** \
packages/@n8n/task-runner-python/.python-version** \
./
RUN uv venv
RUN uv sync \
--frozen \
--no-editable \
--no-install-project \
--no-dev \
--all-extras
COPY packages/@n8n/task-runner-python/ ./
RUN uv sync \
--frozen \
--no-dev \
--all-extras \
--no-editable
# Install the python runner package itself into site packages. We can remove the src directory then
RUN uv pip install . && rm -rf /app/task-runner-python/src
# ==============================================================================
# STAGE 3: Task Runner Launcher download
# ==============================================================================
FROM alpine:3.22 AS launcher-downloader
ARG TARGETPLATFORM
ARG LAUNCHER_VERSION=1.4.3
RUN set -e; \
case "$TARGETPLATFORM" in \
"linux/amd64") ARCH_NAME="amd64" ;; \
"linux/arm64") ARCH_NAME="arm64" ;; \
*) echo "Unsupported platform: $TARGETPLATFORM" && exit 1 ;; \
esac; \
mkdir /launcher-temp && cd /launcher-temp; \
wget -q "https://github.com/n8n-io/task-runner-launcher/releases/download/${LAUNCHER_VERSION}/task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz"; \
wget -q "https://github.com/n8n-io/task-runner-launcher/releases/download/${LAUNCHER_VERSION}/task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz.sha256"; \
echo "$(cat task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz.sha256) task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz" > checksum.sha256; \
sha256sum -c checksum.sha256; \
mkdir -p /launcher-bin; \
tar xzf task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz -C /launcher-bin; \
cd / && rm -rf /launcher-temp
# ==============================================================================
# STAGE 4: Node alpine base for JS task runner
# ==============================================================================
FROM node:${NODE_VERSION}-alpine AS node-alpine
# ==============================================================================
# STAGE 5: Runtime
# ==============================================================================
FROM python:${PYTHON_VERSION}-alpine AS runtime
ARG N8N_VERSION=snapshot
ARG N8N_RELEASE_TYPE=dev
ENV NODE_ENV=production \
N8N_RELEASE_TYPE=${N8N_RELEASE_TYPE} \
SHELL=/bin/sh
# Bring `uv` over from python-runner-builder, to make the image easier to extend
COPY --from=python-runner-builder /usr/local/bin/uv /usr/local/bin/uv
# Bring node over from node-alpine
COPY --from=node-alpine /usr/local/bin/node /usr/local/bin/node
# libstdc++ is required by Node
# libc6-compat is required by task-runner-launcher
RUN apk add --no-cache ca-certificates tini libstdc++ libc6-compat && \
apk del apk-tools
# Bring corepack and pnpm over, to make the image easier to extend
COPY --from=node-alpine /usr/local/lib/node_modules/corepack /usr/local/lib/node_modules/corepack
RUN ln -s ../lib/node_modules/corepack/dist/corepack.js /usr/local/bin/corepack && \
ln -s ../lib/node_modules/corepack/dist/pnpm.js /usr/local/bin/pnpm
RUN addgroup -g 1000 -S runner \
&& adduser -u 1000 -S -G runner -h /home/runner -D runner
WORKDIR /home/runner
COPY --from=javascript-runner-builder --chown=root:root /app/task-runner-javascript /opt/runners/task-runner-javascript
COPY --from=python-runner-builder --chown=root:root /app/task-runner-python /opt/runners/task-runner-python
COPY --from=launcher-downloader /launcher-bin/* /usr/local/bin/
COPY --chown=root:root docker/images/runners/n8n-task-runners.json /etc/n8n-task-runners.json
USER runner
EXPOSE 5680/tcp
ENTRYPOINT ["tini", "--", "/usr/local/bin/task-runner-launcher"]
CMD ["javascript", "python"]
LABEL org.opencontainers.image.title="n8n task runners" \
org.opencontainers.image.description="Sidecar image providing n8n task runners for JavaScript and Python code execution" \
org.opencontainers.image.source="https://github.com/n8n-io/n8n" \
org.opencontainers.image.url="https://n8n.io" \
org.opencontainers.image.version="${N8N_VERSION}"
+198
View File
@@ -0,0 +1,198 @@
# ==============================================================================
# DISTROLESS RUNNERS IMAGE
# ==============================================================================
# This is a distroless variant of docker/images/runners/Dockerfile, intended
# for cloud deployments. It removes all shell, package managers, and system
# utilities for security hardening.
#
# Key differences:
# - Uses Debian-based builders (glibc instead of musl)
# - Final image is Google's distroless/cc-debian12
# - Extra runtime-prep stage to organize filesystem
# - Uses distroless nonroot user (UID 65532)
# ==============================================================================
ARG NODE_VERSION=24.13.1
ARG PYTHON_VERSION=3.13
# ==============================================================================
# STAGE 1: JavaScript runner (@n8n/task-runner) artifact from CI
# ==============================================================================
FROM node:${NODE_VERSION}-bookworm-slim AS javascript-runner-builder
COPY ./dist/task-runner-javascript /app/task-runner-javascript
WORKDIR /app/task-runner-javascript
RUN corepack enable pnpm
# 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));"
# 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
# ==============================================================================
# STAGE 2: Python runner build (@n8n/task-runner-python) with uv
# Produces a relocatable venv tied to the python version used
# ==============================================================================
FROM python:${PYTHON_VERSION}-slim-bookworm AS python-runner-builder
ARG TARGETPLATFORM
ARG UV_VERSION=0.8.14
RUN set -e; \
apt-get update && apt-get install -y --no-install-recommends wget ca-certificates && \
case "$TARGETPLATFORM" in \
"linux/amd64") UV_ARCH="x86_64-unknown-linux-gnu" ;; \
"linux/arm64") UV_ARCH="aarch64-unknown-linux-gnu" ;; \
*) echo "Unsupported platform: $TARGETPLATFORM" >&2; exit 1 ;; \
esac; \
mkdir -p /tmp/uv && cd /tmp/uv; \
wget -q "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${UV_ARCH}.tar.gz"; \
wget -q "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${UV_ARCH}.tar.gz.sha256"; \
sha256sum -c "uv-${UV_ARCH}.tar.gz.sha256"; \
tar -xzf "uv-${UV_ARCH}.tar.gz"; \
install -m 0755 "uv-${UV_ARCH}/uv" /usr/local/bin/uv; \
cd / && rm -rf /tmp/uv && \
apt-get clean && rm -rf /var/lib/apt/lists/*
WORKDIR /app/task-runner-python
COPY packages/@n8n/task-runner-python/pyproject.toml \
packages/@n8n/task-runner-python/uv.lock** \
packages/@n8n/task-runner-python/.python-version** \
./
RUN uv venv
RUN uv sync \
--frozen \
--no-editable \
--no-install-project \
--no-dev \
--all-extras
COPY packages/@n8n/task-runner-python/ ./
RUN uv sync \
--frozen \
--no-dev \
--all-extras \
--no-editable
# Install the python runner package itself into site packages. We can remove the src directory then
RUN uv pip install . && rm -rf /app/task-runner-python/src
# ==============================================================================
# STAGE 3: Task Runner Launcher download
# ==============================================================================
FROM debian:bookworm-slim AS launcher-downloader
ARG TARGETPLATFORM
ARG LAUNCHER_VERSION=1.4.3
RUN set -e; \
apt-get update && apt-get install -y --no-install-recommends wget ca-certificates && \
case "$TARGETPLATFORM" in \
"linux/amd64") ARCH_NAME="amd64" ;; \
"linux/arm64") ARCH_NAME="arm64" ;; \
*) echo "Unsupported platform: $TARGETPLATFORM" && exit 1 ;; \
esac; \
mkdir /launcher-temp && cd /launcher-temp; \
wget -q "https://github.com/n8n-io/task-runner-launcher/releases/download/${LAUNCHER_VERSION}/task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz"; \
wget -q "https://github.com/n8n-io/task-runner-launcher/releases/download/${LAUNCHER_VERSION}/task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz.sha256"; \
echo "$(cat task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz.sha256) task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz" > checksum.sha256; \
sha256sum -c checksum.sha256; \
mkdir -p /launcher-bin; \
tar xzf task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz -C /launcher-bin; \
cd / && rm -rf /launcher-temp && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# ==============================================================================
# STAGE 4: Node Debian base for JS task runner
# ==============================================================================
FROM node:${NODE_VERSION}-bookworm-slim AS node-debian
# ==============================================================================
# STAGE 5: Runtime preparation
# ==============================================================================
# Prepare a clean filesystem structure with all necessary files before
# copying to the distroless base. This stage runs with a shell so we can
# create symlinks and organize files properly.
# ==============================================================================
FROM debian:bookworm-slim AS runtime-prep
# Copy Python runtime
COPY --from=python-runner-builder /usr/local/bin/python3.13 /runtime/usr/local/bin/python3.13
COPY --from=python-runner-builder /usr/local/lib/python3.13 /runtime/usr/local/lib/python3.13
COPY --from=python-runner-builder /usr/local/lib/libpython3.13.so* /runtime/usr/local/lib/
# Copy Python dependencies (architecture-specific directories)
# The /* glob will match x86_64-linux-gnu or aarch64-linux-gnu
COPY --from=python-runner-builder /lib/*-linux-gnu* /runtime/lib/
COPY --from=python-runner-builder /usr/lib/*-linux-gnu* /runtime/usr/lib/
# Copy Node.js runtime
COPY --from=node-debian /usr/local/bin/node /runtime/usr/local/bin/node
# Copy task runners
COPY --from=javascript-runner-builder /app/task-runner-javascript /runtime/opt/runners/task-runner-javascript
COPY --from=python-runner-builder /app/task-runner-python /runtime/opt/runners/task-runner-python
# Copy launcher
COPY --from=launcher-downloader /launcher-bin/* /runtime/usr/local/bin/
# Copy configuration
COPY docker/images/runners/n8n-task-runners.json /runtime/etc/n8n-task-runners.json
# Create necessary directories with proper permissions
RUN mkdir -p /runtime/home/runner && \
chmod 755 /runtime/home/runner && \
chmod +x /runtime/usr/local/bin/*
# ==============================================================================
# STAGE 6: Distroless Runtime
# ==============================================================================
# Uses Google's distroless/cc-debian12 which provides:
# - glibc, libgcc, libstdc++
# - CA certificates
# - Timezone data
# - nonroot user (UID 65532)
# - NO shell, NO package manager, NO system utilities
# ==============================================================================
FROM gcr.io/distroless/cc-debian12:latest AS runtime
ARG N8N_VERSION=snapshot
ARG N8N_RELEASE_TYPE=dev
ENV NODE_ENV=production \
N8N_RELEASE_TYPE=${N8N_RELEASE_TYPE} \
HOME=/home/runner
# Copy everything from the prepared runtime filesystem
COPY --from=runtime-prep --chown=root:root /runtime/ /
WORKDIR /home/runner
# Switch to nonroot user (UID 65532)
USER 65532:65532
EXPOSE 5680/tcp
ENTRYPOINT ["/usr/local/bin/task-runner-launcher"]
CMD ["javascript", "python"]
LABEL org.opencontainers.image.title="n8n task runners (distroless)" \
org.opencontainers.image.description="Distroless sidecar image providing n8n task runners for JavaScript and Python code execution" \
org.opencontainers.image.source="https://github.com/n8n-io/n8n" \
org.opencontainers.image.url="https://n8n.io" \
org.opencontainers.image.version="${N8N_VERSION}"
+56
View File
@@ -0,0 +1,56 @@
# n8n - Task runners (`n8nio/runners`) - (PREVIEW)
`n8nio/runners` image includes [JavaScript runner](https://github.com/n8n-io/n8n/tree/master/packages/%40n8n/task-runner),
[Python runner](https://github.com/n8n-io/n8n/tree/master/packages/%40n8n/task-runner-python) and
[Task runner launcher](https://github.com/n8n-io/task-runner-launcher) that connects to a Task Broker
running on the main n8n instance when running in `external` mode. This image is to be launched as a sidecar
container to the main n8n container.
[Task runners](https://docs.n8n.io/hosting/configuration/task-runners/) are used to execute user-provided code
in the [Code Node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/), isolated from the n8n instance.
For official documentation, please see [here](https://docs.n8n.io/hosting/configuration/task-runners/).
For a distroless variant of this image, see [here](./Dockerfile.distroless).
For development purposes only, see below.
## Testing locally
### 1) Make a production build of n8n
```
pnpm run build:n8n
```
### 2) Build the task runners image
```
docker buildx build \
-f docker/images/runners/Dockerfile \
-t n8nio/runners \
.
```
### 3) Start n8n on your host machine with Task Broker enabled
```
N8N_RUNNERS_MODE=external \
N8N_RUNNERS_AUTH_TOKEN=test \
N8N_LOG_LEVEL=debug \
pnpm start
```
### 4) Start the task runner container
```
docker run --rm -it \
-e N8N_RUNNERS_AUTH_TOKEN=test \
-e N8N_RUNNERS_LAUNCHER_LOG_LEVEL=debug \
-e N8N_RUNNERS_TASK_BROKER_URI=http://host.docker.internal:5679 \
-p 5680:5680 \
n8nio/runners
```
If you need to add extra dependencies (custom image), follow [these instructions](https://docs.n8n.io/hosting/configuration/task-runners/#adding-extra-dependencies).
@@ -0,0 +1,55 @@
{
"task-runners": [
{
"runner-type": "javascript",
"workdir": "/home/runner",
"command": "/usr/local/bin/node",
"args": [
"--disallow-code-generation-from-strings",
"--disable-proto=delete",
"/opt/runners/task-runner-javascript/dist/start.js"
],
"health-check-server-port": "5681",
"allowed-env": [
"PATH",
"GENERIC_TIMEZONE",
"NODE_OPTIONS",
"N8N_RUNNERS_AUTO_SHUTDOWN_TIMEOUT",
"N8N_RUNNERS_TASK_TIMEOUT",
"N8N_RUNNERS_MAX_CONCURRENCY",
"N8N_SENTRY_DSN",
"N8N_VERSION",
"ENVIRONMENT",
"DEPLOYMENT_NAME",
"HOME"
],
"env-overrides": {
"NODE_FUNCTION_ALLOW_BUILTIN": "crypto",
"NODE_FUNCTION_ALLOW_EXTERNAL": "moment",
"N8N_RUNNERS_HEALTH_CHECK_SERVER_HOST": "0.0.0.0"
}
},
{
"runner-type": "python",
"workdir": "/home/runner",
"command": "/opt/runners/task-runner-python/.venv/bin/python",
"args": ["-I", "-B", "-X", "disable_remote_debug", "-m", "src.main"],
"health-check-server-port": "5682",
"allowed-env": [
"PATH",
"N8N_RUNNERS_LAUNCHER_LOG_LEVEL",
"N8N_RUNNERS_AUTO_SHUTDOWN_TIMEOUT",
"N8N_RUNNERS_TASK_TIMEOUT",
"N8N_RUNNERS_MAX_CONCURRENCY",
"N8N_SENTRY_DSN",
"N8N_VERSION",
"ENVIRONMENT",
"DEPLOYMENT_NAME"
],
"env-overrides": {
"N8N_RUNNERS_STDLIB_ALLOW": "",
"N8N_RUNNERS_EXTERNAL_ALLOW": ""
}
}
]
}