From 313194ff50e946a63c3f545d8564506f41a40db1 Mon Sep 17 00:00:00 2001 From: Declan Carroll Date: Sat, 10 Oct 2026 11:01:24 +0100 Subject: [PATCH] ci: preload Namespace tools and use native caches --- .github/WORKFLOWS.md | 2 ++ .github/actionlint.yml | 2 +- .github/actions/setup-nodejs/action.yml | 44 ++++++++++++++++++++----- .github/namespace/Dockerfile | 29 ++++++++++++++++ .github/namespace/README.md | 35 ++++++++++++++++++++ .github/namespace/profile.json | 20 +++++++++++ .github/workflows/docker-build-push.yml | 23 +++++++++++-- 7 files changed, 144 insertions(+), 11 deletions(-) create mode 100644 .github/namespace/Dockerfile create mode 100644 .github/namespace/README.md create mode 100644 .github/namespace/profile.json diff --git a/.github/WORKFLOWS.md b/.github/WORKFLOWS.md index e06bc7a3fff..60104779305 100644 --- a/.github/WORKFLOWS.md +++ b/.github/WORKFLOWS.md @@ -648,6 +648,8 @@ Composite actions in `.github/actions/`: ### setup-nodejs +For the opt-in Namespace Docker trial, see [Configure the Namespace Docker trial](namespace/README.md). + ```yaml inputs: node-version: # default: '26.5.1' diff --git a/.github/actionlint.yml b/.github/actionlint.yml index c9371b20fe4..5ff4c69f179 100644 --- a/.github/actionlint.yml +++ b/.github/actionlint.yml @@ -7,5 +7,5 @@ self-hosted-runner: - blacksmith-8vcpu-ubuntu-2204-arm - blacksmith-32vcpu-ubuntu-2204 - blacksmith-32vcpu-ubuntu-2204-arm - - nscloud-ubuntu-22.04-amd64-8x16 + - namespace-profile-n8n-docker-poc - ubuntu-slim diff --git a/.github/actions/setup-nodejs/action.yml b/.github/actions/setup-nodejs/action.yml index 1bc770e31f4..c5a9ab77627 100644 --- a/.github/actions/setup-nodejs/action.yml +++ b/.github/actions/setup-nodejs/action.yml @@ -1,11 +1,16 @@ # This action works transparently on both Blacksmith and GitHub-hosted runners. # Blacksmith runners benefit from transparent caching and optional Docker layer caching. # GitHub-hosted runners use standard GitHub Actions caching. +# The Namespace POC uses preloaded tools and native pnpm and Turbo caches. name: 'Node.js Build Setup' description: 'Configures Node.js with pnpm, installs Aikido SafeChain for supply chain protection, installs dependencies, enables Turborepo caching, (optional) sets up Docker layer caching, and builds the project or an optional command.' inputs: + namespace-poc: + description: 'Use the preloaded Namespace POC toolchain and native caches.' + required: false + default: 'false' node-version: description: 'Node.js version to use. Pinned to 26.5.1 by default for reproducible builds.' required: false @@ -34,6 +39,14 @@ inputs: runs: using: 'composite' steps: + - name: Activate Namespace toolchain + if: inputs.namespace-poc == 'true' + shell: bash + run: | + echo /opt/n8n-ci/node/bin >> "$GITHUB_PATH" + echo /opt/n8n-ci/pnpm >> "$GITHUB_PATH" + echo "PNPM_HOME=$HOME/setup-pnpm" >> "$GITHUB_ENV" + # An empty key makes the builder use a local, uncached one. The job stays # green and loses the cache. Fail here instead. - name: Verify Docker cache key is set @@ -51,7 +64,7 @@ runs: # Windows cannot activate a POSIX home path from the restored cache. - name: Restore pnpm executable - if: runner.os != 'Windows' + if: runner.os != 'Windows' && inputs.namespace-poc != 'true' id: cache-pnpm-exe uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: @@ -60,7 +73,7 @@ runs: - name: Setup pnpm id: setup-pnpm - if: steps.cache-pnpm-exe.outputs.cache-hit != 'true' + if: steps.cache-pnpm-exe.outputs.cache-hit != 'true' && inputs.namespace-poc != 'true' continue-on-error: true uses: pnpm/setup@703c52620218391530e48b9e8870d5c0082e1b9b # v2.1.0 with: @@ -98,7 +111,7 @@ runs: # Save before later job failures can prevent cache creation. - name: Save pnpm executable - if: runner.os != 'Windows' && steps.cache-pnpm-exe.outputs.cache-hit != 'true' + if: runner.os != 'Windows' && steps.cache-pnpm-exe.outputs.cache-hit != 'true' && inputs.namespace-poc != 'true' uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ~/setup-pnpm @@ -117,13 +130,14 @@ runs: # quietly takes over. Caching the parent version dir captures the marker # alongside the toolchain. Key bumped to v2 to invalidate stale entries. - name: Restore Node.js Toolcache - if: runner.os == 'Linux' && runner.arch == 'X64' + if: runner.os == 'Linux' && runner.arch == 'X64' && inputs.namespace-poc != 'true' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: /opt/hostedtoolcache/node/${{ inputs.node-version }} key: node-toolcache-v2-${{ runner.os }}-${{ runner.arch }}-${{ inputs.node-version }} - name: Setup Node.js + if: inputs.namespace-poc != 'true' uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ inputs.node-version }} @@ -169,8 +183,15 @@ runs: echo "path=$PNPM_CACHE_PATH" >> "$GITHUB_OUTPUT" echo "version=$(pnpm --version)" >> "$GITHUB_OUTPUT" + - name: Mount Namespace pnpm cache + if: inputs.namespace-poc == 'true' && inputs.install-command != '' + uses: namespacelabs/nscloud-cache-action@1124a6f3ce44e5cf84cc22111530961f4d2a15f9 # v1 + with: + cache: pnpm + path: ${{ steps.pnpm-metadata-cache.outputs.path }} + - name: Restore pnpm metadata cache - if: ${{ inputs.install-command != '' }} + if: inputs.install-command != '' && inputs.namespace-poc != 'true' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ${{ steps.pnpm-metadata-cache.outputs.path }} @@ -190,6 +211,7 @@ runs: # radius across jobs, retry covers the first-job-per-key case where the # CDN must be hit. Keep the version in sync with the download step below. - name: Restore Aikido SafeChain Binary + if: inputs.namespace-poc != 'true' id: cache-safe-chain uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: @@ -201,7 +223,7 @@ runs: # script also runs `setup-ci`, so on a cache miss this both downloads AND # activates; the Activate step below then re-runs `setup-ci` idempotently. - name: Download Aikido SafeChain - if: steps.cache-safe-chain.outputs.cache-hit != 'true' + if: steps.cache-safe-chain.outputs.cache-hit != 'true' && inputs.namespace-poc != 'true' run: | VERSION="1.5.20" EXPECTED_SHA256="0ad25efe15d1fa56105157a454d647223e78eb0c53d1f85e3d10afcd722e7bfd" @@ -360,11 +382,17 @@ runs: # `fetch failed`, turning the whole job red. Skip it so the one Windows # build job stays green; Linux/Blacksmith jobs keep remote caching. - name: Configure Turborepo Cache - if: steps.turbo-server.outputs.running != 'true' && runner.os != 'Windows' + if: steps.turbo-server.outputs.running != 'true' && runner.os != 'Windows' && inputs.namespace-poc != 'true' uses: rharkor/caching-for-turbo@2238fae6eb9a9936f92356f54cb3660200d105e7 # v2.5.1 with: server-port: 0 + - name: Configure Namespace Turborepo cache + if: inputs.namespace-poc == 'true' + uses: namespace-actions/setup-turbocache@a94b84c111f2cc781a4a83c4d07a99f2c63e33d6 # v0 + with: + team: n8n-docker-poc + - name: Setup Docker Builder for Docker Cache (Blacksmith) if: ${{ inputs.enable-docker-cache == 'true' && contains(runner.name, 'blacksmith') }} uses: useblacksmith/setup-docker-builder@a5256a73e30f09e37e3eceb8ca36043d17621d24 # v2.1.0 @@ -372,7 +400,7 @@ runs: cache-key: ${{ inputs.docker-cache-key }} - name: Setup Docker Builder (GitHub fallback) - if: ${{ inputs.enable-docker-cache == 'true' && !contains(runner.name, 'blacksmith') }} + if: inputs.enable-docker-cache == 'true' && !contains(runner.name, 'blacksmith') && inputs.namespace-poc != 'true' uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Build Project diff --git a/.github/namespace/Dockerfile b/.github/namespace/Dockerfile new file mode 100644 index 00000000000..bc8d1de17a4 --- /dev/null +++ b/.github/namespace/Dockerfile @@ -0,0 +1,29 @@ +# Keep these versions aligned with docker-build-push.yml, package.json, and setup-nodejs. +ARG NAMESPACE_BASE_IMAGE_REF +FROM ${NAMESPACE_BASE_IMAGE_REF} + +USER root + +RUN mkdir -p /opt/n8n-ci/node /opt/n8n-ci/pnpm /home/runner/.safe-chain/bin \ + && curl --fail --silent --show-error --location --retry 3 \ + https://nodejs.org/dist/v26.10.0/node-v26.10.0-linux-x64.tar.xz -o /tmp/node.tar.xz \ + && echo 'ca70e9e349de048b9522abb3adc05b3bd6f43c5ffd3ec57916c7da292f59f022 /tmp/node.tar.xz' | sha256sum -c - \ + && tar -xJf /tmp/node.tar.xz -C /opt/n8n-ci/node --strip-components=1 \ + && curl --fail --silent --show-error --location --retry 3 \ + https://github.com/pnpm/pnpm/releases/download/v12.4.2/pnpm-linux-x64.tar.gz -o /tmp/pnpm.tar.gz \ + && echo 'ce1ed690fe9c2f091d7267e1afbe9380abb08bb577e95348fda194a41147d2ec /tmp/pnpm.tar.gz' | sha256sum -c - \ + && tar -xzf /tmp/pnpm.tar.gz -C /opt/n8n-ci/pnpm \ + && curl --fail --silent --show-error --location --retry 3 \ + https://github.com/AikidoSec/safe-chain/releases/download/1.5.20/safe-chain-linuxstatic-x64 \ + -o /home/runner/.safe-chain/bin/safe-chain \ + && echo '13636819384f1e672c8c93c4ee6496113f9bde4b038ff9c59220fbb25f165199 /home/runner/.safe-chain/bin/safe-chain' | sha256sum -c - \ + && chmod +x /home/runner/.safe-chain/bin/safe-chain \ + && chown -R runner:runner /home/runner/.safe-chain \ + && rm /tmp/node.tar.xz /tmp/pnpm.tar.gz + +ENV PATH="/opt/n8n-ci/node/bin:/opt/n8n-ci/pnpm:${PATH}" + +USER runner +RUN test "$(node --version)" = 'v26.10.0' \ + && test "$(pnpm --version)" = '12.4.2' \ + && /home/runner/.safe-chain/bin/safe-chain -v diff --git a/.github/namespace/README.md b/.github/namespace/README.md new file mode 100644 index 00000000000..8dc4c9b618a --- /dev/null +++ b/.github/namespace/README.md @@ -0,0 +1,35 @@ +# Configure the Namespace Docker trial + +Use a Namespace workspace connected to this repository. Run these commands from the repository root. + +1. Authenticate with `nsc login` or set `NSC_TOKEN_FILE` to your token file. +2. Create the runner profile: + + ```bash + PROFILE_ID=$(nsc github profile create --spec_file .github/namespace/profile.json -o json | jq -r .profile_id) + ``` + + For an existing profile, get its ID with `nsc github profile list -o json`. + +3. Attach the custom image: + + ```bash + nsc github profile update --profile_id "$PROFILE_ID" --dockerfile .github/namespace/Dockerfile -o json + ``` + +4. Wait for the image to become ready in the [Namespace profile dashboard](https://cloud.namespace.so/workspace/actions/profiles). +5. Push your trial branch. +6. Start an AMD64 build: + + ```bash + gh workflow run docker-build-push.yml --ref "$(git branch --show-current)" \ + -f namespace_poc=true -f push_enabled=true -f include_arm64=false + ``` + +Verify that the job uses `namespace-profile-n8n-docker-poc` and passes the Node, pnpm, and SafeChain checks. +Check that the pnpm cache mounts and Turbo uses the Namespace endpoint. +Run the same commit again to measure warm caches. + +When tool versions change, update the Dockerfile versions and checksums together. +Match Node to `docker-build-push.yml`, pnpm to `package.json`, and SafeChain to `setup-nodejs/action.yml`. +Repeat step 3 to rebuild the image. diff --git a/.github/namespace/profile.json b/.github/namespace/profile.json new file mode 100644 index 00000000000..7790d481528 --- /dev/null +++ b/.github/namespace/profile.json @@ -0,0 +1,20 @@ +{ + "tag": "n8n-docker-poc", + "description": "n8n Docker trial with preloaded tools and Namespace caches", + "os": "ubuntu-22.04", + "instance_shape": { + "virtual_cpu": 8, + "memory_megabytes": 16384, + "machine_arch": "amd64", + "os": "linux" + }, + "builder_mode": "USE_REMOTE_BUILDER", + "cache_volume_settings": [ + { + "volume_tag": "n8n-docker-poc", + "volume_size_gb": 20, + "bundled_git_mirror": true, + "bundled_runner_actions_cache": true + } + ] +} diff --git a/.github/workflows/docker-build-push.yml b/.github/workflows/docker-build-push.yml index b30a98a9421..8e4c967fcf8 100644 --- a/.github/workflows/docker-build-push.yml +++ b/.github/workflows/docker-build-push.yml @@ -134,12 +134,21 @@ jobs: build-app: name: Build application for Docker images needs: determine-build-context - runs-on: ${{ inputs.namespace_poc && 'nscloud-ubuntu-22.04-amd64-8x16' || 'blacksmith-8vcpu-ubuntu-2204' }} + runs-on: ${{ inputs.namespace_poc && 'namespace-profile-n8n-docker-poc' || 'blacksmith-8vcpu-ubuntu-2204' }} timeout-minutes: 35 permissions: contents: read steps: + - name: Checkout code with Namespace + if: inputs.namespace_poc + uses: namespacelabs/nscloud-checkout-action@66f2dc6f6c42a8ac6c4e53473c4840006822831e # v9 + with: + ref: ${{ inputs.ref }} + fetch-depth: 1 + persist-credentials: false + - name: Checkout code + if: ${{ !inputs.namespace_poc }} uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ inputs.ref }} @@ -149,6 +158,7 @@ jobs: - name: Setup and Build uses: ./.github/actions/setup-nodejs with: + namespace-poc: ${{ inputs.namespace_poc && 'true' || 'false' }} # The image compiles native bindings against this Node version later. node-version: ${{ env.NODE_VERSION }} build-command: pnpm build:n8n @@ -168,7 +178,7 @@ jobs: build-and-push-docker: name: Build and Push Docker Image (${{ matrix.platform }}) needs: [determine-build-context, build-app] - runs-on: ${{ inputs.namespace_poc && 'nscloud-ubuntu-22.04-amd64-8x16' || matrix.runner }} + runs-on: ${{ inputs.namespace_poc && 'namespace-profile-n8n-docker-poc' || matrix.runner }} timeout-minutes: 35 permissions: actions: read @@ -191,7 +201,16 @@ jobs: runners_date_manifest_tag: ${{ steps.determine-tags.outputs.runners_date_primary_tag }} runners_distroless_date_manifest_tag: ${{ steps.determine-tags.outputs.runners_distroless_date_primary_tag }} steps: + - name: Checkout code with Namespace + if: inputs.namespace_poc + uses: namespacelabs/nscloud-checkout-action@66f2dc6f6c42a8ac6c4e53473c4840006822831e # v9 + with: + ref: ${{ inputs.ref }} + fetch-depth: 1 + persist-credentials: false + - name: Checkout code + if: ${{ !inputs.namespace_poc }} uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ inputs.ref }}