Skip to content

Commit 6d405a6

Browse files
authored
ci: Add Dockerfile for multi-runner task runners image (#18975)
1 parent 10ac58a commit 6d405a6

File tree

6 files changed

+291
-25
lines changed

6 files changed

+291
-25
lines changed

docker/images/runners/Dockerfile

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
ARG PYTHON_IMAGE=python:3.13-slim
2+
3+
# ==============================================================================
4+
# STAGE 1: JavaScript runner (@n8n/task-runner) artifact from CI
5+
# ==============================================================================
6+
FROM alpine:3.22.1 AS app-artifact-processor
7+
COPY ./dist/task-runner-javascript /app/task-runner-javascript
8+
9+
# ==============================================================================
10+
# STAGE 2: Python runner build (@n8n/task-runner-python) with uv
11+
# Produces a relocatable venv tied to PYTHON_IMAGE
12+
# ==============================================================================
13+
FROM ${PYTHON_IMAGE} AS python-runner-builder
14+
ARG TARGETPLATFORM
15+
ARG UV_VERSION=0.8.14
16+
17+
RUN apt-get update && apt-get install -y --no-install-recommends \
18+
curl ca-certificates build-essential pkg-config git \
19+
&& rm -rf /var/lib/apt/lists/*
20+
21+
RUN set -e; \
22+
case "$TARGETPLATFORM" in \
23+
"linux/amd64") UV_ARCH="x86_64-unknown-linux-gnu" ;; \
24+
"linux/arm64") UV_ARCH="aarch64-unknown-linux-gnu" ;; \
25+
*) echo "Unsupported platform: $TARGETPLATFORM" >&2; exit 1 ;; \
26+
esac; \
27+
mkdir -p /tmp/uv && cd /tmp/uv; \
28+
curl -fsSLO "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${UV_ARCH}.tar.gz"; \
29+
curl -fsSLO "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${UV_ARCH}.tar.gz.sha256"; \
30+
sha256sum -c "uv-${UV_ARCH}.tar.gz.sha256"; \
31+
tar -xzf "uv-${UV_ARCH}.tar.gz"; \
32+
install -m 0755 "uv-${UV_ARCH}/uv" /usr/local/bin/uv; \
33+
cd / && rm -rf /tmp/uv
34+
35+
WORKDIR /app/task-runner-python
36+
37+
COPY packages/@n8n/task-runner-python/pyproject.toml \
38+
packages/@n8n/task-runner-python/uv.lock** \
39+
packages/@n8n/task-runner-python/.python-version** \
40+
./
41+
42+
RUN uv venv
43+
RUN uv sync \
44+
--frozen \
45+
--no-editable \
46+
--no-install-project \
47+
--all-extras
48+
49+
COPY packages/@n8n/task-runner-python/ ./
50+
RUN uv sync \
51+
--frozen \
52+
--no-editable
53+
54+
# ==============================================================================
55+
# STAGE 3: Task Runner Launcher download
56+
# ==============================================================================
57+
FROM alpine:3.22.1 AS launcher-downloader
58+
ARG TARGETPLATFORM
59+
ARG LAUNCHER_VERSION=1.3.0
60+
61+
RUN set -e; \
62+
case "$TARGETPLATFORM" in \
63+
"linux/amd64") ARCH_NAME="amd64" ;; \
64+
"linux/arm64") ARCH_NAME="arm64" ;; \
65+
*) echo "Unsupported platform: $TARGETPLATFORM" && exit 1 ;; \
66+
esac; \
67+
mkdir /launcher-temp && cd /launcher-temp; \
68+
wget -q "https://github.com/n8n-io/task-runner-launcher/releases/download/${LAUNCHER_VERSION}/task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz"; \
69+
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"; \
70+
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; \
71+
sha256sum -c checksum.sha256; \
72+
mkdir -p /launcher-bin; \
73+
tar xzf task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz -C /launcher-bin; \
74+
cd / && rm -rf /launcher-temp
75+
76+
# ==============================================================================
77+
# STAGE 4: Runtime
78+
# ==============================================================================
79+
FROM ${PYTHON_IMAGE} AS runtime
80+
ARG NODE_VERSION=22
81+
ARG N8N_VERSION=snapshot
82+
ARG N8N_RELEASE_TYPE=dev
83+
84+
ENV NODE_ENV=production
85+
ENV N8N_RELEASE_TYPE=${N8N_RELEASE_TYPE}
86+
ENV SHELL=/bin/sh
87+
88+
RUN apt-get update \
89+
&& apt-get install -y --no-install-recommends curl gnupg ca-certificates tini \
90+
&& mkdir -p /etc/apt/keyrings \
91+
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
92+
| gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
93+
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_VERSION}.x nodistro main" \
94+
> /etc/apt/sources.list.d/nodesource.list \
95+
&& apt-get update \
96+
&& apt-get install -y --no-install-recommends nodejs \
97+
&& apt-get remove curl -y \
98+
&& apt-get clean \
99+
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*.deb
100+
101+
RUN useradd -m -u 1000 runner
102+
WORKDIR /home/runner
103+
104+
COPY --from=app-artifact-processor /app/task-runner-javascript /opt/runners/task-runner-javascript
105+
COPY --from=python-runner-builder /app/task-runner-python /opt/runners/task-runner-python
106+
COPY --from=launcher-downloader /launcher-bin/* /usr/local/bin/
107+
108+
COPY docker/images/runners/n8n-task-runners.json /etc/n8n-task-runners.json
109+
110+
RUN chown -R runner:runner /opt/runners /home/runner
111+
USER runner
112+
113+
EXPOSE 5680/tcp
114+
ENTRYPOINT ["tini", "--", "/usr/local/bin/task-runner-launcher"]
115+
CMD ["javascript", "python"]
116+
117+
LABEL org.opencontainers.image.title="n8n task runners" \
118+
org.opencontainers.image.description="Sidecar image providing n8n task runners for JavaScript and Python code execution" \
119+
org.opencontainers.image.source="https://github.com/n8n-io/n8n" \
120+
org.opencontainers.image.url="https://n8n.io" \
121+
org.opencontainers.image.version="${N8N_VERSION}"

docker/images/runners/README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# n8n - Task runners (`n8nio/runners`) - (PREVIEW)
2+
3+
`n8nio/runners` image includes [JavaScript runner](https://github.com/n8n-io/n8n/tree/master/packages/%40n8n/task-runner),
4+
[Python runner](https://github.com/n8n-io/n8n/tree/master/packages/%40n8n/task-runner-python) and
5+
[Task runner launcher](https://github.com/n8n-io/task-runner-launcher) that connects to a Task Broker
6+
running on the main n8n instance when running in `external` mode. This image is to be launched as a sidecar
7+
container to the main n8n container.
8+
9+
[Task runners](https://docs.n8n.io/hosting/configuration/task-runners/) are used to execute user-provided code
10+
in the [Code Node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/), isolated from the n8n instance.
11+
12+
13+
## Testing locally
14+
15+
1. Make a production build of n8n
16+
17+
```
18+
pnpm run build:n8n
19+
```
20+
21+
2. Build the task runners image
22+
23+
```
24+
docker buildx build --no-cache \
25+
-f docker/images/runners/Dockerfile \
26+
-t n8nio/runners \
27+
.
28+
```
29+
30+
3. Start n8n on your host machine with Task Broker enabled
31+
32+
```
33+
N8N_RUNNERS_ENABLED=true \
34+
N8N_RUNNERS_MODE=external \
35+
N8N_RUNNERS_AUTH_TOKEN=test \
36+
N8N_LOG_LEVEL=debug \
37+
pnpm start
38+
```
39+
40+
41+
4. Start the task runner container
42+
43+
```
44+
docker run --rm -it \
45+
-e N8N_RUNNERS_AUTH_TOKEN=test \
46+
-e N8N_RUNNERS_LAUNCHER_LOG_LEVEL=debug \
47+
-e N8N_RUNNERS_TASK_BROKER_URI=http://host.docker.internal:5679 \
48+
-p 5680:5680 \
49+
n8nio/runners
50+
```
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"task-runners": [
3+
{
4+
"runner-type": "javascript",
5+
"workdir": "/home/runner",
6+
"command": "/usr/bin/node",
7+
"args": [
8+
"--disallow-code-generation-from-strings",
9+
"--disable-proto=delete",
10+
"/opt/runners/task-runner-javascript/dist/start.js"
11+
],
12+
"health-check-server-port": "5681",
13+
"allowed-env": [
14+
"PATH",
15+
"GENERIC_TIMEZONE",
16+
"N8N_RUNNERS_MAX_CONCURRENCY",
17+
"NODE_OPTIONS",
18+
"N8N_SENTRY_DSN",
19+
"N8N_VERSION",
20+
"ENVIRONMENT",
21+
"DEPLOYMENT_NAME"
22+
],
23+
"env-overrides": {
24+
"NODE_FUNCTION_ALLOW_BUILTIN": "crypto",
25+
"NODE_FUNCTION_ALLOW_EXTERNAL": "moment",
26+
"N8N_RUNNERS_HEALTH_CHECK_SERVER_HOST": "0.0.0.0"
27+
}
28+
},
29+
{
30+
"runner-type": "python",
31+
"workdir": "/home/runner",
32+
"command": "/opt/runners/task-runner-python/.venv/bin/python",
33+
"args": ["-m", "src.main"],
34+
"health-check-server-port": "5682",
35+
"allowed-env": [
36+
"PATH",
37+
"N8N_RUNNERS_LAUNCHER_LOG_LEVEL",
38+
"N8N_SENTRY_DSN",
39+
"N8N_VERSION",
40+
"ENVIRONMENT",
41+
"DEPLOYMENT_NAME"
42+
],
43+
"env-overrides": {
44+
"PYTHONPATH": "/opt/runners/task-runner-python"
45+
}
46+
}
47+
]
48+
}

packages/@n8n/task-runner/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"acorn-walk": "8.3.4",
4343
"lodash": "catalog:",
4444
"luxon": "catalog:",
45+
"moment": "2.30.1",
4546
"n8n-core": "workspace:*",
4647
"n8n-workflow": "workspace:*",
4748
"nanoid": "catalog:",

0 commit comments

Comments
 (0)