mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 09:36:44 +00:00
ci: Docker move build stage outside container (no-changelog) (#16009)
This commit is contained in:
@@ -12,7 +12,6 @@ packages/**/*.test.*
|
||||
.github
|
||||
!.github/scripts
|
||||
*.tsbuildinfo
|
||||
packages/cli/dist/**/e2e.*
|
||||
docker/compose
|
||||
docker/**/Dockerfile
|
||||
.vscode
|
||||
|
||||
332
.github/workflows/docker-build-push.yml
vendored
Normal file
332
.github/workflows/docker-build-push.yml
vendored
Normal file
@@ -0,0 +1,332 @@
|
||||
# This workflow is used to build and push the Docker image for n8n
|
||||
# - 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.
|
||||
|
||||
name: 'Docker: Build and Push'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
workflow_call:
|
||||
inputs:
|
||||
n8n_version:
|
||||
description: 'N8N version to build'
|
||||
required: true
|
||||
type: string
|
||||
release_type:
|
||||
description: 'Release type (stable, nightly, dev)'
|
||||
required: true
|
||||
type: string
|
||||
default: 'dev'
|
||||
push_enabled:
|
||||
description: 'Whether to push the built images'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_type:
|
||||
description: 'Release type'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- nightly
|
||||
- dev
|
||||
- stable
|
||||
- branch
|
||||
default: 'dev'
|
||||
push_to_registry:
|
||||
description: 'Push image to registry'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- ready_for_review
|
||||
paths:
|
||||
- '.github/workflows/docker-build-push.yml'
|
||||
- 'docker/images/n8n/Dockerfile'
|
||||
|
||||
jobs:
|
||||
build-and-push-docker:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [amd64, arm64]
|
||||
include:
|
||||
- platform: amd64
|
||||
runner: blacksmith-4vcpu-ubuntu-2204
|
||||
docker_platform: linux/amd64
|
||||
- platform: arm64
|
||||
runner: blacksmith-4vcpu-ubuntu-2204-arm
|
||||
docker_platform: linux/arm64
|
||||
|
||||
name: Build App, then Build and Push Docker Image (${{ matrix.platform }})
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 15
|
||||
outputs:
|
||||
image_ref: ${{ steps.determine-tags.outputs.primary_tag }}
|
||||
primary_ghcr_manifest_tag: ${{ steps.determine-tags.outputs.primary_ghcr_manifest_tag }}
|
||||
push_enabled_status: ${{ steps.context.outputs.push_enabled }}
|
||||
release_type: ${{ steps.context.outputs.release_type }}
|
||||
n8n_version: ${{ steps.context.outputs.n8n_version }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: useblacksmith/setup-node@65c6ca86fdeb0ab3d85e78f57e4f6a7e4780b391 # v5.0.4
|
||||
with:
|
||||
node-version: 22.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
shell: bash
|
||||
|
||||
- name: Configure Turborepo Cache
|
||||
uses: useblacksmith/caching-for-turbo@bafb57e7ebdbf1185762286ec94d24648cd3938a # v1
|
||||
|
||||
- name: Build n8n for Docker
|
||||
run: pnpm build:n8n
|
||||
shell: bash
|
||||
|
||||
- name: Determine build context values
|
||||
id: context
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "schedule" ]]; then
|
||||
echo "release_type=nightly" >> $GITHUB_OUTPUT
|
||||
echo "n8n_version=snapshot" >> $GITHUB_OUTPUT
|
||||
echo "push_enabled=true" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ github.event_name }}" == "workflow_call" ]]; then
|
||||
echo "release_type=${{ inputs.release_type }}" >> $GITHUB_OUTPUT
|
||||
echo "n8n_version=${{ inputs.n8n_version }}" >> $GITHUB_OUTPUT
|
||||
echo "push_enabled=${{ inputs.push_enabled }}" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
if [[ "${{ inputs.release_type }}" == "branch" ]]; then
|
||||
BRANCH_NAME="${{ github.ref_name }}"
|
||||
SAFE_BRANCH_NAME=$(echo "$BRANCH_NAME" | tr '/' '-' | tr -cd '[:alnum:]-_')
|
||||
echo "release_type=branch" >> $GITHUB_OUTPUT
|
||||
echo "n8n_version=branch-${SAFE_BRANCH_NAME}" >> $GITHUB_OUTPUT
|
||||
echo "push_enabled=${{ inputs.push_to_registry }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "release_type=${{ inputs.release_type }}" >> $GITHUB_OUTPUT
|
||||
echo "n8n_version=snapshot" >> $GITHUB_OUTPUT
|
||||
echo "push_enabled=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
elif [[ "${{ github.event_name }}" == "push" ]]; then
|
||||
echo "release_type=dev" >> $GITHUB_OUTPUT
|
||||
echo "n8n_version=snapshot" >> $GITHUB_OUTPUT
|
||||
echo "push_enabled=true" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
echo "release_type=dev" >> $GITHUB_OUTPUT
|
||||
echo "n8n_version=pr-${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
|
||||
echo "push_enabled=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "release_type=dev" >> $GITHUB_OUTPUT
|
||||
echo "n8n_version=snapshot" >> $GITHUB_OUTPUT
|
||||
echo "push_enabled=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Determine Docker tags
|
||||
id: determine-tags
|
||||
run: |
|
||||
RELEASE_TYPE="${{ steps.context.outputs.release_type }}"
|
||||
N8N_VERSION_TAG="${{ steps.context.outputs.n8n_version }}"
|
||||
GHCR_BASE="ghcr.io/${{ github.repository_owner }}/n8n"
|
||||
DOCKER_BASE="${{ secrets.DOCKER_USERNAME }}/n8n"
|
||||
PLATFORM="${{ matrix.platform }}"
|
||||
|
||||
GHCR_TAGS_FOR_PUSH=""
|
||||
DOCKER_TAGS_FOR_PUSH=""
|
||||
|
||||
PRIMARY_GHCR_MANIFEST_TAG_VALUE=""
|
||||
PRIMARY_DOCKER_MANIFEST_TAG_VALUE=""
|
||||
|
||||
if [[ "$RELEASE_TYPE" == "stable" && -z "$N8N_VERSION_TAG" ]]; then
|
||||
echo "Error: N8N_VERSION_TAG is empty for a stable release."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$RELEASE_TYPE" in
|
||||
"stable")
|
||||
PRIMARY_GHCR_MANIFEST_TAG_VALUE="${GHCR_BASE}:${N8N_VERSION_TAG}"
|
||||
PRIMARY_DOCKER_MANIFEST_TAG_VALUE="${DOCKER_BASE}:${N8N_VERSION_TAG}"
|
||||
GHCR_TAGS_FOR_PUSH="${PRIMARY_GHCR_MANIFEST_TAG_VALUE}-${PLATFORM}"
|
||||
DOCKER_TAGS_FOR_PUSH="${PRIMARY_DOCKER_MANIFEST_TAG_VALUE}-${PLATFORM}"
|
||||
;;
|
||||
"nightly")
|
||||
PRIMARY_GHCR_MANIFEST_TAG_VALUE="${GHCR_BASE}:nightly"
|
||||
PRIMARY_DOCKER_MANIFEST_TAG_VALUE="${DOCKER_BASE}:nightly"
|
||||
GHCR_TAGS_FOR_PUSH="${PRIMARY_GHCR_MANIFEST_TAG_VALUE}-${PLATFORM}"
|
||||
DOCKER_TAGS_FOR_PUSH="${PRIMARY_DOCKER_MANIFEST_TAG_VALUE}-${PLATFORM}"
|
||||
;;
|
||||
"branch")
|
||||
PRIMARY_GHCR_MANIFEST_TAG_VALUE="${GHCR_BASE}:${N8N_VERSION_TAG}"
|
||||
GHCR_TAGS_FOR_PUSH="${PRIMARY_GHCR_MANIFEST_TAG_VALUE}-${PLATFORM}"
|
||||
PRIMARY_DOCKER_MANIFEST_TAG_VALUE=""
|
||||
DOCKER_TAGS_FOR_PUSH=""
|
||||
;;
|
||||
"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}"
|
||||
PRIMARY_DOCKER_MANIFEST_TAG_VALUE=""
|
||||
DOCKER_TAGS_FOR_PUSH=""
|
||||
else
|
||||
PRIMARY_GHCR_MANIFEST_TAG_VALUE="${GHCR_BASE}:dev"
|
||||
PRIMARY_DOCKER_MANIFEST_TAG_VALUE="${DOCKER_BASE}:dev"
|
||||
GHCR_TAGS_FOR_PUSH="${PRIMARY_GHCR_MANIFEST_TAG_VALUE}-${PLATFORM}"
|
||||
DOCKER_TAGS_FOR_PUSH="${PRIMARY_DOCKER_MANIFEST_TAG_VALUE}-${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 "Generated Tags for push: $ALL_TAGS"
|
||||
echo "tags<<EOF" >> $GITHUB_OUTPUT
|
||||
echo -e "$ALL_TAGS" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "ghcr_platform_tag=${GHCR_TAGS_FOR_PUSH}" >> $GITHUB_OUTPUT
|
||||
echo "dockerhub_platform_tag=${DOCKER_TAGS_FOR_PUSH}" >> $GITHUB_OUTPUT
|
||||
echo "primary_ghcr_manifest_tag=${PRIMARY_GHCR_MANIFEST_TAG_VALUE}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: steps.context.outputs.push_enabled == 'true'
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: steps.context.outputs.push_enabled == 'true'
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: useblacksmith/build-push-action@6fe3b1c3665ca911656e8249f6195103b7dc9782 # v1.2
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/images/n8n/Dockerfile
|
||||
build-args: |
|
||||
NODE_VERSION=22
|
||||
N8N_VERSION=${{ steps.context.outputs.n8n_version }}
|
||||
N8N_RELEASE_TYPE=${{ steps.context.outputs.release_type }}
|
||||
platforms: ${{ matrix.docker_platform }}
|
||||
provenance: false
|
||||
push: ${{ steps.context.outputs.push_enabled }}
|
||||
tags: ${{ steps.determine-tags.outputs.tags }}
|
||||
|
||||
create_multi_arch_manifest:
|
||||
name: Create Multi-Arch Manifest
|
||||
needs: build-and-push-docker
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
needs.build-and-push-docker.result == 'success' &&
|
||||
needs.build-and-push-docker.outputs.push_enabled_status == 'true'
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Reconstruct Docker Hub Primary Tag
|
||||
id: reconstruct_dockerhub_tag
|
||||
run: |
|
||||
RELEASE_TYPE="${{ needs.build-and-push-docker.outputs.release_type }}"
|
||||
N8N_VERSION="${{ needs.build-and-push-docker.outputs.n8n_version }}"
|
||||
DOCKER_BASE="${{ secrets.DOCKER_USERNAME }}/n8n"
|
||||
|
||||
PRIMARY_DOCKER_MANIFEST_TAG=""
|
||||
|
||||
case "$RELEASE_TYPE" in
|
||||
"stable")
|
||||
PRIMARY_DOCKER_MANIFEST_TAG="${DOCKER_BASE}:${N8N_VERSION}"
|
||||
;;
|
||||
"nightly")
|
||||
PRIMARY_DOCKER_MANIFEST_TAG="${DOCKER_BASE}:nightly"
|
||||
;;
|
||||
"dev")
|
||||
if [[ "$N8N_VERSION" != pr-* ]]; then
|
||||
PRIMARY_DOCKER_MANIFEST_TAG="${DOCKER_BASE}:dev"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -n "$PRIMARY_DOCKER_MANIFEST_TAG" ]]; then
|
||||
echo "PRIMARY_DOCKER_MANIFEST_TAG=$PRIMARY_DOCKER_MANIFEST_TAG" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "::notice::No Docker Hub primary tag to reconstruct for release type '$RELEASE_TYPE' and version '$N8N_VERSION'. Skipping Docker Hub manifest creation."
|
||||
fi
|
||||
|
||||
- name: Create GHCR multi-arch manifest
|
||||
if: needs.build-and-push-docker.outputs.primary_ghcr_manifest_tag != ''
|
||||
run: |
|
||||
MANIFEST_TAG="${{ needs.build-and-push-docker.outputs.primary_ghcr_manifest_tag }}"
|
||||
|
||||
echo "Creating GHCR manifest: $MANIFEST_TAG"
|
||||
|
||||
# Create and push the multi-arch manifest using buildx
|
||||
docker buildx imagetools create \
|
||||
--tag $MANIFEST_TAG \
|
||||
${MANIFEST_TAG}-amd64 \
|
||||
${MANIFEST_TAG}-arm64
|
||||
|
||||
# Create Docker Hub multi-arch manifest
|
||||
- name: Create Docker Hub multi-arch manifest
|
||||
if: env.PRIMARY_DOCKER_MANIFEST_TAG != ''
|
||||
run: |
|
||||
MANIFEST_TAG="${{ env.PRIMARY_DOCKER_MANIFEST_TAG }}"
|
||||
|
||||
echo "Creating Docker Hub manifest: $MANIFEST_TAG"
|
||||
|
||||
# Create and push the multi-arch manifest using buildx
|
||||
docker buildx imagetools create \
|
||||
--tag $MANIFEST_TAG \
|
||||
${MANIFEST_TAG}-amd64 \
|
||||
${MANIFEST_TAG}-arm64
|
||||
|
||||
security-scan:
|
||||
name: Security Scan
|
||||
needs: [build-and-push-docker]
|
||||
if: |
|
||||
success() &&
|
||||
(github.event_name == 'schedule' ||
|
||||
(github.event_name == 'workflow_call' && inputs.release_type == 'stable'))
|
||||
uses: ./.github/workflows/security-trivy-scan-callable.yml
|
||||
with:
|
||||
image_ref: ${{ needs.build-and-push-docker.outputs.image_ref }}
|
||||
secrets:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
83
.github/workflows/docker-images-custom.yml
vendored
83
.github/workflows/docker-images-custom.yml
vendored
@@ -1,83 +0,0 @@
|
||||
name: Docker Custom Image CI
|
||||
run-name: Build ${{ inputs.branch }} - ${{ inputs.user }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: 'GitHub branch to create image off.'
|
||||
required: true
|
||||
tag:
|
||||
description: 'Name of the docker tag to create.'
|
||||
required: true
|
||||
merge-master:
|
||||
description: 'Merge with master.'
|
||||
type: boolean
|
||||
required: true
|
||||
default: false
|
||||
user:
|
||||
description: ''
|
||||
required: false
|
||||
default: 'none'
|
||||
start-url:
|
||||
description: 'URL to call after workflow is kicked off.'
|
||||
required: false
|
||||
default: ''
|
||||
success-url:
|
||||
description: 'URL to call after Docker Image got built successfully.'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Call Start URL - optionally
|
||||
if: ${{ github.event.inputs.start-url != '' }}
|
||||
run: curl -v -X POST -d 'url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' ${{github.event.inputs.start-url}} || echo ""
|
||||
shell: bash
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
ref: ${{ github.event.inputs.branch }}
|
||||
|
||||
- name: Merge Master - optionally
|
||||
if: github.event.inputs.merge-master
|
||||
run: git remote add upstream https://github.com/n8n-io/n8n.git -f; git merge upstream/master --allow-unrelated-histories || echo ""
|
||||
shell: bash
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push image to GHCR
|
||||
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/images/n8n/Dockerfile
|
||||
build-args: |
|
||||
N8N_RELEASE_TYPE=development
|
||||
platforms: linux/amd64
|
||||
provenance: false
|
||||
push: true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
tags: ghcr.io/${{ github.repository_owner }}/n8n:${{ inputs.tag }}
|
||||
|
||||
- name: Call Success URL - optionally
|
||||
if: ${{ github.event.inputs.success-url != '' }}
|
||||
run: curl -v ${{github.event.inputs.success-url}} || echo ""
|
||||
shell: bash
|
||||
58
.github/workflows/docker-images-nightly.yml
vendored
58
.github/workflows/docker-images-nightly.yml
vendored
@@ -1,58 +0,0 @@
|
||||
name: Docker Nightly Image CI
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push image to GHCR and DockerHub
|
||||
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/images/n8n/Dockerfile
|
||||
build-args: |
|
||||
N8N_RELEASE_TYPE=nightly
|
||||
platforms: linux/amd64,linux/arm64
|
||||
provenance: false
|
||||
push: true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository_owner }}/n8n:nightly
|
||||
${{ secrets.DOCKER_USERNAME }}/n8n:nightly
|
||||
|
||||
security-scan:
|
||||
needs: build
|
||||
uses: ./.github/workflows/security-trivy-scan-callable.yml
|
||||
with:
|
||||
image_ref: ghcr.io/${{ github.repository_owner }}/n8n:nightly
|
||||
secrets:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
59
.github/workflows/release-publish.yml
vendored
59
.github/workflows/release-publish.yml
vendored
@@ -72,62 +72,11 @@ jobs:
|
||||
publish-to-docker-hub:
|
||||
name: Publish to DockerHub
|
||||
needs: [publish-to-npm]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.merged == true
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
uses: ./.github/workflows/docker-build-push.yml
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@53851d14592bedcffcf25ea515637cff71ef929a # v3.3.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
with:
|
||||
context: .
|
||||
file: docker/images/n8n/Dockerfile
|
||||
build-args: |
|
||||
N8N_VERSION=${{ needs.publish-to-npm.outputs.release }}
|
||||
N8N_RELEASE_TYPE=stable
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
platforms: linux/amd64,linux/arm64
|
||||
provenance: false
|
||||
push: true
|
||||
tags: |
|
||||
${{ secrets.DOCKER_USERNAME }}/n8n:${{ needs.publish-to-npm.outputs.release }}
|
||||
ghcr.io/${{ github.repository_owner }}/n8n:${{ needs.publish-to-npm.outputs.release }}
|
||||
|
||||
security-scan:
|
||||
name: Security Scan Release Image
|
||||
needs: [publish-to-npm, publish-to-docker-hub]
|
||||
uses: ./.github/workflows/security-trivy-scan-callable.yml
|
||||
with:
|
||||
image_ref: ghcr.io/${{ github.repository_owner }}/n8n:${{ needs.publish-to-npm.outputs.release }}
|
||||
secrets:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
n8n_version: ${{ needs.publish-to-npm.outputs.release }}
|
||||
release_type: stable
|
||||
secrets: inherit
|
||||
|
||||
create-github-release:
|
||||
name: Create a GitHub Release
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -26,3 +26,7 @@ build-storybook.log
|
||||
junit.xml
|
||||
test-results.json
|
||||
*.0x
|
||||
compiled_app_output
|
||||
trivy_report*
|
||||
compiled
|
||||
jest.config.js
|
||||
@@ -1,37 +1,34 @@
|
||||
ARG NODE_VERSION=22
|
||||
|
||||
# 1. Use a builder step to download various dependencies
|
||||
# ==============================================================================
|
||||
# STAGE 1: Builder for Base Dependencies
|
||||
# ==============================================================================
|
||||
FROM node:${NODE_VERSION}-alpine AS builder
|
||||
|
||||
# Install fonts
|
||||
RUN \
|
||||
apk --no-cache add --virtual fonts msttcorefonts-installer fontconfig && \
|
||||
apk --no-cache add --virtual .build-deps-fonts msttcorefonts-installer fontconfig && \
|
||||
update-ms-fonts && \
|
||||
fc-cache -f && \
|
||||
apk del fonts && \
|
||||
apk del .build-deps-fonts && \
|
||||
find /usr/share/fonts/truetype/msttcorefonts/ -type l -exec unlink {} \;
|
||||
|
||||
# Install git and other OS dependencies
|
||||
RUN apk add --update git openssh graphicsmagick tini tzdata ca-certificates libc6-compat jq
|
||||
# Install essential OS dependencies
|
||||
RUN apk add --no-cache git openssh graphicsmagick tini tzdata ca-certificates libc6-compat jq
|
||||
|
||||
# Update npm and install full-uci
|
||||
COPY .npmrc /usr/local/etc/npmrc
|
||||
RUN npm install -g corepack@0.33 full-icu@1.5.0
|
||||
# Update npm, install full-icu and npm@11.4.2 to fix brace-expansion vulnerability
|
||||
# Remove npm update after vulnerability is fixed in in node image
|
||||
RUN npm install -g full-icu@1.5.0 npm@11.4.2
|
||||
|
||||
# Activate corepack, and install pnpm
|
||||
WORKDIR /tmp
|
||||
COPY package.json ./
|
||||
RUN corepack enable && corepack prepare --activate
|
||||
RUN apk del apk-tools && \
|
||||
rm -rf /tmp/* /root/.npm /root/.cache/node /opt/yarn* /var/cache/apk/* /lib/apk/db
|
||||
|
||||
# Cleanup
|
||||
RUN rm -rf /lib/apk/db /var/cache/apk/ /tmp/* /root/.npm /root/.cache/node /opt/yarn*
|
||||
|
||||
# 2. Start with a new clean image and copy over the added files into a single layer
|
||||
# ==============================================================================
|
||||
# STAGE 2: Final Base Runtime Image
|
||||
# ==============================================================================
|
||||
FROM node:${NODE_VERSION}-alpine
|
||||
COPY --from=builder / /
|
||||
|
||||
# Delete this folder to make the base image backward compatible to be able to build older version images
|
||||
RUN rm -rf /tmp/v8-compile-cache*
|
||||
COPY --from=builder / /
|
||||
|
||||
WORKDIR /home/node
|
||||
ENV NODE_ICU_DATA=/usr/local/lib/node_modules/full-icu
|
||||
|
||||
@@ -1,81 +1,75 @@
|
||||
ARG NODE_VERSION=22
|
||||
|
||||
# 1. Create an image to build n8n
|
||||
FROM --platform=linux/amd64 n8nio/base:${NODE_VERSION} AS builder
|
||||
|
||||
# Build the application from source
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store --mount=type=cache,id=pnpm-metadata,target=/root/.cache/pnpm/metadata DOCKER_BUILD=true pnpm install --frozen-lockfile
|
||||
RUN pnpm build
|
||||
|
||||
# Delete all dev dependencies
|
||||
RUN node .github/scripts/trim-fe-packageJson.js
|
||||
# We don't want to remove all patches because we want them still to be applied
|
||||
# in `pnpm deploy`. However, we need to remove FE patches because we trim the FE
|
||||
# package.json files and `pnpm deploy` will fail otherwise. element-plus is the
|
||||
# only FE patch that we need to remove.
|
||||
RUN jq '.pnpm.patchedDependencies |= with_entries(select(.key | startswith("pdfjs-dist") or startswith("pkce-challenge")))' package.json > package.json.tmp; mv package.json.tmp package.json
|
||||
|
||||
# Delete any source code or typings
|
||||
RUN find . -type f -name "*.ts" -o -name "*.vue" -o -name "tsconfig.json" -o -name "*.tsbuildinfo" | xargs rm -rf
|
||||
|
||||
# Deploy the `n8n` package into /compiled
|
||||
RUN mkdir /compiled
|
||||
RUN NODE_ENV=production DOCKER_BUILD=true pnpm --filter=n8n --prod --no-optional --legacy deploy /compiled
|
||||
|
||||
# 2. Start with a new clean image with just the code that is needed to run n8n
|
||||
FROM n8nio/base:${NODE_VERSION}
|
||||
ENV NODE_ENV=production
|
||||
|
||||
ARG N8N_VERSION=snapshot
|
||||
ARG N8N_RELEASE_TYPE=dev
|
||||
ENV N8N_RELEASE_TYPE=${N8N_RELEASE_TYPE}
|
||||
ARG LAUNCHER_VERSION=1.1.3
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
LABEL org.opencontainers.image.title="n8n"
|
||||
LABEL org.opencontainers.image.description="Workflow Automation Tool"
|
||||
LABEL org.opencontainers.image.source="https://github.com/n8n-io/n8n"
|
||||
LABEL org.opencontainers.image.url="https://n8n.io"
|
||||
LABEL org.opencontainers.image.version=${N8N_VERSION}
|
||||
# ==============================================================================
|
||||
# STAGE 1: System Dependencies & Base Setup
|
||||
# ==============================================================================
|
||||
FROM n8nio/base:${NODE_VERSION} AS system-deps
|
||||
|
||||
# ==============================================================================
|
||||
# STAGE 2: Application Artifact Processor
|
||||
# ==============================================================================
|
||||
FROM alpine:3.22.0 AS app-artifact-processor
|
||||
|
||||
COPY ./compiled /app/
|
||||
|
||||
# ==============================================================================
|
||||
# STAGE 3: Task Runner Launcher
|
||||
# ==============================================================================
|
||||
FROM alpine:3.22.0 AS launcher-downloader
|
||||
ARG TARGETPLATFORM
|
||||
ARG LAUNCHER_VERSION
|
||||
|
||||
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: Final Runtime Image
|
||||
# ==============================================================================
|
||||
FROM system-deps AS runtime
|
||||
|
||||
ARG N8N_VERSION
|
||||
ARG N8N_RELEASE_TYPE=dev
|
||||
ENV NODE_ENV=production
|
||||
ENV N8N_RELEASE_TYPE=${N8N_RELEASE_TYPE}
|
||||
ENV NODE_ICU_DATA=/usr/local/lib/node_modules/full-icu
|
||||
ENV SHELL=/bin/sh
|
||||
|
||||
WORKDIR /home/node
|
||||
COPY --from=builder /compiled /usr/local/lib/node_modules/n8n
|
||||
|
||||
COPY --from=app-artifact-processor /app /usr/local/lib/node_modules/n8n
|
||||
COPY --from=launcher-downloader /launcher-bin/* /usr/local/bin/
|
||||
COPY docker/images/n8n/docker-entrypoint.sh /
|
||||
|
||||
# Setup the Task Runner Launcher
|
||||
ARG TARGETPLATFORM
|
||||
ARG LAUNCHER_VERSION=1.1.3
|
||||
COPY docker/images/n8n/n8n-task-runners.json /etc/n8n-task-runners.json
|
||||
# Download, verify, then extract the launcher binary
|
||||
RUN \
|
||||
if [[ "$TARGETPLATFORM" = "linux/amd64" ]]; then export ARCH_NAME="amd64"; \
|
||||
elif [[ "$TARGETPLATFORM" = "linux/arm64" ]]; then export ARCH_NAME="arm64"; fi; \
|
||||
mkdir /launcher-temp && \
|
||||
cd /launcher-temp && \
|
||||
wget https://github.com/n8n-io/task-runner-launcher/releases/download/${LAUNCHER_VERSION}/task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz && \
|
||||
wget https://github.com/n8n-io/task-runner-launcher/releases/download/${LAUNCHER_VERSION}/task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz.sha256 && \
|
||||
# The .sha256 does not contain the filename --> Form the correct checksum file
|
||||
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 && \
|
||||
tar xvf task-runner-launcher-${LAUNCHER_VERSION}-linux-${ARCH_NAME}.tar.gz --directory=/usr/local/bin && \
|
||||
cd - && \
|
||||
rm -r /launcher-temp
|
||||
|
||||
RUN \
|
||||
cd /usr/local/lib/node_modules/n8n && \
|
||||
npm rebuild sqlite3 && \
|
||||
cd - && \
|
||||
ln -s /usr/local/lib/node_modules/n8n/bin/n8n /usr/local/bin/n8n && \
|
||||
mkdir .n8n && \
|
||||
chown node:node .n8n
|
||||
RUN ln -s /usr/local/lib/node_modules/n8n/bin/n8n /usr/local/bin/n8n && \
|
||||
mkdir -p /home/node/.n8n && \
|
||||
chown -R node:node /home/node
|
||||
|
||||
# pdfjs-dist has an optional dependency on @napi-rs/canvas, which is required
|
||||
# for it to work.
|
||||
# Install npm@11.4.2 to fix brace-expansion vulnerability, remove after vulnerability is fixed in node image
|
||||
RUN npm install -g npm@11.4.2
|
||||
RUN cd /usr/local/lib/node_modules/n8n/node_modules/pdfjs-dist && npm install @napi-rs/canvas
|
||||
|
||||
# Install npm 11.4.1 to fix the vulnerable cross-spawn dependency
|
||||
RUN npm install -g npm@11.4.1
|
||||
|
||||
ENV SHELL /bin/sh
|
||||
EXPOSE 5678/tcp
|
||||
USER node
|
||||
ENTRYPOINT ["tini", "--", "/docker-entrypoint.sh"]
|
||||
|
||||
LABEL org.opencontainers.image.title="n8n" \
|
||||
org.opencontainers.image.description="Workflow Automation Tool" \
|
||||
org.opencontainers.image.source="https://github.com/n8n-io/n8n" \
|
||||
org.opencontainers.image.url="https://n8n.io" \
|
||||
org.opencontainers.image.version=${N8N_VERSION}
|
||||
@@ -14,6 +14,9 @@
|
||||
"build:backend": "turbo run build:backend",
|
||||
"build:frontend": "turbo run build:frontend",
|
||||
"build:nodes": "turbo run build:nodes",
|
||||
"build:n8n": "node scripts/build-n8n.mjs",
|
||||
"build:docker": "node scripts/build-n8n.mjs && node scripts/dockerize-n8n.mjs",
|
||||
"build:docker:scan": "node scripts/build-n8n.mjs && node scripts/dockerize-n8n.mjs && node scripts/scan-n8n-image.mjs",
|
||||
"typecheck": "turbo typecheck",
|
||||
"dev": "turbo run dev --parallel --env-mode=loose --filter=!@n8n/design-system --filter=!@n8n/chat --filter=!@n8n/task-runner",
|
||||
"dev:be": "turbo run dev --parallel --env-mode=loose --filter=!@n8n/design-system --filter=!@n8n/chat --filter=!@n8n/task-runner --filter=!n8n-editor-ui",
|
||||
@@ -95,7 +98,9 @@
|
||||
"vue-tsc": "^2.2.8",
|
||||
"google-gax": "^4.3.7",
|
||||
"ws": ">=8.17.1",
|
||||
"zod": "3.25.67"
|
||||
"zod": "3.25.67",
|
||||
"brace-expansion@1": "1.1.12",
|
||||
"brace-expansion@2": "2.0.2"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"bull@4.16.4": "patches/bull@4.16.4.patch",
|
||||
|
||||
@@ -49,8 +49,7 @@
|
||||
"files": [
|
||||
"bin",
|
||||
"templates",
|
||||
"dist",
|
||||
"!dist/**/e2e.*"
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@n8n/typescript-config": "workspace:*",
|
||||
|
||||
118
pnpm-lock.yaml
generated
118
pnpm-lock.yaml
generated
@@ -187,6 +187,8 @@ overrides:
|
||||
google-gax: ^4.3.7
|
||||
ws: '>=8.17.1'
|
||||
zod: 3.25.67
|
||||
brace-expansion@1: 1.1.12
|
||||
brace-expansion@2: 2.0.2
|
||||
|
||||
patchedDependencies:
|
||||
'@types/express-serve-static-core@5.0.6':
|
||||
@@ -5305,73 +5307,73 @@ packages:
|
||||
'@n8n_io/riot-tmpl@4.0.1':
|
||||
resolution: {integrity: sha512-/zdRbEfTFjsm1NqnpPQHgZTkTdbp5v3VUxGeMA9098sps8jRCTraQkc3AQstJgHUm7ylBXJcIVhnVeLUMWAfwQ==}
|
||||
|
||||
'@napi-rs/canvas-android-arm64@0.1.70':
|
||||
resolution: {integrity: sha512-I/YOuQ0wbkVYxVaYtCgN42WKTYxNqFA0gTcTrHIGG1jfpDSyZWII/uHcjOo4nzd19io6Y4+/BqP8E5hJgf9OmQ==}
|
||||
'@napi-rs/canvas-android-arm64@0.1.71':
|
||||
resolution: {integrity: sha512-cxi3VCotIOS9kNFQI7dcysbVJi106pxryVY1Hi85pX+ZeqahRyeqc/NsLaZ998Ae99+F3HI5X/39G1Y/Byrf0A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@napi-rs/canvas-darwin-arm64@0.1.70':
|
||||
resolution: {integrity: sha512-4pPGyXetHIHkw2TOJHujt3mkCP8LdDu8+CT15ld9Id39c752RcI0amDHSuMLMQfAjvusA9B5kKxazwjMGjEJpQ==}
|
||||
'@napi-rs/canvas-darwin-arm64@0.1.71':
|
||||
resolution: {integrity: sha512-7Y4D/6vIuMLYsVNtRM/w2j0+fB1GyqeOxc7I0BTx8eLP1S6BZE2Rj6zJfdG+zmLEOW0IlHa+VQq1q2MUAjW84w==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@napi-rs/canvas-darwin-x64@0.1.70':
|
||||
resolution: {integrity: sha512-+2N6Os9LbkmDMHL+raknrUcLQhsXzc5CSXRbXws9C3pv/mjHRVszQ9dhFUUe9FjfPhCJznO6USVdwOtu7pOrzQ==}
|
||||
'@napi-rs/canvas-darwin-x64@0.1.71':
|
||||
resolution: {integrity: sha512-Z0IUqxclrYdfVt/SK9nKCzUHTOXKTWiygtO71YCzs0OtxKdNI7GJRJdYG48wXZEDQ/pqTF4F7Ifgtidfc2tYpg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.70':
|
||||
resolution: {integrity: sha512-QjscX9OaKq/990sVhSMj581xuqLgiaPVMjjYvWaCmAJRkNQ004QfoSMEm3FoTqM4DRoquP8jvuEXScVJsc1rqQ==}
|
||||
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.71':
|
||||
resolution: {integrity: sha512-KlpqqCASak5ruY+UIolJgmhMZ9Pa2o1QyaNu648L8sz4WNBbNa+aOT60XCLCL1VIKLv11B3MlNgiOHoYNmDhXQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@napi-rs/canvas-linux-arm64-gnu@0.1.70':
|
||||
resolution: {integrity: sha512-LNakMOwwqwiHIwMpnMAbFRczQMQ7TkkMyATqFCOtUJNlE6LPP/QiUj/mlFrNbUn/hctqShJ60gWEb52ZTALbVw==}
|
||||
'@napi-rs/canvas-linux-arm64-gnu@0.1.71':
|
||||
resolution: {integrity: sha512-bdGZCGu8YQNAiu3nkIVVUp6nIn6fPd36IuZsLXTG027E52KyIuZ3obCxehSwjDIUNkFWvmff5D6JYfWwAoioEw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/canvas-linux-arm64-musl@0.1.70':
|
||||
resolution: {integrity: sha512-wBTOllEYNfJCHOdZj9v8gLzZ4oY3oyPX8MSRvaxPm/s7RfEXxCyZ8OhJ5xAyicsDdbE5YBZqdmaaeP5+xKxvtg==}
|
||||
'@napi-rs/canvas-linux-arm64-musl@0.1.71':
|
||||
resolution: {integrity: sha512-1R5sMWe9ur8uM+hAeylBwG0b6UHDR+iWQNgzXmF9vbBYRooQvmDWqpcgytKLJAC0vnWhIkKwqd7yExn7cwczmg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@napi-rs/canvas-linux-riscv64-gnu@0.1.70':
|
||||
resolution: {integrity: sha512-GVUUPC8TuuFqHip0rxHkUqArQnlzmlXmTEBuXAWdgCv85zTCFH8nOHk/YCF5yo0Z2eOm8nOi90aWs0leJ4OE5Q==}
|
||||
'@napi-rs/canvas-linux-riscv64-gnu@0.1.71':
|
||||
resolution: {integrity: sha512-xjjKsipueuG+LdKIk6/uAlqdo+rzGcmNpTZPXdakIT1sHX4NNSnQTzjRaj9Gh96Czjd9G89UWR0KIlE7fwOgFA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/canvas-linux-x64-gnu@0.1.70':
|
||||
resolution: {integrity: sha512-/kvUa2lZRwGNyfznSn5t1ShWJnr/m5acSlhTV3eXECafObjl0VBuA1HJw0QrilLpb4Fe0VLywkpD1NsMoVDROQ==}
|
||||
'@napi-rs/canvas-linux-x64-gnu@0.1.71':
|
||||
resolution: {integrity: sha512-3s6YpklXDB4OeeULG1XTRyKrKAOo7c3HHEqM9A6N4STSjMaJtzmpp7tB/JTvAFeOeFte6gWN8IwC+7AjGJ6MpQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/canvas-linux-x64-musl@0.1.70':
|
||||
resolution: {integrity: sha512-aqlv8MLpycoMKRmds7JWCfVwNf1fiZxaU7JwJs9/ExjTD8lX2KjsO7CTeAj5Cl4aEuzxUWbJPUUE2Qu9cZ1vfg==}
|
||||
'@napi-rs/canvas-linux-x64-musl@0.1.71':
|
||||
resolution: {integrity: sha512-5v9aCLzCXw7u10ray5juQMdl7TykZSn1X5AIGYwBvTAcKSgrqaR9QkRxp1Lqk3njQmFekOW1SFN9bZ/i/6y6kA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@napi-rs/canvas-win32-x64-msvc@0.1.70':
|
||||
resolution: {integrity: sha512-Q9QU3WIpwBTVHk4cPfBjGHGU4U0llQYRXgJtFtYqqGNEOKVN4OT6PQ+ve63xwIPODMpZ0HHyj/KLGc9CWc3EtQ==}
|
||||
'@napi-rs/canvas-win32-x64-msvc@0.1.71':
|
||||
resolution: {integrity: sha512-oJughk6xjsRIr0Rd9EqjmZmhIMkvcPuXgr3MNn2QexTqn+YFOizrwHS5ha0BDfFl7TEGRvwaDUXBQtu8JKXb8A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@napi-rs/canvas@0.1.70':
|
||||
resolution: {integrity: sha512-nD6NGa4JbNYSZYsTnLGrqe9Kn/lCkA4ybXt8sx5ojDqZjr2i0TWAHxx/vhgfjX+i3hCdKWufxYwi7CfXqtITSA==}
|
||||
'@napi-rs/canvas@0.1.71':
|
||||
resolution: {integrity: sha512-92ybDocKl6JM48ZpYbj+A7Qt45IaTABDk0y3sDecEQfgdhfNzJtEityqNHoCZ4Vty2dldPkJhxgvOnbrQMXTTA==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
'@ngneat/falso@7.4.0':
|
||||
@@ -7822,11 +7824,11 @@ packages:
|
||||
bowser@2.11.0:
|
||||
resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==}
|
||||
|
||||
brace-expansion@1.1.11:
|
||||
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
||||
brace-expansion@1.1.12:
|
||||
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
||||
|
||||
brace-expansion@2.0.1:
|
||||
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
|
||||
brace-expansion@2.0.2:
|
||||
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
|
||||
|
||||
braces@3.0.3:
|
||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||
@@ -18051,48 +18053,48 @@ snapshots:
|
||||
dependencies:
|
||||
eslint-config-riot: 1.0.0
|
||||
|
||||
'@napi-rs/canvas-android-arm64@0.1.70':
|
||||
'@napi-rs/canvas-android-arm64@0.1.71':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-darwin-arm64@0.1.70':
|
||||
'@napi-rs/canvas-darwin-arm64@0.1.71':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-darwin-x64@0.1.70':
|
||||
'@napi-rs/canvas-darwin-x64@0.1.71':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.70':
|
||||
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.71':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-arm64-gnu@0.1.70':
|
||||
'@napi-rs/canvas-linux-arm64-gnu@0.1.71':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-arm64-musl@0.1.70':
|
||||
'@napi-rs/canvas-linux-arm64-musl@0.1.71':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-riscv64-gnu@0.1.70':
|
||||
'@napi-rs/canvas-linux-riscv64-gnu@0.1.71':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-x64-gnu@0.1.70':
|
||||
'@napi-rs/canvas-linux-x64-gnu@0.1.71':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-x64-musl@0.1.70':
|
||||
'@napi-rs/canvas-linux-x64-musl@0.1.71':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-win32-x64-msvc@0.1.70':
|
||||
'@napi-rs/canvas-win32-x64-msvc@0.1.71':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas@0.1.70':
|
||||
'@napi-rs/canvas@0.1.71':
|
||||
optionalDependencies:
|
||||
'@napi-rs/canvas-android-arm64': 0.1.70
|
||||
'@napi-rs/canvas-darwin-arm64': 0.1.70
|
||||
'@napi-rs/canvas-darwin-x64': 0.1.70
|
||||
'@napi-rs/canvas-linux-arm-gnueabihf': 0.1.70
|
||||
'@napi-rs/canvas-linux-arm64-gnu': 0.1.70
|
||||
'@napi-rs/canvas-linux-arm64-musl': 0.1.70
|
||||
'@napi-rs/canvas-linux-riscv64-gnu': 0.1.70
|
||||
'@napi-rs/canvas-linux-x64-gnu': 0.1.70
|
||||
'@napi-rs/canvas-linux-x64-musl': 0.1.70
|
||||
'@napi-rs/canvas-win32-x64-msvc': 0.1.70
|
||||
'@napi-rs/canvas-android-arm64': 0.1.71
|
||||
'@napi-rs/canvas-darwin-arm64': 0.1.71
|
||||
'@napi-rs/canvas-darwin-x64': 0.1.71
|
||||
'@napi-rs/canvas-linux-arm-gnueabihf': 0.1.71
|
||||
'@napi-rs/canvas-linux-arm64-gnu': 0.1.71
|
||||
'@napi-rs/canvas-linux-arm64-musl': 0.1.71
|
||||
'@napi-rs/canvas-linux-riscv64-gnu': 0.1.71
|
||||
'@napi-rs/canvas-linux-x64-gnu': 0.1.71
|
||||
'@napi-rs/canvas-linux-x64-musl': 0.1.71
|
||||
'@napi-rs/canvas-win32-x64-msvc': 0.1.71
|
||||
optional: true
|
||||
|
||||
'@ngneat/falso@7.4.0':
|
||||
@@ -21258,12 +21260,12 @@ snapshots:
|
||||
|
||||
bowser@2.11.0: {}
|
||||
|
||||
brace-expansion@1.1.11:
|
||||
brace-expansion@1.1.12:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
concat-map: 0.0.1
|
||||
|
||||
brace-expansion@2.0.1:
|
||||
brace-expansion@2.0.2:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
|
||||
@@ -22898,7 +22900,7 @@ snapshots:
|
||||
ajv: 6.12.6
|
||||
chalk: 4.1.2
|
||||
cross-spawn: 7.0.6
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1(supports-color@8.1.1)
|
||||
doctrine: 3.0.0
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint-scope: 7.2.2
|
||||
@@ -25435,31 +25437,31 @@ snapshots:
|
||||
|
||||
minimatch@10.0.1:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
brace-expansion: 2.0.2
|
||||
|
||||
minimatch@3.0.8:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.11
|
||||
brace-expansion: 1.1.12
|
||||
|
||||
minimatch@3.1.2:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.11
|
||||
brace-expansion: 1.1.12
|
||||
|
||||
minimatch@5.1.5:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
brace-expansion: 2.0.2
|
||||
|
||||
minimatch@9.0.1:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
brace-expansion: 2.0.2
|
||||
|
||||
minimatch@9.0.3:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
brace-expansion: 2.0.2
|
||||
|
||||
minimatch@9.0.5:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
brace-expansion: 2.0.2
|
||||
|
||||
minimist@1.2.8: {}
|
||||
|
||||
@@ -26554,7 +26556,7 @@ snapshots:
|
||||
|
||||
pdfjs-dist@5.3.31(patch_hash=421253c8e411cdaef58ba96d2bb44ae0784e1b3e446f5caca50710daa1fa5dcd):
|
||||
optionalDependencies:
|
||||
'@napi-rs/canvas': 0.1.70
|
||||
'@napi-rs/canvas': 0.1.71
|
||||
|
||||
peberminta@0.9.0: {}
|
||||
|
||||
|
||||
219
scripts/build-n8n.mjs
Executable file
219
scripts/build-n8n.mjs
Executable file
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* This script is used to build the n8n application for production.
|
||||
* It will:
|
||||
* 1. Clean the previous build output
|
||||
* 2. Run pnpm install and build
|
||||
* 3. Prepare for deployment - clean package.json files
|
||||
* 4. Create a pruned production deployment in 'compiled'
|
||||
*/
|
||||
|
||||
import { $, echo, fs, chalk } from 'zx';
|
||||
import path from 'path';
|
||||
|
||||
// Check if running in a CI environment
|
||||
const isCI = process.env.CI === 'true';
|
||||
|
||||
// Disable verbose output and force color only if not in CI
|
||||
$.verbose = !isCI;
|
||||
process.env.FORCE_COLOR = isCI ? '0' : '1';
|
||||
|
||||
const scriptDir = path.dirname(new URL(import.meta.url).pathname);
|
||||
const isInScriptsDir = path.basename(scriptDir) === 'scripts';
|
||||
const rootDir = isInScriptsDir ? path.join(scriptDir, '..') : scriptDir;
|
||||
|
||||
// --- Configuration ---
|
||||
const config = {
|
||||
compiledAppDir: process.env.BUILD_OUTPUT_DIR || path.join(rootDir, 'compiled'),
|
||||
rootDir: rootDir,
|
||||
};
|
||||
|
||||
// Define backend patches to keep during deployment
|
||||
const PATCHES_TO_KEEP = ['pdfjs-dist', 'pkce-challenge', 'bull'];
|
||||
|
||||
// --- Helper Functions ---
|
||||
const timers = new Map();
|
||||
|
||||
function startTimer(name) {
|
||||
timers.set(name, Date.now());
|
||||
}
|
||||
|
||||
function getElapsedTime(name) {
|
||||
const start = timers.get(name);
|
||||
if (!start) return 0;
|
||||
return Math.floor((Date.now() - start) / 1000);
|
||||
}
|
||||
|
||||
function formatDuration(seconds) {
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const secs = seconds % 60;
|
||||
|
||||
if (hours > 0) return `${hours}h ${minutes}m ${secs}s`;
|
||||
if (minutes > 0) return `${minutes}m ${secs}s`;
|
||||
return `${secs}s`;
|
||||
}
|
||||
|
||||
function printHeader(title) {
|
||||
echo('');
|
||||
echo(chalk.blue.bold(`===== ${title} =====`));
|
||||
}
|
||||
|
||||
function printDivider() {
|
||||
echo(chalk.gray('-----------------------------------------------'));
|
||||
}
|
||||
|
||||
// --- Main Build Process ---
|
||||
printHeader('n8n Build & Production Preparation');
|
||||
echo(`INFO: Output Directory: ${config.compiledAppDir}`);
|
||||
printDivider();
|
||||
|
||||
startTimer('total_build');
|
||||
|
||||
// 0. Clean Previous Build Output
|
||||
echo(chalk.yellow(`INFO: Cleaning previous output directory: ${config.compiledAppDir}...`));
|
||||
await fs.remove(config.compiledAppDir);
|
||||
printDivider();
|
||||
|
||||
// 1. Local Application Pre-build
|
||||
echo(chalk.yellow('INFO: Starting local application pre-build...'));
|
||||
startTimer('package_build');
|
||||
|
||||
echo(chalk.yellow('INFO: Running pnpm install and build...'));
|
||||
await $`cd ${config.rootDir} && pnpm install --frozen-lockfile`;
|
||||
await $`cd ${config.rootDir} && pnpm build`;
|
||||
echo(chalk.green('✅ pnpm install and build completed'));
|
||||
|
||||
const packageBuildTime = getElapsedTime('package_build');
|
||||
echo(chalk.green(`✅ Package build completed in ${formatDuration(packageBuildTime)}`));
|
||||
printDivider();
|
||||
|
||||
// 2. Prepare for deployment - clean package.json files
|
||||
echo(chalk.yellow('INFO: Performing pre-deploy cleanup on package.json files...'));
|
||||
|
||||
// Find and backup package.json files
|
||||
const packageJsonFiles = await $`cd ${config.rootDir} && find . -name "package.json" \
|
||||
-not -path "./node_modules/*" \
|
||||
-not -path "*/node_modules/*" \
|
||||
-not -path "./compiled/*" \
|
||||
-type f`.lines();
|
||||
|
||||
// Backup all package.json files
|
||||
// This is only needed locally, not in CI
|
||||
if (process.env.CI !== 'true') {
|
||||
for (const file of packageJsonFiles) {
|
||||
if (file) {
|
||||
const fullPath = path.join(config.rootDir, file);
|
||||
await fs.copy(fullPath, `${fullPath}.bak`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Run FE trim script
|
||||
await $`cd ${config.rootDir} && node .github/scripts/trim-fe-packageJson.js`;
|
||||
echo(chalk.yellow('INFO: Performing selective patch cleanup...'));
|
||||
|
||||
const packageJsonPath = path.join(config.rootDir, 'package.json');
|
||||
|
||||
if (await fs.pathExists(packageJsonPath)) {
|
||||
try {
|
||||
// 1. Read the package.json file
|
||||
const packageJsonContent = await fs.readFile(packageJsonPath, 'utf8');
|
||||
let packageJson = JSON.parse(packageJsonContent);
|
||||
|
||||
// 2. Modify the patchedDependencies directly in JavaScript
|
||||
if (packageJson.pnpm && packageJson.pnpm.patchedDependencies) {
|
||||
const filteredPatches = {};
|
||||
for (const [key, value] of Object.entries(packageJson.pnpm.patchedDependencies)) {
|
||||
// Check if the key (patch name) starts with any of the allowed patches
|
||||
const shouldKeep = PATCHES_TO_KEEP.some((patchPrefix) => key.startsWith(patchPrefix));
|
||||
if (shouldKeep) {
|
||||
filteredPatches[key] = value;
|
||||
}
|
||||
}
|
||||
packageJson.pnpm.patchedDependencies = filteredPatches;
|
||||
}
|
||||
|
||||
// 3. Write the modified package.json back
|
||||
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf8');
|
||||
|
||||
echo(chalk.green('✅ Kept backend patches: ' + PATCHES_TO_KEEP.join(', ')));
|
||||
echo(
|
||||
chalk.gray(
|
||||
`Removed FE/dev patches that are not in the list of backend patches to keep: ${PATCHES_TO_KEEP.join(', ')}`,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
echo(chalk.red(`ERROR: Failed to cleanup patches in package.json: ${error.message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
echo(chalk.yellow(`INFO: Creating pruned production deployment in '${config.compiledAppDir}'...`));
|
||||
startTimer('package_deploy');
|
||||
|
||||
await fs.ensureDir(config.compiledAppDir);
|
||||
|
||||
await $`cd ${config.rootDir} && NODE_ENV=production DOCKER_BUILD=true pnpm --filter=n8n --prod --legacy deploy --no-optional ./compiled`;
|
||||
|
||||
const packageDeployTime = getElapsedTime('package_deploy');
|
||||
|
||||
// Restore package.json files
|
||||
// This is only needed locally, not in CI
|
||||
if (process.env.CI !== 'true') {
|
||||
for (const file of packageJsonFiles) {
|
||||
if (file) {
|
||||
const fullPath = path.join(config.rootDir, file);
|
||||
const backupPath = `${fullPath}.bak`;
|
||||
if (await fs.pathExists(backupPath)) {
|
||||
await fs.move(backupPath, fullPath, { overwrite: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate output size
|
||||
const compiledAppOutputSize = (await $`du -sh ${config.compiledAppDir} | cut -f1`).stdout.trim();
|
||||
|
||||
// Generate build manifest
|
||||
const buildManifest = {
|
||||
buildTime: new Date().toISOString(),
|
||||
artifactSize: compiledAppOutputSize,
|
||||
buildDuration: {
|
||||
packageBuild: packageBuildTime,
|
||||
packageDeploy: packageDeployTime,
|
||||
total: getElapsedTime('total_build'),
|
||||
},
|
||||
};
|
||||
|
||||
await fs.writeJson(path.join(config.compiledAppDir, 'build-manifest.json'), buildManifest, {
|
||||
spaces: 2,
|
||||
});
|
||||
|
||||
echo(chalk.green(`✅ Package deployment completed in ${formatDuration(packageDeployTime)}`));
|
||||
echo(`INFO: Size of ${config.compiledAppDir}: ${compiledAppOutputSize}`);
|
||||
printDivider();
|
||||
|
||||
// Calculate total time
|
||||
const totalBuildTime = getElapsedTime('total_build');
|
||||
|
||||
// --- Final Output ---
|
||||
echo('');
|
||||
echo(chalk.green.bold('================ BUILD SUMMARY ================'));
|
||||
echo(chalk.green(`✅ n8n built successfully!`));
|
||||
echo('');
|
||||
echo(chalk.blue('📦 Build Output:'));
|
||||
echo(` Directory: ${path.resolve(config.compiledAppDir)}`);
|
||||
echo(` Size: ${compiledAppOutputSize}`);
|
||||
echo('');
|
||||
echo(chalk.blue('⏱️ Build Times:'));
|
||||
echo(` Package Build: ${formatDuration(packageBuildTime)}`);
|
||||
echo(` Package Deploy: ${formatDuration(packageDeployTime)}`);
|
||||
echo(chalk.gray(' -----------------------------'));
|
||||
echo(chalk.bold(` Total Time: ${formatDuration(totalBuildTime)}`));
|
||||
echo('');
|
||||
echo(chalk.blue('📋 Build Manifest:'));
|
||||
echo(` ${path.resolve(config.compiledAppDir)}/build-manifest.json`);
|
||||
echo(chalk.green.bold('=============================================='));
|
||||
|
||||
// Exit with success
|
||||
process.exit(0);
|
||||
88
scripts/dockerize-n8n.mjs
Executable file
88
scripts/dockerize-n8n.mjs
Executable file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* This script is used to build the n8n docker image locally.
|
||||
* It simulates how we build for CI and should allow for local testing.
|
||||
* By default it outputs the tag 'dev' and the image name 'n8n-local:dev'.
|
||||
* It can be overridden by setting the IMAGE_BASE_NAME and IMAGE_TAG environment variables.
|
||||
*/
|
||||
|
||||
import { $, echo, fs, chalk } from 'zx';
|
||||
import path from 'path';
|
||||
|
||||
// Disable verbose mode for cleaner output
|
||||
$.verbose = false;
|
||||
process.env.FORCE_COLOR = '1';
|
||||
|
||||
// --- Determine script location ---
|
||||
const scriptDir = path.dirname(new URL(import.meta.url).pathname);
|
||||
const isInScriptsDir = path.basename(scriptDir) === 'scripts';
|
||||
const rootDir = isInScriptsDir ? path.join(scriptDir, '..') : scriptDir;
|
||||
|
||||
// --- Configuration ---
|
||||
const config = {
|
||||
dockerfilePath: path.join(rootDir, 'docker/images/n8n/Dockerfile'),
|
||||
imageBaseName: process.env.IMAGE_BASE_NAME || 'n8n-local',
|
||||
imageTag: process.env.IMAGE_TAG || 'dev',
|
||||
buildContext: rootDir,
|
||||
compiledAppDir: path.join(rootDir, 'compiled'),
|
||||
};
|
||||
|
||||
config.fullImageName = `${config.imageBaseName}:${config.imageTag}`;
|
||||
|
||||
// --- Check Prerequisites ---
|
||||
echo(chalk.blue.bold('===== Docker Build for n8n ====='));
|
||||
echo(`INFO: Image: ${config.fullImageName}`);
|
||||
echo(chalk.gray('-----------------------------------------------'));
|
||||
|
||||
// Check if compiled directory exists
|
||||
if (!(await fs.pathExists(config.compiledAppDir))) {
|
||||
echo(chalk.red(`Error: Compiled app directory not found at ${config.compiledAppDir}`));
|
||||
echo(chalk.yellow('Please run build-n8n.mjs first!'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check Docker
|
||||
try {
|
||||
await $`command -v docker`;
|
||||
} catch {
|
||||
echo(chalk.red('Error: Docker is not installed or not in PATH'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// --- Build Docker Image ---
|
||||
const startTime = Date.now();
|
||||
echo(chalk.yellow('INFO: Building Docker image...'));
|
||||
|
||||
try {
|
||||
const buildOutput = await $`docker build \
|
||||
-t ${config.fullImageName} \
|
||||
-f ${config.dockerfilePath} \
|
||||
${config.buildContext}`;
|
||||
|
||||
echo(buildOutput.stdout);
|
||||
} catch (error) {
|
||||
echo(chalk.red(`ERROR: Docker build failed: ${error.stderr || error.message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const buildTime = Math.floor((Date.now() - startTime) / 1000);
|
||||
|
||||
// Get image size
|
||||
let imageSize = 'Unknown';
|
||||
try {
|
||||
const sizeOutput = await $`docker images ${config.fullImageName} --format "{{.Size}}"`;
|
||||
imageSize = sizeOutput.stdout.trim();
|
||||
} catch (error) {
|
||||
echo(chalk.yellow('Warning: Could not get image size'));
|
||||
}
|
||||
|
||||
// --- Summary ---
|
||||
echo('');
|
||||
echo(chalk.green.bold('================ DOCKER BUILD COMPLETE ================'));
|
||||
echo(chalk.green(`✅ Image built: ${config.fullImageName}`));
|
||||
echo(` Size: ${imageSize}`);
|
||||
echo(` Build time: ${buildTime}s`);
|
||||
echo(chalk.green.bold('===================================================='));
|
||||
|
||||
// Exit with success
|
||||
process.exit(0);
|
||||
152
scripts/scan-n8n-image.mjs
Executable file
152
scripts/scan-n8n-image.mjs
Executable file
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* This script is used to scan the n8n docker image for vulnerabilities.
|
||||
* It uses Trivy to scan the image.
|
||||
*/
|
||||
|
||||
import { $, echo, fs, chalk } from 'zx';
|
||||
import path from 'path';
|
||||
|
||||
$.verbose = false;
|
||||
process.env.FORCE_COLOR = '1';
|
||||
|
||||
const scriptDir = path.dirname(new URL(import.meta.url).pathname);
|
||||
const isInScriptsDir = path.basename(scriptDir) === 'scripts';
|
||||
const rootDir = isInScriptsDir ? path.join(scriptDir, '..') : scriptDir;
|
||||
|
||||
// --- Configuration ---
|
||||
const config = {
|
||||
imageBaseName: process.env.IMAGE_BASE_NAME || 'n8n-local',
|
||||
imageTag: process.env.IMAGE_TAG || 'dev',
|
||||
trivyImage: process.env.TRIVY_IMAGE || 'aquasec/trivy:latest',
|
||||
severity: process.env.TRIVY_SEVERITY || 'CRITICAL,HIGH,MEDIUM,LOW',
|
||||
outputFormat: process.env.TRIVY_FORMAT || 'table',
|
||||
outputFile: process.env.TRIVY_OUTPUT || null,
|
||||
scanTimeout: process.env.TRIVY_TIMEOUT || '10m',
|
||||
ignoreUnfixed: process.env.TRIVY_IGNORE_UNFIXED === 'true',
|
||||
scanners: process.env.TRIVY_SCANNERS || 'vuln',
|
||||
quiet: process.env.TRIVY_QUIET === 'true',
|
||||
rootDir: rootDir,
|
||||
};
|
||||
|
||||
config.fullImageName = `${config.imageBaseName}:${config.imageTag}`;
|
||||
|
||||
const printHeader = (title) =>
|
||||
!config.quiet && echo(`\n${chalk.blue.bold(`===== ${title} =====`)}`);
|
||||
|
||||
const printSummary = (status, time, message) => {
|
||||
if (config.quiet) return;
|
||||
|
||||
echo('\n' + chalk.blue.bold('===== Scan Summary ====='));
|
||||
echo(status === 'success' ? chalk.green.bold(message) : chalk.yellow.bold(message));
|
||||
echo(chalk[status === 'success' ? 'green' : 'yellow'](` Scan time: ${time}s`));
|
||||
|
||||
if (config.outputFile) {
|
||||
const resolvedPath = path.isAbsolute(config.outputFile)
|
||||
? config.outputFile
|
||||
: path.join(config.rootDir, config.outputFile);
|
||||
echo(chalk[status === 'success' ? 'green' : 'yellow'](` Report saved to: ${resolvedPath}`));
|
||||
}
|
||||
|
||||
echo('\n' + chalk.gray('Scan Configuration:'));
|
||||
echo(chalk.gray(` • Target Image: ${config.fullImageName}`));
|
||||
echo(chalk.gray(` • Severity Levels: ${config.severity}`));
|
||||
echo(chalk.gray(` • Scanners: ${config.scanners}`));
|
||||
if (config.ignoreUnfixed) echo(chalk.gray(` • Ignored unfixed: yes`));
|
||||
echo(chalk.blue.bold('========================'));
|
||||
};
|
||||
|
||||
// --- Main Process ---
|
||||
(async () => {
|
||||
printHeader('Trivy Security Scan for n8n Image');
|
||||
|
||||
try {
|
||||
await $`command -v docker`;
|
||||
} catch {
|
||||
echo(chalk.red('Error: Docker is not installed or not in PATH'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
await $`docker image inspect ${config.fullImageName} > /dev/null 2>&1`;
|
||||
} catch {
|
||||
echo(chalk.red(`Error: Docker image '${config.fullImageName}' not found`));
|
||||
echo(chalk.yellow('Please run dockerize-n8n.mjs first!'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Pull latest Trivy image silently
|
||||
try {
|
||||
await $`docker pull ${config.trivyImage} > /dev/null 2>&1`;
|
||||
} catch {
|
||||
// Silent fallback to cached version
|
||||
}
|
||||
|
||||
// Build Trivy command
|
||||
const trivyArgs = [
|
||||
'run',
|
||||
'--rm',
|
||||
'-v',
|
||||
'/var/run/docker.sock:/var/run/docker.sock',
|
||||
config.trivyImage,
|
||||
'image',
|
||||
'--severity',
|
||||
config.severity,
|
||||
'--format',
|
||||
config.outputFormat,
|
||||
'--timeout',
|
||||
config.scanTimeout,
|
||||
'--scanners',
|
||||
config.scanners,
|
||||
'--no-progress',
|
||||
];
|
||||
|
||||
if (config.ignoreUnfixed) trivyArgs.push('--ignore-unfixed');
|
||||
if (config.quiet && config.outputFormat === 'table') trivyArgs.push('--quiet');
|
||||
|
||||
// Handle output file - resolve relative to root directory
|
||||
if (config.outputFile) {
|
||||
const outputPath = path.isAbsolute(config.outputFile)
|
||||
? config.outputFile
|
||||
: path.join(config.rootDir, config.outputFile);
|
||||
await fs.ensureDir(path.dirname(outputPath));
|
||||
trivyArgs.push('--output', '/tmp/trivy-output', '-v', `${outputPath}:/tmp/trivy-output`);
|
||||
}
|
||||
|
||||
trivyArgs.push(config.fullImageName);
|
||||
|
||||
// Run the scan
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
const result = await $`docker ${trivyArgs}`;
|
||||
|
||||
// Print Trivy output first
|
||||
if (!config.outputFile && result.stdout) {
|
||||
echo(result.stdout);
|
||||
}
|
||||
|
||||
// Then print our summary
|
||||
const scanTime = Math.floor((Date.now() - startTime) / 1000);
|
||||
printSummary('success', scanTime, '✅ Security scan completed successfully');
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
const scanTime = Math.floor((Date.now() - startTime) / 1000);
|
||||
|
||||
// Trivy returns exit code 1 when vulnerabilities are found
|
||||
if (error.exitCode === 1) {
|
||||
// Print Trivy output first
|
||||
if (!config.outputFile && error.stdout) {
|
||||
echo(error.stdout);
|
||||
}
|
||||
|
||||
// Then print our summary
|
||||
printSummary('warning', scanTime, '⚠️ Vulnerabilities found!');
|
||||
process.exit(1);
|
||||
} else {
|
||||
echo(chalk.red(`❌ Scan failed: ${error.message}`));
|
||||
process.exit(error.exitCode || 1);
|
||||
}
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user