diff --git a/.github/workflows/docker-build-push.yml b/.github/workflows/docker-build-push.yml index f0964f7834..95b687223a 100644 --- a/.github/workflows/docker-build-push.yml +++ b/.github/workflows/docker-build-push.yml @@ -1,4 +1,5 @@ # This workflow is used to build and push the Docker image for n8n +# - determine-build-context: Determines what needs to be built based on the trigger # - build-and-push-docker: This builds on both an ARM64 and AMD64 runner so the builds are native to the platform. Uses blacksmith native runners and build-push-action # - create_multi_arch_manifest: This creates the multi-arch manifest for the Docker image. Needed to recombine the images from the build-and-push-docker job since they are separate runners. # - security-scan: This scans the Docker image for security vulnerabilities using Trivy. @@ -20,9 +21,9 @@ on: type: string release_type: description: 'Release type (stable, nightly, dev)' - required: true + required: false type: string - default: 'dev' + default: 'stable' push_enabled: description: 'Whether to push the built images' required: false @@ -31,17 +32,7 @@ on: workflow_dispatch: inputs: - release_type: - description: 'Release type' - required: true - type: choice - options: - - nightly - - dev - - stable - - branch - default: 'dev' - push_to_registry: + push_enabled: description: 'Push image to registry' required: false type: boolean @@ -60,27 +51,115 @@ on: - '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 + determine-build-context: + name: Determine Build Context + runs-on: ubuntu-latest + outputs: + release_type: ${{ steps.context.outputs.release_type }} + n8n_version: ${{ steps.context.outputs.n8n_version }} + push_enabled: ${{ steps.context.outputs.push_enabled }} + build_matrix: ${{ steps.matrix.outputs.matrix }} + steps: + - name: Determine build context values + id: context + run: | + # Debug info + echo "Event: ${{ github.event_name }}" + echo "Ref: ${{ github.ref }}" + echo "Ref Name: ${{ github.ref_name }}" + # Check if called by another workflow (has n8n_version input) + if [[ -n "${{ inputs.n8n_version }}" ]]; then + # workflow_call - used for releases + 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 }}" == "schedule" ]]; then + # Nightly builds + echo "release_type=nightly" >> $GITHUB_OUTPUT + echo "n8n_version=snapshot" >> $GITHUB_OUTPUT + echo "push_enabled=true" >> $GITHUB_OUTPUT + + elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + # Build branches for Nathan deploy + BRANCH_NAME="${{ github.ref_name }}" + + # Fallback to parsing ref if ref_name is empty + if [[ -z "$BRANCH_NAME" ]] && [[ "${{ github.ref }}" =~ ^refs/heads/(.+)$ ]]; then + BRANCH_NAME="${BASH_REMATCH[1]}" + fi + + # Sanitize branch name for Docker tag + SAFE_BRANCH_NAME=$(echo "$BRANCH_NAME" | tr '/' '-' | tr -cd '[:alnum:]-_') + + if [[ -z "$SAFE_BRANCH_NAME" ]]; then + echo "Error: Could not determine valid branch name" + exit 1 + fi + + echo "release_type=branch" >> $GITHUB_OUTPUT + echo "n8n_version=branch-${SAFE_BRANCH_NAME}" >> $GITHUB_OUTPUT + echo "push_enabled=${{ inputs.push_enabled }}" >> $GITHUB_OUTPUT + + elif [[ "${{ github.event_name }}" == "pull_request" ]]; then + # Direct PR triggers for testing Dockerfile changes + echo "release_type=dev" >> $GITHUB_OUTPUT + echo "n8n_version=pr-${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT + echo "push_enabled=false" >> $GITHUB_OUTPUT + fi + + # Output summary for logs + echo "=== Build Context Summary ===" + echo "Release type: $(grep release_type $GITHUB_OUTPUT | cut -d= -f2)" + echo "N8N version: $(grep n8n_version $GITHUB_OUTPUT | cut -d= -f2)" + echo "Push enabled: $(grep push_enabled $GITHUB_OUTPUT | cut -d= -f2)" + + - name: Determine build matrix + id: matrix + run: | + RELEASE_TYPE="${{ steps.context.outputs.release_type }}" + + # Branch builds only need AMD64, everything else needs both platforms + if [[ "$RELEASE_TYPE" == "branch" ]]; then + MATRIX='{ + "platform": ["amd64"], + "include": [{ + "platform": "amd64", + "runner": "blacksmith-4vcpu-ubuntu-2204", + "docker_platform": "linux/amd64" + }] + }' + else + # All other builds (stable, nightly, dev, PR) need both platforms + 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" + }] + }' + fi + + # Output matrix as single line for GITHUB_OUTPUT + echo "matrix=$(echo $MATRIX | jq -c .)" >> $GITHUB_OUTPUT + echo "Build matrix: $(echo $MATRIX | jq .)" + + 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: 15 + strategy: + matrix: ${{ fromJSON(needs.determine-build-context.outputs.build_matrix) }} outputs: image_ref: ${{ steps.determine-tags.outputs.primary_ghcr_manifest_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 @@ -108,69 +187,11 @@ jobs: run: pnpm build:n8n shell: bash - - name: Determine build context values - id: context - run: | - if [[ "${{ github.event_name }}" == "workflow_call" ]]; then - # workflow_call has n8n_version input (Used in release) - 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 }}" == "schedule" ]]; then - # Nightly builds, build with nightly tag/snapshot - echo "release_type=nightly" >> $GITHUB_OUTPUT - echo "n8n_version=snapshot" >> $GITHUB_OUTPUT - echo "push_enabled=true" >> $GITHUB_OUTPUT - - elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - # Manual dispatch, used for building branches for Nathan deploy - if [[ "${{ inputs.release_type }}" == "branch" ]]; then - # Get branch name with multiple fallbacks - if [[ -n "${{ github.ref_name }}" ]]; then - BRANCH_NAME="${{ github.ref_name }}" - elif [[ "${{ github.ref }}" =~ ^refs/heads/(.+)$ ]]; then - BRANCH_NAME="${BASH_REMATCH[1]}" - else - BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD || echo "unknown") - fi - - SAFE_BRANCH_NAME=$(echo "$BRANCH_NAME" | tr '/' '-' | tr -cd '[:alnum:]-_') - - # Ensure we have a valid branch name - if [[ -z "$SAFE_BRANCH_NAME" ]]; then - echo "Error: Could not determine branch name" - exit 1 - fi - - 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 - # Other manual dispatch types (dev, stable, nightly), are these used, could cleanup? - echo "release_type=${{ inputs.release_type }}" >> $GITHUB_OUTPUT - echo "n8n_version=snapshot" >> $GITHUB_OUTPUT - echo "push_enabled=${{ inputs.push_to_registry }}" >> $GITHUB_OUTPUT - fi - - elif [[ "${{ github.event_name }}" == "pull_request" ]]; then - # Pull requests, used for changes to the Dockerfile to test - echo "release_type=dev" >> $GITHUB_OUTPUT - echo "n8n_version=pr-${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT - echo "push_enabled=false" >> $GITHUB_OUTPUT - fi - - # Debug output - echo "Event: ${{ github.event_name }}" - echo "Release type: $(grep release_type $GITHUB_OUTPUT | cut -d= -f2)" - echo "N8N version: $(grep n8n_version $GITHUB_OUTPUT | cut -d= -f2)" - echo "Push enabled: $(grep push_enabled $GITHUB_OUTPUT | cut -d= -f2)" - - name: Determine Docker tags id: determine-tags run: | - RELEASE_TYPE="${{ steps.context.outputs.release_type }}" - N8N_VERSION_TAG="${{ steps.context.outputs.n8n_version }}" + RELEASE_TYPE="${{ needs.determine-build-context.outputs.release_type }}" + N8N_VERSION_TAG="${{ needs.determine-build-context.outputs.n8n_version }}" GHCR_BASE="ghcr.io/${{ github.repository_owner }}/n8n" DOCKER_BASE="${{ secrets.DOCKER_USERNAME }}/n8n" PLATFORM="${{ matrix.platform }}" @@ -179,7 +200,6 @@ jobs: DOCKER_TAGS_FOR_PUSH="" PRIMARY_GHCR_MANIFEST_TAG_VALUE="" - PRIMARY_DOCKER_MANIFEST_TAG_VALUE="" # Validate inputs if [[ "$RELEASE_TYPE" == "stable" && -z "$N8N_VERSION_TAG" ]]; then @@ -196,21 +216,18 @@ jobs: 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}" + DOCKER_TAGS_FOR_PUSH="${DOCKER_BASE}:${N8N_VERSION_TAG}-${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}" + DOCKER_TAGS_FOR_PUSH="${DOCKER_BASE}:nightly-${PLATFORM}" ;; "branch") PRIMARY_GHCR_MANIFEST_TAG_VALUE="${GHCR_BASE}:${N8N_VERSION_TAG}" GHCR_TAGS_FOR_PUSH="${PRIMARY_GHCR_MANIFEST_TAG_VALUE}-${PLATFORM}" # No Docker Hub tags for branch builds - PRIMARY_DOCKER_MANIFEST_TAG_VALUE="" DOCKER_TAGS_FOR_PUSH="" ;; "dev"|*) @@ -218,14 +235,12 @@ jobs: # PR builds only go to GHCR 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 # Regular dev builds go to both registries 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}" + DOCKER_TAGS_FOR_PUSH="${DOCKER_BASE}:dev-${PLATFORM}" fi ;; esac @@ -243,13 +258,17 @@ jobs: 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 + + # Only output manifest tags from the first platform to avoid duplicates + if [[ "$PLATFORM" == "amd64" ]]; then + echo "primary_ghcr_manifest_tag=${PRIMARY_GHCR_MANIFEST_TAG_VALUE}" >> $GITHUB_OUTPUT + fi - name: Set up Docker Buildx uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 - name: Login to GitHub Container Registry - if: steps.context.outputs.push_enabled == 'true' + if: needs.determine-build-context.outputs.push_enabled == 'true' uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: registry: ghcr.io @@ -257,7 +276,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Login to DockerHub - if: steps.context.outputs.push_enabled == 'true' && needs.build-and-push-docker.outputs.release_type != 'branch' + if: needs.determine-build-context.outputs.push_enabled == 'true' && steps.determine-tags.outputs.dockerhub_platform_tag != '' uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: username: ${{ secrets.DOCKER_USERNAME }} @@ -270,20 +289,20 @@ jobs: 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 }} + 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 - push: ${{ steps.context.outputs.push_enabled == 'true' }} + push: ${{ needs.determine-build-context.outputs.push_enabled == 'true' }} tags: ${{ steps.determine-tags.outputs.tags }} create_multi_arch_manifest: name: Create Multi-Arch Manifest - needs: build-and-push-docker + needs: [determine-build-context, 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' + needs.determine-build-context.outputs.push_enabled == 'true' steps: - name: Set up Docker Buildx uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 @@ -295,63 +314,70 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Determine Docker Hub manifest tag + id: dockerhub_check + run: | + RELEASE_TYPE="${{ needs.determine-build-context.outputs.release_type }}" + N8N_VERSION="${{ needs.determine-build-context.outputs.n8n_version }}" + DOCKER_BASE="${{ secrets.DOCKER_USERNAME }}/n8n" + + # Determine if Docker Hub manifest is needed and construct the tag + case "$RELEASE_TYPE" in + "stable") + echo "DOCKER_MANIFEST_TAG=${DOCKER_BASE}:${N8N_VERSION}" >> $GITHUB_OUTPUT + echo "CREATE_DOCKERHUB_MANIFEST=true" >> $GITHUB_OUTPUT + ;; + "nightly") + echo "DOCKER_MANIFEST_TAG=${DOCKER_BASE}:nightly" >> $GITHUB_OUTPUT + echo "CREATE_DOCKERHUB_MANIFEST=true" >> $GITHUB_OUTPUT + ;; + "dev") + if [[ "$N8N_VERSION" != pr-* ]]; then + echo "DOCKER_MANIFEST_TAG=${DOCKER_BASE}:dev" >> $GITHUB_OUTPUT + echo "CREATE_DOCKERHUB_MANIFEST=true" >> $GITHUB_OUTPUT + else + echo "CREATE_DOCKERHUB_MANIFEST=false" >> $GITHUB_OUTPUT + fi + ;; + *) + echo "CREATE_DOCKERHUB_MANIFEST=false" >> $GITHUB_OUTPUT + ;; + esac + - name: Login to Docker Hub - if: needs.build-and-push-docker.outputs.release_type != 'branch' && needs.build-and-push-docker.outputs.release_type != 'pr' + if: steps.dockerhub_check.outputs.CREATE_DOCKERHUB_MANIFEST == 'true' 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 }}" + RELEASE_TYPE="${{ needs.determine-build-context.outputs.release_type }}" 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 + # For branch builds, only AMD64 is built + if [[ "$RELEASE_TYPE" == "branch" ]]; then + docker buildx imagetools create \ + --tag $MANIFEST_TAG \ + ${MANIFEST_TAG}-amd64 + else + docker buildx imagetools create \ + --tag $MANIFEST_TAG \ + ${MANIFEST_TAG}-amd64 \ + ${MANIFEST_TAG}-arm64 + fi - name: Create Docker Hub multi-arch manifest - if: env.PRIMARY_DOCKER_MANIFEST_TAG != '' + if: steps.dockerhub_check.outputs.CREATE_DOCKERHUB_MANIFEST == 'true' run: | - MANIFEST_TAG="${{ env.PRIMARY_DOCKER_MANIFEST_TAG }}" + MANIFEST_TAG="${{ steps.dockerhub_check.outputs.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 \ @@ -374,11 +400,11 @@ jobs: security-scan: name: Security Scan - needs: [build-and-push-docker] + needs: [determine-build-context, build-and-push-docker] if: | success() && - (github.event_name == 'schedule' || - (github.event_name == 'workflow_call' && inputs.release_type == 'stable')) + (needs.determine-build-context.outputs.release_type == 'stable' || + needs.determine-build-context.outputs.release_type == 'nightly') uses: ./.github/workflows/security-trivy-scan-callable.yml with: image_ref: ${{ needs.build-and-push-docker.outputs.image_ref }}