Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 171 additions & 6 deletions .github/workflows/docker-build-push.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# This workflow is used to build and push the Docker image for n8n
# This workflow is used to build and push the Docker image for n8nio/n8n and n8nio/runners
# - determine-build-context: Determines what needs to be built based on the trigger
# - build-and-push-docker: This builds on both an ARM64 and AMD64 runner so the builds are native to the platform. Uses blacksmith native runners and build-push-action
# - create_multi_arch_manifest: This creates the multi-arch manifest for the Docker image. Needed to recombine the images from the build-and-push-docker job since they are separate runners.
# - security-scan: This scans the Docker image for security vulnerabilities using Trivy.
# - security-scan: This scans the n8nio/n8n Docker image for security vulnerabilities using Trivy.
# - security-scan-runners: This scans the n8nio/runners Docker image for security vulnerabilities using Trivy.

name: 'Docker: Build and Push'

Expand Down Expand Up @@ -49,6 +50,7 @@ on:
paths:
- '.github/workflows/docker-build-push.yml'
- 'docker/images/n8n/Dockerfile'
- 'docker/images/runners/Dockerfile'

jobs:
determine-build-context:
Expand Down Expand Up @@ -168,6 +170,7 @@ jobs:
outputs:
image_ref: ${{ steps.determine-tags.outputs.primary_ghcr_manifest_tag }}
primary_ghcr_manifest_tag: ${{ steps.determine-tags.outputs.primary_ghcr_manifest_tag }}
runners_primary_ghcr_manifest_tag: ${{ steps.determine-runners-tags.outputs.primary_ghcr_manifest_tag }}
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
Expand Down Expand Up @@ -260,6 +263,69 @@ jobs:
echo "primary_ghcr_manifest_tag=${PRIMARY_GHCR_MANIFEST_TAG_VALUE}" >> "$GITHUB_OUTPUT"
fi
- name: Determine Docker tags (runners)
id: determine-runners-tags
run: |
RELEASE_TYPE="${{ needs.determine-build-context.outputs.release_type }}"
N8N_VERSION_TAG="${{ needs.determine-build-context.outputs.n8n_version }}"
GHCR_BASE="ghcr.io/${{ github.repository_owner }}/runners"
DOCKER_BASE="${{ secrets.DOCKER_USERNAME }}/runners"
PLATFORM="${{ matrix.platform }}"
GHCR_TAGS_FOR_PUSH=""
DOCKER_TAGS_FOR_PUSH=""
PRIMARY_GHCR_MANIFEST_TAG_VALUE=""
case "$RELEASE_TYPE" in
"stable")
PRIMARY_GHCR_MANIFEST_TAG_VALUE="${GHCR_BASE}:${N8N_VERSION_TAG}"
GHCR_TAGS_FOR_PUSH="${PRIMARY_GHCR_MANIFEST_TAG_VALUE}-${PLATFORM}"
DOCKER_TAGS_FOR_PUSH="${DOCKER_BASE}:${N8N_VERSION_TAG}-${PLATFORM}"
;;
"nightly")
PRIMARY_GHCR_MANIFEST_TAG_VALUE="${GHCR_BASE}:nightly"
GHCR_TAGS_FOR_PUSH="${PRIMARY_GHCR_MANIFEST_TAG_VALUE}-${PLATFORM}"
DOCKER_TAGS_FOR_PUSH="${DOCKER_BASE}:nightly-${PLATFORM}"
;;
"branch")
PRIMARY_GHCR_MANIFEST_TAG_VALUE="${GHCR_BASE}:${N8N_VERSION_TAG}"
GHCR_TAGS_FOR_PUSH="${PRIMARY_GHCR_MANIFEST_TAG_VALUE}-${PLATFORM}"
DOCKER_TAGS_FOR_PUSH="" # mirror n8n logic: no Docker Hub for branch
;;
"dev"|*)
if [[ "$N8N_VERSION_TAG" == pr-* ]]; then
PRIMARY_GHCR_MANIFEST_TAG_VALUE="${GHCR_BASE}:${N8N_VERSION_TAG}"
GHCR_TAGS_FOR_PUSH="${PRIMARY_GHCR_MANIFEST_TAG_VALUE}-${PLATFORM}"
DOCKER_TAGS_FOR_PUSH=""
else
PRIMARY_GHCR_MANIFEST_TAG_VALUE="${GHCR_BASE}:dev"
GHCR_TAGS_FOR_PUSH="${PRIMARY_GHCR_MANIFEST_TAG_VALUE}-${PLATFORM}"
DOCKER_TAGS_FOR_PUSH="${DOCKER_BASE}:dev-${PLATFORM}"
fi
;;
esac
ALL_TAGS="${GHCR_TAGS_FOR_PUSH}"
if [[ -n "$DOCKER_TAGS_FOR_PUSH" ]]; then
ALL_TAGS="${ALL_TAGS}\n${DOCKER_TAGS_FOR_PUSH}"
fi
{
echo "tags<<EOF"
echo -e "$ALL_TAGS"
echo "EOF"
} >> "$GITHUB_OUTPUT"
{
echo "ghcr_platform_tag=${GHCR_TAGS_FOR_PUSH}"
echo "dockerhub_platform_tag=${DOCKER_TAGS_FOR_PUSH}"
} >> "$GITHUB_OUTPUT"
# Only output manifest tags from the first platform to avoid duplicates
if [[ "$PLATFORM" == "amd64" ]]; then
echo "primary_ghcr_manifest_tag=${PRIMARY_GHCR_MANIFEST_TAG_VALUE}" >> "$GITHUB_OUTPUT"
fi
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0

