mirror of
https://github.com/n8n-io/n8n
synced 2026-04-21 15:47:20 +00:00
457 lines
19 KiB
YAML
457 lines
19 KiB
YAML
# 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.14.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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.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 }}
|
|
n8n_sha_manifest_tag: ${{ steps.determine-tags.outputs.n8n_sha_primary_tag }}
|
|
runners_sha_manifest_tag: ${{ steps.determine-tags.outputs.runners_sha_primary_tag }}
|
|
runners_distroless_sha_manifest_tag: ${{ steps.determine-tags.outputs.runners_distroless_sha_primary_tag }}
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.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 }}" \
|
|
--sha "${GITHUB_SHA::7}" \
|
|
${{ 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
|
|
|
- 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 }}"
|
|
|
|
# Create SHA-tagged manifests (immutable references for deployments)
|
|
create_manifest "n8n (sha)" "${{ needs.build-and-push-docker.outputs.n8n_sha_manifest_tag }}"
|
|
create_manifest "runners (sha)" "${{ needs.build-and-push-docker.outputs.runners_sha_manifest_tag }}"
|
|
create_manifest "runners-distroless (sha)" "${{ needs.build-and-push-docker.outputs.runners_distroless_sha_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"
|
|
)
|
|
|
|
SHORT_SHA="${GITHUB_SHA::7}"
|
|
|
|
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"
|
|
|
|
# Create SHA-tagged manifest (immutable reference)
|
|
# For distroless, insert SHA between version and -distroless suffix
|
|
# to match docker-tags.mjs format: nightly-abc1234-distroless (not nightly-distroless-abc1234)
|
|
if [[ "$image" == *"-distroless"* ]]; then
|
|
SHA_SUFFIX="${VERSION}-${SHORT_SHA}-distroless"
|
|
else
|
|
SHA_SUFFIX="${TAG_SUFFIX}-${SHORT_SHA}"
|
|
fi
|
|
echo "Creating Docker Hub SHA manifest for $image: ${SHA_SUFFIX}"
|
|
docker buildx imagetools create \
|
|
--tag "${DOCKER_BASE}/${IMAGE_NAME}:${SHA_SUFFIX}" \
|
|
"${DOCKER_BASE}/${IMAGE_NAME}:${SHA_SUFFIX}-amd64" \
|
|
"${DOCKER_BASE}/${IMAGE_NAME}:${SHA_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
|
|
|
|
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
|
|
# SLSA L3 Provenance - Must use version tags (@vX.Y.Z), NOT SHAs
|
|
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
|
|
# SLSA L3 Provenance - Must use version tags (@vX.Y.Z), NOT SHAs
|
|
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
|
|
# SLSA L3 Provenance - Must use version tags (@vX.Y.Z), NOT SHAs
|
|
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Install Cosign
|
|
uses: sigstore/cosign-installer@7e8b541eb2e61bf99390e1afd4be13a184e9ebc5 # v3.10.1
|
|
|
|
- name: Login to GHCR
|
|
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.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 }}
|