ci: preload Namespace tools and use native caches

This commit is contained in:
Declan Carroll 2026-10-10 11:01:24 +01:00
parent 1ad4e1500f
commit 313194ff50
7 changed files with 144 additions and 11 deletions

View File

@ -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'

View File

@ -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

View File

@ -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

29
.github/namespace/Dockerfile vendored Normal file
View File

@ -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

35
.github/namespace/README.md vendored Normal file
View File

@ -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.

20
.github/namespace/profile.json vendored Normal file
View File

@ -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
}
]
}

View File

@ -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 }}