Skip to content
This repository was archived by the owner on Jan 14, 2026. It is now read-only.

Docker: Build and Push #46

Docker: Build and Push

Docker: Build and Push #46

# 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: "22.21.0"
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
pull_request:
types:
- opened
- ready_for_review
paths:
- ".github/workflows/docker-build-push.yml"
- ".github/scripts/docker/docker-config.mjs"
- ".github/scripts/docker/docker-tags.mjs"
- "docker/images/n8n/Dockerfile"
- "docker/images/runners/Dockerfile"
- "docker/images/runners/Dockerfile.distroless"
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: 30
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-blacksmith
with:
build-command: pnpm build:n8n
- 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: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Login to GitHub Container Registry
if: needs.determine-build-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: Configure AWS credentials
if: needs.determine-build-context.outputs.push_enabled == 'true'
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: "${{ secrets.AWS_ACCESS_KEY }}"
aws-secret-access-key: "${{ secrets.AWS_SECRET_ACCESS_KEY }}"
aws-region: "us-west-2"
- name: Login to Amazon ECR
if: needs.determine-build-context.outputs.push_enabled == 'true'
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push n8n Docker image
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
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: true
sbom: true
push: ${{ needs.determine-build-context.outputs.push_enabled == 'true' }}
tags: 548235256840.dkr.ecr.us-west-2.amazonaws.com/n8n-forked/n8n:${{ needs.determine-build-context.outputs.n8n_version }}-${{ matrix.platform }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build and push task runners Docker image (Alpine)
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
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: true
sbom: true
push: ${{ needs.determine-build-context.outputs.push_enabled == 'true' }}
tags: 548235256840.dkr.ecr.us-west-2.amazonaws.com/n8n-forked/runners:${{ needs.determine-build-context.outputs.n8n_version }}-${{ matrix.platform }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build and push task runners Docker image (distroless)
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
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: true
sbom: true
push: ${{ needs.determine-build-context.outputs.push_enabled == 'true' }}
tags: 548235256840.dkr.ecr.us-west-2.amazonaws.com/n8n-forked/runners-distroless:${{ needs.determine-build-context.outputs.n8n_version }}-${{ matrix.platform }}
cache-from: type=gha
cache-to: type=gha,mode=max
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'
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: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: "${{ secrets.AWS_ACCESS_KEY }}"
aws-secret-access-key: "${{ secrets.AWS_SECRET_ACCESS_KEY }}"
aws-region: "us-west-2"
- name: Login to Amazon ECR
uses: aws-actions/amazon-ecr-login@v2
# - name: Login to Docker Hub
# if: needs.determine-build-context.outputs.push_to_docker == 'true'
# uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
# with:
# username: ${{ secrets.DOCKER_USERNAME }}
# password: ${{ secrets.DOCKER_PASSWORD }}
- name: Create ECR multi-arch manifests
run: |
RELEASE_TYPE="${{ needs.determine-build-context.outputs.release_type }}"
VERSION="${{ needs.determine-build-context.outputs.n8n_version }}"
ECR_BASE="548235256840.dkr.ecr.us-west-2.amazonaws.com/n8n-forked"
# Function to create manifest for an image in ECR
create_ecr_manifest() {
local IMAGE_NAME=$1
local MANIFEST_TAG="${ECR_BASE}/${IMAGE_NAME}:${VERSION}"
echo "Creating ECR manifest for ${IMAGE_NAME}: ${MANIFEST_TAG}"
# Always create multi-arch manifest with both amd64 and arm64
docker buildx imagetools create \
--tag "${MANIFEST_TAG}" \
"${MANIFEST_TAG}-amd64" \
"${MANIFEST_TAG}-arm64"
# Also create 'latest' tag for stable releases
if [[ "$RELEASE_TYPE" == "stable" ]]; then
echo "Creating 'latest' tag for ${IMAGE_NAME}"
docker buildx imagetools create \
--tag "${ECR_BASE}/${IMAGE_NAME}:latest" \
"${MANIFEST_TAG}-amd64" \
"${MANIFEST_TAG}-arm64"
fi
# Also create 'latest' tag for stable releases
if [[ "$RELEASE_TYPE" == "stable" ]]; then
echo "Creating 'latest' tag for ${IMAGE_NAME}"
if [[ "$RELEASE_TYPE" == "branch" ]]; then
docker buildx imagetools create \
--tag "${ECR_BASE}/${IMAGE_NAME}:latest" \
"${MANIFEST_TAG}-amd64"
else
docker buildx imagetools create \
--tag "${ECR_BASE}/${IMAGE_NAME}:latest" \
"${MANIFEST_TAG}-amd64" \
"${MANIFEST_TAG}-arm64"
fi
fi
}
# Create manifests for all images
create_ecr_manifest "n8n"
create_ecr_manifest "runners"
create_ecr_manifest "runners-distroless"
# - 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
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
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 }}