first commit
Some checks failed
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
Some checks failed
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:
422
.github/workflows/docker-build-push.yml
vendored
Normal file
422
.github/workflows/docker-build-push.yml
vendored
Normal file
@@ -0,0 +1,422 @@
|
||||
# This workflow is used to build and push the Docker image for n8nio/n8n and n8nio/runners
|
||||
#
|
||||
# - Uses docker-config.mjs for context determination, this determines what needs to be built based on the trigger
|
||||
# - Uses docker-tags.mjs for tag generation, this generates the tags for the images
|
||||
|
||||
name: 'Docker: Build and Push'
|
||||
|
||||
env:
|
||||
NODE_OPTIONS: '--max-old-space-size=7168'
|
||||
NODE_VERSION: '24.13.1'
|
||||
|
||||
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: false
|
||||
type: string
|
||||
default: 'stable'
|
||||
push_enabled:
|
||||
description: 'Whether to push the built images'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
push_enabled:
|
||||
description: 'Push image to registry'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
success_url:
|
||||
description: 'URL to call after the build is successful'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
determine-build-context:
|
||||
name: Determine Build Context
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
release_type: ${{ steps.context.outputs.release_type }}
|
||||
n8n_version: ${{ steps.context.outputs.version }}
|
||||
push_enabled: ${{ steps.context.outputs.push_enabled }}
|
||||
push_to_docker: ${{ steps.context.outputs.push_to_docker }}
|
||||
build_matrix: ${{ steps.context.outputs.build_matrix }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Determine build context
|
||||
id: context
|
||||
run: |
|
||||
node .github/scripts/docker/docker-config.mjs \
|
||||
--event "${{ github.event_name }}" \
|
||||
--pr "${{ github.event.pull_request.number }}" \
|
||||
--branch "${{ github.ref_name }}" \
|
||||
--version "${{ inputs.n8n_version }}" \
|
||||
--release-type "${{ inputs.release_type }}" \
|
||||
--push-enabled "${{ inputs.push_enabled }}"
|
||||
|
||||
build-and-push-docker:
|
||||
name: Build App, then Build and Push Docker Image (${{ matrix.platform }})
|
||||
needs: determine-build-context
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 25
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.determine-build-context.outputs.build_matrix) }}
|
||||
outputs:
|
||||
image_ref: ${{ steps.determine-tags.outputs.n8n_primary_tag }}
|
||||
primary_ghcr_manifest_tag: ${{ steps.determine-tags.outputs.n8n_primary_tag }}
|
||||
runners_primary_ghcr_manifest_tag: ${{ steps.determine-tags.outputs.runners_primary_tag }}
|
||||
runners_distroless_primary_ghcr_manifest_tag: ${{ steps.determine-tags.outputs.runners_distroless_primary_tag }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup and Build
|
||||
uses: ./.github/actions/setup-nodejs
|
||||
with:
|
||||
build-command: pnpm build:n8n
|
||||
enable-docker-cache: 'true'
|
||||
env:
|
||||
RELEASE: ${{ needs.determine-build-context.outputs.n8n_version }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
|
||||
- name: Determine Docker tags for all images
|
||||
id: determine-tags
|
||||
run: |
|
||||
node .github/scripts/docker/docker-tags.mjs \
|
||||
--all \
|
||||
--version "${{ needs.determine-build-context.outputs.n8n_version }}" \
|
||||
--platform "${{ matrix.docker_platform }}" \
|
||||
${{ needs.determine-build-context.outputs.push_to_docker == 'true' && '--include-docker' || '' }}
|
||||
|
||||
echo "=== Generated Docker Tags ==="
|
||||
cat "$GITHUB_OUTPUT" | grep "_tags=" | while IFS='=' read -r key value; do
|
||||
echo "${key}: ${value%%,*}..." # Show first tag for brevity
|
||||
done
|
||||
|
||||
- name: Login to Docker registries
|
||||
if: needs.determine-build-context.outputs.push_enabled == 'true'
|
||||
uses: ./.github/actions/docker-registry-login
|
||||
with:
|
||||
login-ghcr: true
|
||||
login-dockerhub: ${{ needs.determine-build-context.outputs.push_to_docker == 'true' }}
|
||||
dockerhub-username: ${{ secrets.DOCKER_USERNAME }}
|
||||
dockerhub-password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push n8n Docker image
|
||||
id: build-n8n
|
||||
uses: useblacksmith/build-push-action@30c71162f16ea2c27c3e21523255d209b8b538c1 # v2
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/images/n8n/Dockerfile
|
||||
build-args: |
|
||||
NODE_VERSION=${{ env.NODE_VERSION }}
|
||||
N8N_VERSION=${{ needs.determine-build-context.outputs.n8n_version }}
|
||||
N8N_RELEASE_TYPE=${{ needs.determine-build-context.outputs.release_type }}
|
||||
platforms: ${{ matrix.docker_platform }}
|
||||
provenance: false # Disabled - using SLSA L3 generator for isolated provenance
|
||||
sbom: true
|
||||
push: ${{ needs.determine-build-context.outputs.push_enabled == 'true' }}
|
||||
tags: ${{ steps.determine-tags.outputs.n8n_tags }}
|
||||
|
||||
- name: Build and push task runners Docker image (Alpine)
|
||||
id: build-runners
|
||||
uses: useblacksmith/build-push-action@30c71162f16ea2c27c3e21523255d209b8b538c1 # v2
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/images/runners/Dockerfile
|
||||
build-args: |
|
||||
NODE_VERSION=${{ env.NODE_VERSION }}
|
||||
N8N_VERSION=${{ needs.determine-build-context.outputs.n8n_version }}
|
||||
N8N_RELEASE_TYPE=${{ needs.determine-build-context.outputs.release_type }}
|
||||
platforms: ${{ matrix.docker_platform }}
|
||||
provenance: false # Disabled - using SLSA L3 generator for isolated provenance
|
||||
sbom: true
|
||||
push: ${{ needs.determine-build-context.outputs.push_enabled == 'true' }}
|
||||
tags: ${{ steps.determine-tags.outputs.runners_tags }}
|
||||
|
||||
- name: Build and push task runners Docker image (distroless)
|
||||
id: build-runners-distroless
|
||||
uses: useblacksmith/build-push-action@30c71162f16ea2c27c3e21523255d209b8b538c1 # v2
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/images/runners/Dockerfile.distroless
|
||||
build-args: |
|
||||
NODE_VERSION=${{ env.NODE_VERSION }}
|
||||
N8N_VERSION=${{ needs.determine-build-context.outputs.n8n_version }}
|
||||
N8N_RELEASE_TYPE=${{ needs.determine-build-context.outputs.release_type }}
|
||||
platforms: ${{ matrix.docker_platform }}
|
||||
provenance: false # Disabled - using SLSA L3 generator for isolated provenance
|
||||
sbom: true
|
||||
push: ${{ needs.determine-build-context.outputs.push_enabled == 'true' }}
|
||||
tags: ${{ steps.determine-tags.outputs.runners_distroless_tags }}
|
||||
|
||||
create_multi_arch_manifest:
|
||||
name: Create Multi-Arch Manifest
|
||||
needs: [determine-build-context, build-and-push-docker]
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
needs.build-and-push-docker.result == 'success' &&
|
||||
needs.determine-build-context.outputs.push_enabled == 'true'
|
||||
outputs:
|
||||
n8n_digest: ${{ steps.get-digests.outputs.n8n_digest }}
|
||||
n8n_image: ${{ steps.get-digests.outputs.n8n_image }}
|
||||
runners_digest: ${{ steps.get-digests.outputs.runners_digest }}
|
||||
runners_image: ${{ steps.get-digests.outputs.runners_image }}
|
||||
runners_distroless_digest: ${{ steps.get-digests.outputs.runners_distroless_digest }}
|
||||
runners_distroless_image: ${{ steps.get-digests.outputs.runners_distroless_image }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- name: Login to Docker registries
|
||||
uses: ./.github/actions/docker-registry-login
|
||||
with:
|
||||
login-ghcr: true
|
||||
login-dockerhub: ${{ needs.determine-build-context.outputs.push_to_docker == 'true' }}
|
||||
dockerhub-username: ${{ secrets.DOCKER_USERNAME }}
|
||||
dockerhub-password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Create GHCR multi-arch manifests
|
||||
run: |
|
||||
RELEASE_TYPE="${{ needs.determine-build-context.outputs.release_type }}"
|
||||
|
||||
# Function to create manifest for an image
|
||||
create_manifest() {
|
||||
local IMAGE_NAME=$1
|
||||
local MANIFEST_TAG=$2
|
||||
|
||||
if [[ -z "$MANIFEST_TAG" ]]; then
|
||||
echo "Skipping $IMAGE_NAME - no manifest tag"
|
||||
return
|
||||
fi
|
||||
|
||||
echo "Creating GHCR manifest for $IMAGE_NAME: $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
|
||||
}
|
||||
|
||||
# Create manifests for all images
|
||||
create_manifest "n8n" "${{ needs.build-and-push-docker.outputs.primary_ghcr_manifest_tag }}"
|
||||
create_manifest "runners" "${{ needs.build-and-push-docker.outputs.runners_primary_ghcr_manifest_tag }}"
|
||||
create_manifest "runners-distroless" "${{ needs.build-and-push-docker.outputs.runners_distroless_primary_ghcr_manifest_tag }}"
|
||||
|
||||
- name: Create Docker Hub manifests
|
||||
if: needs.determine-build-context.outputs.push_to_docker == 'true'
|
||||
run: |
|
||||
VERSION="${{ needs.determine-build-context.outputs.n8n_version }}"
|
||||
DOCKER_BASE="${{ secrets.DOCKER_USERNAME }}"
|
||||
|
||||
# Create manifests for each image type
|
||||
declare -A images=(
|
||||
["n8n"]="${VERSION}"
|
||||
["runners"]="${VERSION}"
|
||||
["runners-distroless"]="${VERSION}-distroless"
|
||||
)
|
||||
|
||||
for image in "${!images[@]}"; do
|
||||
TAG_SUFFIX="${images[$image]}"
|
||||
IMAGE_NAME="${image//-distroless/}" # Remove -distroless from image name
|
||||
|
||||
echo "Creating Docker Hub manifest for $image"
|
||||
docker buildx imagetools create \
|
||||
--tag "${DOCKER_BASE}/${IMAGE_NAME}:${TAG_SUFFIX}" \
|
||||
"${DOCKER_BASE}/${IMAGE_NAME}:${TAG_SUFFIX}-amd64" \
|
||||
"${DOCKER_BASE}/${IMAGE_NAME}:${TAG_SUFFIX}-arm64"
|
||||
done
|
||||
|
||||
- name: Get manifest digests for attestation
|
||||
id: get-digests
|
||||
env:
|
||||
N8N_TAG: ${{ needs.build-and-push-docker.outputs.primary_ghcr_manifest_tag }}
|
||||
RUNNERS_TAG: ${{ needs.build-and-push-docker.outputs.runners_primary_ghcr_manifest_tag }}
|
||||
DISTROLESS_TAG: ${{ needs.build-and-push-docker.outputs.runners_distroless_primary_ghcr_manifest_tag }}
|
||||
run: node .github/scripts/docker/get-manifest-digests.mjs
|
||||
|
||||
call-success-url:
|
||||
name: Call Success URL
|
||||
needs: [create_multi_arch_manifest]
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.create_multi_arch_manifest.result == 'success' || needs.create_multi_arch_manifest.result == 'skipped'
|
||||
steps:
|
||||
- name: Call Success URL
|
||||
env:
|
||||
SUCCESS_URL: ${{ github.event.inputs.success_url }}
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.success_url != '' }}
|
||||
run: |
|
||||
echo "Calling success URL: ${{ env.SUCCESS_URL }}"
|
||||
curl -v "${{ env.SUCCESS_URL }}" || echo "Failed to call success URL"
|
||||
shell: bash
|
||||
|
||||
# SLSA L3 Provenance - Must use version tags (@vX.Y.Z), NOT SHAs
|
||||
provenance-n8n:
|
||||
name: SLSA Provenance (n8n)
|
||||
needs: [determine-build-context, build-and-push-docker, create_multi_arch_manifest]
|
||||
if: |
|
||||
needs.create_multi_arch_manifest.result == 'success' &&
|
||||
needs.create_multi_arch_manifest.outputs.n8n_digest != ''
|
||||
permissions:
|
||||
id-token: write
|
||||
packages: write
|
||||
actions: read
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||
with:
|
||||
image: ${{ needs.create_multi_arch_manifest.outputs.n8n_image }}
|
||||
digest: ${{ needs.create_multi_arch_manifest.outputs.n8n_digest }}
|
||||
registry-username: ${{ github.actor }}
|
||||
secrets:
|
||||
registry-password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
provenance-runners:
|
||||
name: SLSA Provenance (runners)
|
||||
needs: [determine-build-context, build-and-push-docker, create_multi_arch_manifest]
|
||||
if: |
|
||||
needs.create_multi_arch_manifest.result == 'success' &&
|
||||
needs.create_multi_arch_manifest.outputs.runners_digest != ''
|
||||
permissions:
|
||||
id-token: write
|
||||
packages: write
|
||||
actions: read
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||
with:
|
||||
image: ${{ needs.create_multi_arch_manifest.outputs.runners_image }}
|
||||
digest: ${{ needs.create_multi_arch_manifest.outputs.runners_digest }}
|
||||
registry-username: ${{ github.actor }}
|
||||
secrets:
|
||||
registry-password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
provenance-runners-distroless:
|
||||
name: SLSA Provenance (runners-distroless)
|
||||
needs: [determine-build-context, build-and-push-docker, create_multi_arch_manifest]
|
||||
if: |
|
||||
needs.create_multi_arch_manifest.result == 'success' &&
|
||||
needs.create_multi_arch_manifest.outputs.runners_distroless_digest != ''
|
||||
permissions:
|
||||
id-token: write
|
||||
packages: write
|
||||
actions: read
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||
with:
|
||||
image: ${{ needs.create_multi_arch_manifest.outputs.runners_distroless_image }}
|
||||
digest: ${{ needs.create_multi_arch_manifest.outputs.runners_distroless_digest }}
|
||||
registry-username: ${{ github.actor }}
|
||||
secrets:
|
||||
registry-password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# VEX Attestation - Documents which CVEs affect us (security/vex.openvex.json)
|
||||
vex-attestation:
|
||||
name: VEX Attestation
|
||||
needs: [determine-build-context, build-and-push-docker, create_multi_arch_manifest, provenance-n8n, provenance-runners, provenance-runners-distroless]
|
||||
if: |
|
||||
always() &&
|
||||
needs.create_multi_arch_manifest.result == 'success' &&
|
||||
(needs.determine-build-context.outputs.release_type == 'stable' ||
|
||||
needs.determine-build-context.outputs.release_type == 'rc' ||
|
||||
needs.determine-build-context.outputs.release_type == 'nightly')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@7e8b541eb2e61bf99390e1afd4be13a184e9ebc5 # v3.10.1
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Attest VEX to n8n image
|
||||
if: needs.create_multi_arch_manifest.outputs.n8n_digest != ''
|
||||
run: |
|
||||
cosign attest --yes \
|
||||
--type openvex \
|
||||
--predicate security/vex.openvex.json \
|
||||
${{ needs.create_multi_arch_manifest.outputs.n8n_image }}@${{ needs.create_multi_arch_manifest.outputs.n8n_digest }}
|
||||
|
||||
- name: Attest VEX to runners image
|
||||
if: needs.create_multi_arch_manifest.outputs.runners_digest != ''
|
||||
run: |
|
||||
cosign attest --yes \
|
||||
--type openvex \
|
||||
--predicate security/vex.openvex.json \
|
||||
${{ needs.create_multi_arch_manifest.outputs.runners_image }}@${{ needs.create_multi_arch_manifest.outputs.runners_digest }}
|
||||
|
||||
- name: Attest VEX to runners-distroless image
|
||||
if: needs.create_multi_arch_manifest.outputs.runners_distroless_digest != ''
|
||||
run: |
|
||||
cosign attest --yes \
|
||||
--type openvex \
|
||||
--predicate security/vex.openvex.json \
|
||||
${{ needs.create_multi_arch_manifest.outputs.runners_distroless_image }}@${{ needs.create_multi_arch_manifest.outputs.runners_distroless_digest }}
|
||||
|
||||
security-scan:
|
||||
name: Security Scan
|
||||
needs: [determine-build-context, build-and-push-docker, create_multi_arch_manifest]
|
||||
if: |
|
||||
success() &&
|
||||
(needs.determine-build-context.outputs.release_type == 'stable' ||
|
||||
needs.determine-build-context.outputs.release_type == 'nightly' ||
|
||||
needs.determine-build-context.outputs.release_type == 'rc')
|
||||
uses: ./.github/workflows/security-trivy-scan-callable.yml
|
||||
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, create_multi_arch_manifest]
|
||||
if: |
|
||||
success() &&
|
||||
(needs.determine-build-context.outputs.release_type == 'stable' ||
|
||||
needs.determine-build-context.outputs.release_type == 'nightly' ||
|
||||
needs.determine-build-context.outputs.release_type == 'rc')
|
||||
uses: ./.github/workflows/security-trivy-scan-callable.yml
|
||||
with:
|
||||
image_ref: ${{ needs.build-and-push-docker.outputs.runners_primary_ghcr_manifest_tag }}
|
||||
secrets: inherit
|
||||
|
||||
notify-on-failure:
|
||||
name: Notify Cats on nightly build failure
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-and-push-docker]
|
||||
if: needs.build-and-push-docker.result == 'failure' && github.event_name == 'schedule'
|
||||
steps:
|
||||
- uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0
|
||||
with:
|
||||
status: ${{ needs.build-and-push-docker.result }}
|
||||
channel: '#team-catalysts'
|
||||
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
message: Nightly Docker build failed - ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
Reference in New Issue
Block a user