Expand All @@ -271,14 +337,16 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Login to DockerHub
if: needs.determine-build-context.outputs.push_enabled == 'true' && steps.determine-tags.outputs.dockerhub_platform_tag != ''
- name: Login to Docker Hub
if: needs.determine-build-context.outputs.push_enabled == 'true' && (
steps.determine-tags.outputs.dockerhub_platform_tag != '' ||
steps.determine-runners-tags.outputs.dockerhub_platform_tag != '')
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Build and push Docker image
- name: Build and push n8n Docker image
uses: useblacksmith/build-push-action@574eb0ee0b59c6a687ace24192f0727dfb65d6d7 # v1.2
with:
context: .
Expand All @@ -293,6 +361,22 @@ jobs:
push: ${{ needs.determine-build-context.outputs.push_enabled == 'true' }}
tags: ${{ steps.determine-tags.outputs.tags }}

- name: Build and push task runners Docker image
uses: useblacksmith/build-push-action@574eb0ee0b59c6a687ace24192f0727dfb65d6d7 # v1.2
with:
context: .
file: ./docker/images/runners/Dockerfile
build-args: |
NODE_VERSION=22.19
PYTHON_VERSION=3.13
N8N_VERSION=${{ needs.determine-build-context.outputs.n8n_version }}
N8N_RELEASE_TYPE=${{ needs.determine-build-context.outputs.release_type }}
platforms: ${{ matrix.docker_platform }}
provenance: true
sbom: true
push: ${{ needs.determine-build-context.outputs.push_enabled == 'true' }}
tags: ${{ steps.determine-runners-tags.outputs.tags }}

create_multi_arch_manifest:
name: Create Multi-Arch Manifest
needs: [determine-build-context, build-and-push-docker]
Expand Down Expand Up @@ -347,8 +431,45 @@ jobs:
;;
esac
- name: Determine Docker Hub manifest tag (runners)
id: dockerhub_runners_check
run: |
RELEASE_TYPE="${{ needs.determine-build-context.outputs.release_type }}"
N8N_VERSION="${{ needs.determine-build-context.outputs.n8n_version }}"
DOCKER_BASE="${{ secrets.DOCKER_USERNAME }}/runners"
# Determine if Docker Hub manifest is needed and construct the tag
case "$RELEASE_TYPE" in
"stable")
{
echo "DOCKER_MANIFEST_TAG=${DOCKER_BASE}:${N8N_VERSION}"
echo "CREATE_DOCKERHUB_MANIFEST=true"
} >> "$GITHUB_OUTPUT"
;;
"nightly")
{
echo "DOCKER_MANIFEST_TAG=${DOCKER_BASE}:nightly"
echo "CREATE_DOCKERHUB_MANIFEST=true"
} >> "$GITHUB_OUTPUT"
;;
"dev")
if [[ "$N8N_VERSION" != pr-* ]]; then
{
echo "DOCKER_MANIFEST_TAG=${DOCKER_BASE}:dev"
echo "CREATE_DOCKERHUB_MANIFEST=true"
} >> "$GITHUB_OUTPUT"
else
echo "CREATE_DOCKERHUB_MANIFEST=false" >> "$GITHUB_OUTPUT"
fi
;;
*)
echo "CREATE_DOCKERHUB_MANIFEST=false" >> "$GITHUB_OUTPUT"
;;
esac
- name: Login to Docker Hub
if: steps.dockerhub_check.outputs.CREATE_DOCKERHUB_MANIFEST == 'true'
if: steps.dockerhub_check.outputs.CREATE_DOCKERHUB_MANIFEST == 'true' ||
steps.dockerhub_runners_check.outputs.CREATE_DOCKERHUB_MANIFEST == 'true'
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
Expand All @@ -374,6 +495,26 @@ jobs:
${MANIFEST_TAG}-arm64
fi
- name: Create GHCR multi-arch manifest (runners)
if: needs.build-and-push-docker.outputs.runners_primary_ghcr_manifest_tag != ''
run: |
MANIFEST_TAG="${{ needs.build-and-push-docker.outputs.runners_primary_ghcr_manifest_tag }}"
RELEASE_TYPE="${{ needs.determine-build-context.outputs.release_type }}"
echo "Creating GHCR runners manifest: $MANIFEST_TAG"
# For branch builds, only AMD64 is built
if [[ "$RELEASE_TYPE" == "branch" ]]; then
docker buildx imagetools create \
--tag $MANIFEST_TAG \
${MANIFEST_TAG}-amd64
else
docker buildx imagetools create \
--tag $MANIFEST_TAG \
${MANIFEST_TAG}-amd64 \
${MANIFEST_TAG}-arm64
fi
- name: Create Docker Hub multi-arch manifest
if: steps.dockerhub_check.outputs.CREATE_DOCKERHUB_MANIFEST == 'true'
run: |
Expand All @@ -386,6 +527,18 @@ jobs:
${MANIFEST_TAG}-amd64 \
${MANIFEST_TAG}-arm64
- name: Create Docker Hub multi-arch manifest (runners)
if: steps.dockerhub_runners_check.outputs.CREATE_DOCKERHUB_MANIFEST == 'true'
run: |
MANIFEST_TAG="${{ steps.dockerhub_runners_check.outputs.DOCKER_MANIFEST_TAG }}"
echo "Creating Docker Hub manifest: $MANIFEST_TAG"
docker buildx imagetools create \
--tag $MANIFEST_TAG \
${MANIFEST_TAG}-amd64 \
${MANIFEST_TAG}-arm64
call-success-url:
name: Call Success URL
needs: [create_multi_arch_manifest]
Expand All @@ -412,3 +565,15 @@ jobs:
with:
image_ref: ${{ needs.build-and-push-docker.outputs.image_ref }}
secrets: inherit

security-scan-runners:
name: Security Scan (runners)
needs: [determine-build-context, build-and-push-docker]
if: |
success() &&
(needs.determine-build-context.outputs.release_type == 'stable' ||
needs.determine-build-context.outputs.release_type == 'nightly')
uses: ./.github/workflows/security-trivy-scan-callable.yml
with:
image_ref: ${{ needs.build-and-push-docker.outputs.runners_primary_ghcr_manifest_tag }}
secrets: inherit
54 changes: 26 additions & 28 deletions docker/images/runners/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
ARG PYTHON_IMAGE=python:3.13-slim
ARG NODE_VERSION=22.19
ARG PYTHON_VERSION=3.13

# ==============================================================================
# STAGE 1: JavaScript runner (@n8n/task-runner) artifact from CI
Expand All @@ -8,25 +9,21 @@ COPY ./dist/task-runner-javascript /app/task-runner-javascript

# ==============================================================================
# STAGE 2: Python runner build (@n8n/task-runner-python) with uv
# Produces a relocatable venv tied to PYTHON_IMAGE
# Produces a relocatable venv tied to the python version used
# ==============================================================================
FROM ${PYTHON_IMAGE} AS python-runner-builder
FROM python:${PYTHON_VERSION}-alpine AS python-runner-builder
ARG TARGETPLATFORM
ARG UV_VERSION=0.8.14

RUN apt-get update && apt-get install -y --no-install-recommends \
curl ca-certificates build-essential pkg-config git \
&& rm -rf /var/lib/apt/lists/*

RUN set -e; \
case "$TARGETPLATFORM" in \
"linux/amd64") UV_ARCH="x86_64-unknown-linux-gnu" ;; \
"linux/arm64") UV_ARCH="aarch64-unknown-linux-gnu" ;; \
"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; \
curl -fsSLO "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${UV_ARCH}.tar.gz"; \
curl -fsSLO "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${UV_ARCH}.tar.gz.sha256"; \
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; \
Expand Down Expand Up @@ -74,31 +71,32 @@ RUN set -e; \
cd / && rm -rf /launcher-temp

# ==============================================================================
# STAGE 4: Runtime
# STAGE 4: Node alpine base for JS task runner
# ==============================================================================
FROM node:${NODE_VERSION}-alpine AS node-alpine

# ==============================================================================
# STAGE 5: Runtime
# ==============================================================================
FROM ${PYTHON_IMAGE} AS runtime
ARG NODE_VERSION=22
FROM python:${PYTHON_VERSION}-alpine AS runtime
ARG N8N_VERSION=snapshot
ARG N8N_RELEASE_TYPE=dev

ENV NODE_ENV=production
ENV N8N_RELEASE_TYPE=${N8N_RELEASE_TYPE}
ENV SHELL=/bin/sh

RUN apt-get update \
&& apt-get install -y --no-install-recommends curl gnupg ca-certificates tini \
&& mkdir -p /etc/apt/keyrings \
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
| gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_VERSION}.x nodistro main" \
> /etc/apt/sources.list.d/nodesource.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends nodejs \
&& apt-get remove curl -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*.deb

RUN useradd -m -u 1000 runner
# Copy over node from node alpine
COPY --from=node-alpine /usr/local/bin/node /usr/local/bin/node
COPY --from=node-alpine /usr/local/bin/npm /usr/local/bin/npm
COPY --from=node-alpine /usr/local/bin/npx /usr/local/bin/npx
COPY --from=node-alpine /usr/local/lib/node_modules /usr/local/lib/node_modules

# Node needs libstdc++
RUN apk add --no-cache ca-certificates tini libstdc++

RUN addgroup -g 1000 -S runner \
&& adduser -u 1000 -S -G runner -h /home/runner -D runner
WORKDIR /home/runner

COPY --from=app-artifact-processor /app/task-runner-javascript /opt/runners/task-runner-javascript
Expand Down
13 changes: 10 additions & 3 deletions docker/images/runners/n8n-task-runners.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{
"runner-type": "javascript",
"workdir": "/home/runner",
"command": "/usr/bin/node",
"command": "/usr/local/bin/node",
"args": [
"--disallow-code-generation-from-strings",
"--disable-proto=delete",
Expand All @@ -13,8 +13,10 @@
"allowed-env": [
"PATH",
"GENERIC_TIMEZONE",
"N8N_RUNNERS_MAX_CONCURRENCY",
"NODE_OPTIONS",
"N8N_RUNNERS_AUTO_SHUTDOWN_TIMEOUT",
"N8N_RUNNERS_TASK_TIMEOUT",
"N8N_RUNNERS_MAX_CONCURRENCY",
"N8N_SENTRY_DSN",
"N8N_VERSION",
"ENVIRONMENT",
Expand All @@ -35,13 +37,18 @@
"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": {
"PYTHONPATH": "/opt/runners/task-runner-python"
"PYTHONPATH": "/opt/runners/task-runner-python",
"N8N_RUNNERS_STDLIB_ALLOW": "",
"N8N_RUNNERS_EXTERNAL_ALLOW": ""
}
}
]
Expand Down
Loading