mirror of
https://github.com/n8n-io/n8n.git
synced 2026-07-28 11:35:03 +02:00
774 lines
43 KiB
Markdown
774 lines
43 KiB
Markdown
# GitHub Actions & CI/CD Documentation
|
|
|
|
Complete reference for n8n's `.github/` folder.
|
|
|
|
---
|
|
|
|
## Folder Structure
|
|
|
|
```
|
|
.github/
|
|
├── WORKFLOWS.md # This document
|
|
├── CI-TELEMETRY.md # Telemetry & metrics guide
|
|
├── CODEOWNERS # Team ownership for PR reviews
|
|
├── pull_request_template.md # PR description template
|
|
├── pull_request_title_conventions.md # Title format rules (Angular)
|
|
├── actionlint.yml # Workflow linter config
|
|
├── docker-compose.yml # DB services for local testing
|
|
├── test-metrics/
|
|
│ └── playwright.json # E2E performance baselines
|
|
├── ISSUE_TEMPLATE/
|
|
│ ├── config.yml # Routes to community/security
|
|
│ └── 01-bug.yml # Structured bug report form
|
|
├── scripts/ # Automation scripts
|
|
│ ├── bump-versions.mjs # Calculate next version
|
|
│ ├── update-changelog.mjs # Generate CHANGELOG
|
|
│ ├── trim-fe-packageJson.js # Strip frontend devDeps
|
|
│ ├── ensure-provenance-fields.mjs # Add license/author fields
|
|
│ ├── validate-docs-links.js # Check documentation URLs
|
|
│ ├── send-build-stats.mjs # Turbo build telemetry → webhook
|
|
│ └── docker/
|
|
│ ├── docker-tags.mjs # Generate image tags
|
|
│ └── docker-config.mjs # Build context config
|
|
├── actions/ # Custom composite actions
|
|
│ ├── setup-nodejs/ # pnpm + Node + Turbo cache
|
|
│ └── docker-registry-login/ # GHCR + DockerHub auth
|
|
└── workflows/ # GitHub Actions workflows
|
|
```
|
|
|
|
---
|
|
|
|
## Architecture Overview
|
|
|
|
```
|
|
┌────────────────────────────────────────────────────────────────────────────┐
|
|
│ n8n CI/CD ARCHITECTURE │
|
|
├────────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ TRIGGERS PIPELINES OUTPUTS │
|
|
│ ──────── ───────── ─────── │
|
|
│ │
|
|
│ ┌──────────┐ ┌──────────────────────────────────┐ ┌────────────┐ │
|
|
│ │ PR │───▶│ ci-pull-requests.yml │───▶│ Checks │ │
|
|
│ └──────────┘ │ ├─ build + paths-filter │ │ Gate │ │
|
|
│ │ ├─ unit-test (reusable) │ └────────────┘ │
|
|
│ ┌──────────┐ │ ├─ typecheck │ │
|
|
│ │ Push │───▶│ ├─ lint (reusable) │ ┌────────────┐ │
|
|
│ │ master │ │ ├─ e2e-tests (reusable) │───▶│ Coverage │ │
|
|
│ └──────────┘ │ └─ security (if .github/**) │ └────────────┘ │
|
|
│ └──────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌──────────┐ ┌──────────────────────────────────┐ ┌────────────┐ │
|
|
│ │ Merge │───▶│ release-publish.yml │───▶│ NPM │ │
|
|
│ │release/* │ │ ├─ publish-to-npm │ ├────────────┤ │
|
|
│ └──────────┘ │ ├─ publish-to-docker-hub │───▶│ Docker │ │
|
|
│ │ ├─ create-github-release │ ├────────────┤ │
|
|
│ │ ├─ create-sentry-release │───▶│ Sentry │ │
|
|
│ │ └─ generate-sbom │ ├────────────┤ │
|
|
│ └──────────────────────────────────┘───▶│ SBOM │ │
|
|
│ └────────────┘ │
|
|
│ ┌──────────┐ ┌──────────────────────────────────┐ │
|
|
│ │ Schedule │───▶│ Nightly/Weekly Jobs │ ┌────────────┐ │
|
|
│ │ (cron) │ │ ├─ docker-build-push (nightly) │───▶│ Images │ │
|
|
│ └──────────┘ │ ├─ test-benchmark-nightly │───▶│ Metrics │ │
|
|
│ │ ├─ test-e2e-vm-expressions │ │
|
|
│ │ └─ test-e2e-coverage-weekly │ │
|
|
│ └──────────────────────────────────┘ │
|
|
│ │
|
|
└────────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Quick Reference
|
|
|
|
| Prefix | Purpose |
|
|
|------------|-----------------------------------------|
|
|
| `test-` | Testing (E2E, unit, visual, benchmarks) |
|
|
| `ci-` | Continuous integration |
|
|
| `util-` | Utilities (notifications, sync, Claude) |
|
|
| `build-` | Build processes |
|
|
| `release-` | Release automation |
|
|
| `sec-` | Security scanning |
|
|
| Other | Docker, SBOM, patch releases |
|
|
|
|
---
|
|
|
|
## PR Title Conventions
|
|
|
|
Commits drive changelog generation. Follow Angular convention:
|
|
|
|
```
|
|
Format: <type>(<scope>): <summary>
|
|
|
|
Types: feat | fix | perf | test | docs | refactor | build | ci | chore
|
|
Scopes: API | benchmark | core | editor | * Node (optional)
|
|
|
|
Examples:
|
|
feat(editor): Add dark mode toggle
|
|
fix(Slack Node): Handle rate limiting correctly
|
|
perf(core): Optimize workflow execution by 20%
|
|
refactor: Migrate to TypeScript strict mode (no-changelog)
|
|
|
|
Breaking Changes: Add "BREAKING CHANGE:" footer with migration guide
|
|
Deprecations: Add "DEPRECATED:" footer with update path
|
|
Skip Changelog: Add "(no-changelog)" to PR title
|
|
```
|
|
|
|
See `pull_request_title_conventions.md` for full spec.
|
|
|
|
---
|
|
|
|
## What Runs When You Open a PR
|
|
|
|
### Flow Diagram
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────────────────────────┐
|
|
│ PR OPENED / UPDATED │
|
|
└─────────────────────────────────────┬────────────────────────────────────────┘
|
|
│
|
|
┌───────────────────────────┴───────────────────────┐
|
|
▼ ▼
|
|
┌───────────────────────────┐ ┌───────────────────────────┐
|
|
│ ci-pull-requests.yml │ │ ci-check-pr-title.yml │
|
|
│ (main orchestrator) │ │ (validates title format) │
|
|
└─────────────┬─────────────┘ └───────────────────────────┘
|
|
│
|
|
▼
|
|
┌───────────────────────────┐
|
|
│ install-and-build │
|
|
│ └─ paths-filter │──────────────────────────────────────────┐
|
|
└─────────────┬─────────────┘ │
|
|
│ │
|
|
│ [if non-Python files changed] │ [if .github/** changed]
|
|
│ │
|
|
┌─────────┼─────────┬─────────────┬─────────────┐ │
|
|
│ │ │ │ │ │
|
|
▼ ▼ ▼ ▼ ▼ ▼
|
|
┌───────┐ ┌───────┐ ┌───────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐
|
|
│ unit │ │ type │ │ lint │ │ e2e-tests │ │ security │ │ security │
|
|
│ test │ │ check │ │ │ │ │ │ checks │ │ checks │
|
|
└───┬───┘ └───┬───┘ └───┬───┘ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘
|
|
│ │ │ │ │ │
|
|
│ │ │ ┌─────┴─────┐ │ │
|
|
│ │ │ ▼ ▼ │ │
|
|
│ │ │ Internal Fork PR │ │
|
|
│ │ │ 14 shards 6 shards │ │
|
|
│ │ │ Docker SQLite │ │
|
|
│ │ │ │ │
|
|
└─────────┴─────────┴──────────┬───────────────┴────────────────┘
|
|
│
|
|
▼
|
|
┌──────────────────────────────┐
|
|
│ required-checks │
|
|
│ (merge gate) │
|
|
└──────────────────────────────┘
|
|
```
|
|
|
|
### Path-Filtered Workflows
|
|
|
|
These only run if specific files changed:
|
|
|
|
| Files Changed | Workflow | Branch |
|
|
|------------------------------------------------------------------------|-----------------------------|------------|
|
|
| `packages/@n8n/task-runner-python/**` | `ci-python.yml` | any |
|
|
| `packages/cli/src/databases/**`, `*.entity.ts`, `*.repository.ts` | `test-db.yml` | any |
|
|
| `packages/frontend/@n8n/storybook/**`, design-system, chat | `release-storybook.yml` | master |
|
|
| `docker/images/n8n-base/Dockerfile` | `build-base-image.yml` | any |
|
|
| `**/package.json`, `**/turbo.json` | `build-windows.yml` | master |
|
|
| `packages/@n8n/ai-workflow-builder.ee/evaluations/programmatic/python/**` | `test-evals-python.yml` | any |
|
|
| `packages/@n8n/benchmark/**` | `build-benchmark-image.yml` | master |
|
|
| `packages/cli/src/public-api/**/*.{css,yaml,yml}` | `util-sync-api-docs.yml` | master |
|
|
| `packages/@n8n/instance-ai/src/**`, `packages/@n8n/instance-ai/skills/**`, `packages/@n8n/instance-ai/knowledge-base/**`, `packages/@n8n/instance-ai/evaluations/**`, `packages/cli/src/modules/instance-ai/**`, `packages/core/src/execution-engine/eval-mock-helpers.ts` | `ci-instance-ai-evals.yml` | on PR `opened` / `reopened` / `ready_for_review` |
|
|
|
|
### On PR Review
|
|
|
|
| Event | Workflow | Condition |
|
|
|----------------------------|-----------------------------|------------------------------------------------------|
|
|
| Review approved | `release-chromatic.yml` | + design files changed |
|
|
| Comment with `@claude` | `util-claude.yml` | mention in any comment |
|
|
| Any review | `util-notify-pr-status.yml` | not community-labeled |
|
|
|
|
**Why Instance AI evals fire once per PR state-change, not per push:** the
|
|
workflow eval is the most expensive job in PR CI (LLM-bound builds). Running it
|
|
on every push made cost untenable; firing on every review approval cascaded
|
|
through the dismiss-stale-on-push → re-approve loop, which also blew up.
|
|
The current trigger fires once per `opened` / `reopened` / `ready_for_review`
|
|
on a non-fork PR touching the eval surface, and runs the `pr` test-case dataset
|
|
(a small set of high-reliability, capability-diverse cases) instead of the full
|
|
suite. Test cases are pulled at run time from the LangTracer suite
|
|
`n8n-workflows` — the source of truth; CI has no disk fallback (local runs
|
|
keep `--source disk` for authoring). To
|
|
re-run after pushing a fix, dispatch `ci-instance-ai-evals.yml` with the PR
|
|
number (optionally `tier: full` for broader coverage) — results post back to
|
|
the PR. The lighter `test-evals-discovery.yml` still runs on every push as part
|
|
of `ci-pull-requests.yml`.
|
|
|
|
**`ci-instance-ai-evals.yml` is the PR gate; `test-evals-instance-ai.yml` is
|
|
the lab bench.** The gate deliberately exposes only PR re-runs. Anything that
|
|
isn't PR gating — baselines, model experiments, arbitrary branch runs — goes
|
|
through `test-evals-instance-ai.yml`'s own dispatch form ("Instance AI
|
|
Evals: Experiments"): full knob set (branch, filter, tier, suite,
|
|
iterations, experiment-name, model), no per-PR cancellation (dispatches run in parallel, e.g. concurrent
|
|
model-comparison arms), and SHA-keyed docker cache hits on master. Evals never
|
|
run on fork PRs: the event trigger gates on `head.repo.fork`, and the `pr`
|
|
re-run path refuses fork PRs in `resolve` (dispatched runs carry secrets).
|
|
|
|
**MCP workflow evals (`ci-mcp-evals.yml`) are manual only (`workflow_dispatch`),
|
|
never per-PR or scheduled.** They reuse the Instance AI verifier but build each
|
|
workflow through the instance MCP server by driving the `claude` CLI, which adds
|
|
Anthropic build cost on top of the verifier — too expensive to run
|
|
automatically. The job boots `lanes` n8n containers on one runner and runs a
|
|
single `eval:instance-ai --build-via-mcp` process: each case is built by driving
|
|
its lane's own MCP server with `claude`, then verified on that same lane
|
|
(work-stealing across lanes, capped per-lane). One process → one experiment in
|
|
the isolated `mcp-workflow-evals` LangSmith dataset, so there is no shard/merge
|
|
step. Dispatch from the Actions tab (set `experiment-name=mcp-baseline` to
|
|
refresh the baseline, `filter=<slug>` to run a single case, or `lanes` to widen
|
|
parallelism). See the `--build-via-mcp` section in
|
|
`packages/@n8n/instance-ai/evaluations/README.md`.
|
|
|
|
### On PR Close/Merge
|
|
|
|
| Event | Workflow |
|
|
|----------------------------|-----------------------------|
|
|
| PR closed (any) | `util-notify-pr-status.yml` |
|
|
| PR merged to `release/*` | `release-publish.yml` |
|
|
|
|
### Manual Triggers (PR Comments)
|
|
|
|
| Command | Workflow | Permissions |
|
|
|--------------------|------------------------------|---------------------|
|
|
| `/test-workflows` | `test-workflows-callable.yml`| admin/write/maintain|
|
|
|
|
**Why:** Re-run tests without pushing commits. Useful for flaky test investigation.
|
|
|
|
### Other Manual Workflows
|
|
|
|
| Workflow | Purpose |
|
|
|---------------------------|---------------------------------------------------------|
|
|
| `util-claude-task.yml` | Run Claude Code to complete a task and create a PR |
|
|
| `util-data-tooling.yml` | SQLite/PostgreSQL export/import validation (manual) |
|
|
|
|
#### Claude Task Runner (`util-claude-task.yml`)
|
|
|
|
Runs Claude Code to complete a task, then creates a PR with the changes. Use for well-specced tasks or simple fixes. Can be triggered via GitHub UI or API.
|
|
|
|
Claude reads templates from `.github/claude-templates/` for task-specific guidance. Add new templates as needed for recurring task types.
|
|
|
|
**Inputs:**
|
|
- `task` - Description of what Claude should do
|
|
- `user_token` - GitHub PAT (PR will be authored by the token owner)
|
|
|
|
**Token requirements** (fine-grained PAT):
|
|
- Repository: `n8n-io/n8n`
|
|
- Contents: `Read and write`
|
|
- Pull requests: `Read and write`
|
|
|
|
**Governance:** If you provide your personal PAT, you cannot approve the resulting PR. For automated/bot use cases (e.g., dependabot-style updates via n8n workflows), an app token can be used instead.
|
|
|
|
---
|
|
|
|
## Workflow Call Graph
|
|
|
|
Shows which workflows call which reusable workflows:
|
|
|
|
```
|
|
CALLER REUSABLE WORKFLOW
|
|
───────────────────────────────────────────────────────────────────────────────
|
|
|
|
ci-pull-requests.yml
|
|
├──────────────────────────▶ test-unit-reusable.yml
|
|
├──────────────────────────▶ test-linting-reusable.yml
|
|
├──────────────────────────▶ test-e2e-reusable.yml
|
|
└──────────────────────────▶ sec-ci-reusable.yml
|
|
└──────────▶ sec-poutine-reusable.yml
|
|
|
|
ci-master.yml
|
|
├──────────────────────────▶ test-unit-reusable.yml
|
|
└──────────────────────────▶ test-linting-reusable.yml
|
|
|
|
release-publish.yml
|
|
├──────────────────────────▶ docker-build-push.yml
|
|
│ └──────────▶ security-trivy-scan-callable.yml
|
|
└──────────────────────────▶ sbom-generation-callable.yml
|
|
|
|
test-workflows-nightly.yml (manual dispatch only — nightly schedule disabled, DEVP-544)
|
|
└──────────────────────────▶ test-workflows-callable.yml
|
|
|
|
test-e2e-vm-expressions-nightly.yml
|
|
└──────────────────────────▶ test-e2e-reusable.yml
|
|
|
|
PR Comment Dispatchers (triggered by /command in PR comments):
|
|
test-workflows-pr-comment.yml
|
|
└──────────────────────────▶ test-workflows-callable.yml
|
|
```
|
|
|
|
---
|
|
|
|
## Release Lifecycle
|
|
|
|
```
|
|
┌────────────────────────────────────────────────────────────────────────────┐
|
|
│ RELEASE LIFECYCLE │
|
|
├────────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ STAGE 1: Create Release PR │
|
|
│ ─────────────────────────── │
|
|
│ Trigger: Manual workflow_dispatch │
|
|
│ │
|
|
│ release-create-pr.yml │
|
|
│ ├─ bump-versions.mjs ────────▶ Calculate X.Y.Z │
|
|
│ ├─ update-changelog.mjs ─────▶ Generate CHANGELOG │
|
|
│ └─ Create PR: release-pr/X.Y.Z → release/X.Y.Z │
|
|
│ │
|
|
│ Inputs: │
|
|
│ ├─ release-type: patch │ minor │ major │ experimental │ premajor │
|
|
│ └─ base-branch: default master │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ STAGE 2: CI Validation │
|
|
│ ─────────────────────── │
|
|
│ ci-pull-requests.yml runs full suite │
|
|
│ ├─ NO ci-check-pr-title.yml (skipped for release branches) │
|
|
│ └─ NO release-chromatic.yml (skipped) │
|
|
│ │ │
|
|
│ ▼ [Merge PR] │
|
|
│ STAGE 3: Publish │
|
|
│ ─────────────── │
|
|
│ release-publish.yml (triggered on merge to release/*) │
|
|
│ ├─ publish-to-npm │
|
|
│ │ ├─ trim-fe-packageJson.js ───▶ Strip devDeps │
|
|
│ │ ├─ ensure-provenance-fields.mjs ───▶ Add license fields │
|
|
│ │ └─ npm publish (tag: rc or latest) │
|
|
│ ├─ publish-to-docker-hub ────────▶ docker-build-push.yml │
|
|
│ │ └─ Multi-arch: amd64 + arm64 │
|
|
│ ├─ create-github-release │
|
|
│ ├─ create-sentry-release (sourcemaps) │
|
|
│ ├─ generate-sbom ────────────────▶ sbom-generation-callable.yml │
|
|
│ │ └─ CycloneDX + Cosign signing │
|
|
│ └─ trigger-release-note (stable only) │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ STAGE 4: Channel Promotion (optional) │
|
|
│ ────────────────────────────────────── │
|
|
│ Trigger: Manual release-push-to-channel.yml │
|
|
│ ├─ beta ─────▶ npm tags: next, beta │
|
|
│ └─ stable ───▶ npm tags: latest, stable │
|
|
│ │
|
|
└────────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Other Release Workflows
|
|
|
|
| Workflow | Trigger | Purpose |
|
|
|----------------------------------|--------------------|------------------------------------------------|
|
|
| `release-standalone-package.yml` | Manual dispatch | Release individual packages (@n8n/codemirror-lang, @n8n/create-node, etc.) |
|
|
| `create-patch-release-branch.yml`| Manual dispatch | Cherry-pick commits for patch releases |
|
|
|
|
---
|
|
|
|
## Fork vs Internal PR
|
|
|
|
| Aspect | Internal PR | Fork PR |
|
|
|--------------------|----------------------------------|-------------------------|
|
|
| E2E Runner | `blacksmith-2vcpu-ubuntu-2204` | `ubuntu-latest` |
|
|
| E2E Mode | `docker-build` (multi-main) | `local` (SQLite) |
|
|
| E2E Shards | 14 + 2 | 6 + 2 |
|
|
| Test Command | `test:container:multi-main:*` | `test:local:*` |
|
|
| Secrets | Full access | None |
|
|
| Currents Recording | Yes | No |
|
|
| Failure Artifacts | No | Yes |
|
|
|
|
**Why:** Fork PRs cannot access repository secrets. Local mode with SQLite provides feedback without paid services.
|
|
|
|
---
|
|
|
|
## ci-master.yml
|
|
|
|
Runs on push to `master` or `1.x`:
|
|
|
|
```
|
|
Push to master/1.x
|
|
├─ build-github (populate cache)
|
|
├─ unit-test (matrix: Node 22.x, 24.16.0, 26.x)
|
|
│ └─ Coverage only on 24.16.0
|
|
├─ lint
|
|
└─ notify-on-failure (Slack #alerts-build)
|
|
```
|
|
|
|
---
|
|
|
|
## Scheduled Jobs
|
|
|
|
| Schedule (UTC) | Workflow | Purpose |
|
|
|---------------------------|-----------------------------------|--------------------------|
|
|
| Daily 00:00 | `docker-build-push.yml` | Nightly Docker images |
|
|
| Daily 00:00 | `test-db.yml` | Database compatibility |
|
|
| Daily 00:00 | `test-e2e-performance-reusable.yml`| Performance E2E |
|
|
| Daily 00:00 | `release-storybook.yml` | Storybook deploy |
|
|
| Daily 00:00 | `release-chromatic.yml` | Visual regression |
|
|
| Daily 00:00 | `util-check-docs-urls.yml` | Doc link validation |
|
|
| Daily 01:30, 02:30, 03:30 | `test-benchmark-nightly.yml` | Performance benchmarks |
|
|
| Daily 04:00 | `test-e2e-vm-expressions-nightly.yml`| VM expression E2E |
|
|
| Daily 05:00 | `test-benchmark-destroy-nightly.yml`| Cleanup benchmark env |
|
|
| Daily 06:00 | `util-sync-master-to-3x.yml` | Sync master → 3.x (v3) |
|
|
| Daily 08:00 | `build-v3-nightly.yml` | Nightly v3 Docker images |
|
|
| Monday 00:00 | `util-update-node-popularity.yml` | Node usage stats |
|
|
| Monday 02:00 | `test-e2e-coverage-weekly.yml` | Weekly E2E coverage |
|
|
| Saturday 22:00 | `test-evals-ai.yml` | AI workflow evals |
|
|
|
|
---
|
|
|
|
## v3 development (master + 3.x)
|
|
|
|
During the v3 release window, `master` carries normal feature work (behind opt-in
|
|
flags) and the long-lived `3.x` branch carries breaking changes. `master` is
|
|
synced into `3.x` daily by `util-sync-master-to-3x.yml` (conflicts open a draft PR
|
|
labeled `automation:v3-sync`, request the breaking-commit authors as reviewers via
|
|
`sync-conflict-owners.mjs`, post to `#alerts-v3-sync`, and pause further syncs).
|
|
`build-v3-nightly.yml` publishes `n8nio/n8n:v3-nightly[-<date>]` images from `3.x`
|
|
by calling `docker-build-push.yml` with `ref: 3.x` + `date_tag`.
|
|
|
|
See **[`DEVELOPING_V3.md`](./DEVELOPING_V3.md)** for the full model.
|
|
|
|
---
|
|
|
|
## Custom Actions
|
|
|
|
Composite actions in `.github/actions/`:
|
|
|
|
| Action | Purpose | Used By |
|
|
|--------------------------|----------------------------------------------|--------------------|
|
|
| `setup-nodejs` | pnpm + Node.js + Turbo cache + Docker (opt) | Most CI workflows |
|
|
| `docker-registry-login` | GHCR + DockerHub + DHI authentication | Docker workflows |
|
|
|
|
### setup-nodejs
|
|
|
|
```yaml
|
|
inputs:
|
|
node-version: # default: '24.16.0'
|
|
enable-docker-cache: # default: 'false' (Blacksmith Buildx)
|
|
build-command: # default: 'pnpm build'
|
|
```
|
|
|
|
### docker-registry-login
|
|
|
|
```yaml
|
|
inputs:
|
|
login-ghcr: # default: 'true'
|
|
login-dockerhub: # default: 'false'
|
|
login-dhi: # default: 'false'
|
|
```
|
|
|
|
---
|
|
|
|
## Reusable Workflows
|
|
|
|
Workflows with `workflow_call` trigger:
|
|
|
|
| Workflow | Inputs | Purpose |
|
|
|------------------------------------|-----------------------------------------------|-----------------------|
|
|
| `test-unit-reusable.yml` | `ref`, `nodeVersion`, `collectCoverage` | Unit tests |
|
|
| `test-linting-reusable.yml` | `ref`, `nodeVersion` | ESLint |
|
|
| `test-e2e-reusable.yml` | `branch`, `test-mode`, `shards`, `runner` | Core E2E executor |
|
|
| `test-workflows-callable.yml` | `git_ref`, `compare_schemas` | Workflow tests |
|
|
| `docker-build-push.yml` | `n8n_version`, `release_type`, `push_enabled`, `ref`, `date_tag` | Docker build |
|
|
| `sec-ci-reusable.yml` | `ref` | Security orchestrator |
|
|
| `sec-poutine-reusable.yml` | `ref` | Poutine scanner |
|
|
| `security-trivy-scan-callable.yml` | `image_ref` | Trivy scan |
|
|
| `sbom-generation-callable.yml` | `n8n_version`, `release_tag_ref` | SBOM generation |
|
|
|
|
---
|
|
|
|
## Scripts
|
|
|
|
Scripts in `.github/scripts/`:
|
|
|
|
### Release Scripts
|
|
|
|
| Script | Purpose | Called By |
|
|
|-------------------------------|----------------------------|-------------------------|
|
|
| `bump-versions.mjs` | Calculate next version | `release-create-pr.yml` |
|
|
| `update-changelog.mjs` | Generate CHANGELOG | `release-create-pr.yml` |
|
|
| `trim-fe-packageJson.js` | Strip frontend devDeps | `release-publish.yml` |
|
|
| `ensure-provenance-fields.mjs`| Add license/author fields | `release-publish.yml` |
|
|
|
|
### Docker Scripts
|
|
|
|
| Script | Purpose | Called By |
|
|
|-------------------------|-------------------|------------------------|
|
|
| `docker/docker-config.mjs`| Build context | `docker-build-push.yml`|
|
|
| `docker/docker-tags.mjs` | Image tags | `docker-build-push.yml`|
|
|
|
|
### Validation Scripts
|
|
|
|
| Script | Purpose | Called By |
|
|
|-------------------------|-------------------|---------------------------|
|
|
| `validate-docs-links.js`| Check doc URLs | `util-check-docs-urls.yml`|
|
|
| `send-build-stats.mjs` | Build telemetry | `setup-nodejs` action |
|
|
|
|
### Slack Scripts
|
|
|
|
See [Slack Notifications](#slack-notifications) for the calling pattern.
|
|
|
|
| Script | Purpose |
|
|
|---------------------------------|-------------------------------------------------------------------------------|
|
|
| `slack/notify.mjs` | CLI + `sendSlackMessage` export. POSTs `chat.postMessage`, fails on `ok:false`. |
|
|
| `slack/build-trivy-blocks.mjs` | `--blocks trivy` — vulnerability digest |
|
|
|
|
---
|
|
|
|
## Telemetry
|
|
|
|
CI metrics are collected via webhooks to n8n, then stored in BigQuery for analysis.
|
|
|
|
See **[CI-TELEMETRY.md](CI-TELEMETRY.md)** for:
|
|
- Common data points (git, CI context, runner info)
|
|
- Existing implementations (build stats, container stack)
|
|
- How to add new telemetry
|
|
- BigQuery schema patterns and queries
|
|
|
|
---
|
|
|
|
## CODEOWNERS
|
|
|
|
Team ownership mappings in `CODEOWNERS`:
|
|
|
|
| Path Pattern | Team |
|
|
|--------------------------------------------------------------|----------------------------|
|
|
| `packages/@n8n/db/src/migrations/` | @n8n-io/migrations-review |
|
|
|
|
---
|
|
|
|
## Runner Selection
|
|
|
|
| Runner | vCPU | Use Case |
|
|
|-------------------------------------|------|-----------------------------|
|
|
| `ubuntu-slim` | 1 | Gate jobs (required-checks) |
|
|
| `ubuntu-latest` | 2 | Simple jobs, fork PR E2E |
|
|
| `blacksmith-2vcpu-ubuntu-2204` | 2 | Standard builds, E2E shards |
|
|
| `blacksmith-4vcpu-ubuntu-2204` | 4 | Unit tests, typecheck, lint |
|
|
| `blacksmith-8vcpu-ubuntu-2204` | 8 | Heavy parallel workloads |
|
|
| `blacksmith-8vcpu-ubuntu-2204-arm` | 8 | ARM64 Docker builds |
|
|
|
|
### Selection Guidelines
|
|
|
|
**`ubuntu-slim`** - Status check aggregation, gate/required-check jobs, notifications
|
|
|
|
**`ubuntu-latest`** - Simple build verification, scheduled maintenance, PR comment handlers, release tagging, Docker manifest creation, any job where speed is not critical
|
|
|
|
**`blacksmith-2vcpu-ubuntu-2204`** - Initial build/install (benefits from Blacksmith caching), database integration tests (I/O bound), Chromatic/Storybook builds
|
|
|
|
**`blacksmith-4vcpu-ubuntu-2204`** - Unit tests (parallelized), linting (parallel file processing), typechecking (CPU-intensive), E2E test shards
|
|
|
|
**`blacksmith-8vcpu-ubuntu-2204`** - Heavy parallel workloads
|
|
|
|
### Runner Provider Toggle
|
|
|
|
The `RUNNER_PROVIDER` repository variable controls runner selection across workflows:
|
|
|
|
| Value | Behavior |
|
|
|-------|----------|
|
|
| (unset) | Use Blacksmith runners (default) |
|
|
| `github` | Use GitHub-hosted `ubuntu-latest` |
|
|
|
|
**Note:** When set to `github`, all jobs use `ubuntu-latest` regardless of any runner inputs or defaults specified in reusable workflows. GitHub runners have fewer vCPUs (2 vs 4), so jobs may run slower.
|
|
|
|
---
|
|
|
|
## Security
|
|
|
|
### Why We Do This
|
|
|
|
Supply chain security ensures artifacts haven't been tampered with. We provide three types of signed attestations:
|
|
|
|
```
|
|
ATTESTATION (signed statement)
|
|
│
|
|
┌─────────────────┼─────────────────┐
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
PROVENANCE SBOM VEX
|
|
|
|
"Trust the "Know the "Understand
|
|
build" contents" the risk"
|
|
```
|
|
|
|
| Attestation | Question It Answers |
|
|
|-------------|--------------------------------|
|
|
| **Provenance** | "Can we trust this artifact came from n8n's CI and wasn't tampered with?" |
|
|
| **SBOM** | "What dependencies are inside?" (license compliance, vulnerability scanning) |
|
|
| **VEX** | "The scanner found CVE-X - does it actually affect us or is it a false positive?" |
|
|
|
|
**How they relate:**
|
|
- **SBOM** is the ingredients list - input for both license checks AND security scanning
|
|
- **VEX** is the security triage output - "we investigated CVE-X, here's our assessment"
|
|
- **Provenance** proves the SBOM and VEX came from our CI, not an attacker
|
|
|
|
---
|
|
|
|
### Poutine (Supply Chain)
|
|
|
|
- **Runs on:** PR changes to `.github/**`
|
|
- **Detects:** Exposed secrets, insecure workflow configs
|
|
- **Output:** SARIF to GitHub Security tab
|
|
|
|
### Trivy (Container)
|
|
|
|
- **Runs on:** stable/nightly/rc Docker builds
|
|
- **Scans:** n8n image, runners image
|
|
- **Output:** GitHub Actions step summary (`$GITHUB_STEP_SUMMARY`) and run logs
|
|
|
|
### SBOM
|
|
|
|
- **Runs on:** release-publish
|
|
- **Format:** CycloneDX JSON
|
|
- **Signing:** GitHub Attestation API
|
|
- **Attached to:** GitHub Release
|
|
|
|
### SLSA L3 Provenance
|
|
|
|
SLSA (Supply-chain Levels for Software Artifacts) Level 3 provides cryptographic proof of build integrity.
|
|
|
|
| Artifact | Generator | Level |
|
|
|----------|-----------|-------|
|
|
| Docker images | `slsa-framework/slsa-github-generator` | L3 |
|
|
| npm packages | `NPM_CONFIG_PROVENANCE=true` | L3 |
|
|
|
|
**Docker provenance** uses the SLSA GitHub Generator as a reusable workflow (not an action). This is required for L3 because provenance must be generated in an isolated environment the build can't tamper with.
|
|
|
|
```yaml
|
|
# IMPORTANT: Must use semantic version tags (@vX.Y.Z), NOT commit SHAs.
|
|
# The slsa-verifier requires tagged versions to verify authenticity.
|
|
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
|
```
|
|
|
|
**Verify provenance:**
|
|
```bash
|
|
# Docker
|
|
slsa-verifier verify-image ghcr.io/n8n-io/n8n:VERSION \
|
|
--source-uri github.com/n8n-io/n8n
|
|
|
|
# npm
|
|
npm audit signatures n8n@VERSION
|
|
```
|
|
|
|
### VEX (Vulnerability Exploitability eXchange)
|
|
|
|
VEX documents which CVEs actually affect n8n vs false positives from scanners.
|
|
|
|
- **File:** `security/vex.openvex.json`
|
|
- **Format:** OpenVEX (broad scanner compatibility - Trivy, Docker Scout, etc.)
|
|
- **Attached to:** GitHub Release, Docker image attestations
|
|
- **Used by:** Trivy scans (via `security/trivy.yaml`)
|
|
|
|
**VEX Status Types:**
|
|
| Status | Meaning |
|
|
|--------|---------|
|
|
| `not_affected` | CVE doesn't impact n8n (code not reachable, etc.) |
|
|
| `affected` | CVE impacts n8n, tracking fix |
|
|
| `fixed` | CVE was present, now fixed |
|
|
| `under_investigation` | Assessing impact |
|
|
|
|
**Verify VEX attestation:**
|
|
```bash
|
|
cosign verify-attestation --type openvex \
|
|
--certificate-identity-regexp '.*github.com/n8n-io/n8n.*' \
|
|
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
|
|
ghcr.io/n8n-io/n8n:VERSION
|
|
```
|
|
|
|
**Adding a CVE statement to security/vex.openvex.json:**
|
|
```json
|
|
{
|
|
"statements": [
|
|
{
|
|
"vulnerability": { "name": "CVE-2024-XXXXX" },
|
|
"products": [{ "@id": "pkg:github/n8n-io/n8n" }],
|
|
"status": "not_affected",
|
|
"justification": "vulnerable_code_not_in_execute_path",
|
|
"statement": "n8n does not use the affected code path in this dependency"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Slack Notifications
|
|
|
|
All workflows post via `.github/scripts/slack/notify.mjs` — a direct `fetch` to `chat.postMessage` that exits non-zero on any Slack error. No third-party action; no silent swallowing.
|
|
|
|
```yaml
|
|
notify-on-failure:
|
|
runs-on: ubuntu-latest
|
|
needs: [build]
|
|
if: ${{ always() && contains(needs.*.result, 'failure') }}
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
sparse-checkout: .github/scripts/slack
|
|
sparse-checkout-cone-mode: false
|
|
- name: Notify Slack
|
|
env:
|
|
SLACK_TOKEN: ${{ secrets.QBOT_SLACK_TOKEN }}
|
|
run: |
|
|
node .github/scripts/slack/notify.mjs \
|
|
--channel '#alerts-build' \
|
|
--text 'Build failed - ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
|
|
```
|
|
|
|
If notify is a step inside an existing checked-out job, skip the `checkout` and gate with step-level `if: failure()` instead.
|
|
|
|
> `if: failure()` at the **step** level of a dedicated notify job is a no-op when a `needs:` dependency fails (the job is skipped before steps evaluate). Always gate the **job** with `if: ${{ always() && contains(needs.*.result, 'failure') }}`.
|
|
|
|
**Rich payloads (Block Kit):** add `build-<name>-blocks.mjs` whose default export returns a blocks array, then pass `--blocks <name>` plus any workflow-specific args. Builders read repo / run context from `GITHUB_*` runner env vars. Kebab-case flags become camelCase keys for the builder (`--image-ref` → `imageRef`).
|
|
|
|
| Token | Bot | Channels |
|
|
|------------------------------|----------------|-------------------------------------------------------------|
|
|
| `QBOT_SLACK_TOKEN` | QBot | Default — engineering / build / security |
|
|
| `RELEASE_HELPER_SLACK_TOKEN` | Release Helper | `#releases` (C036AELNMV0) |
|
|
|
|
Adding a new channel requires inviting the bot first; the first run otherwise fails loudly with `not_in_channel`. Private-repo workflows (`sec-publish-fix*.yml`) need `QBOT_SLACK_TOKEN` set in `n8n-io/n8n-private`; the scripts themselves are mirrored by `sec-sync-public-to-private.yml`.
|
|
|
|
---
|
|
|
|
## Secrets
|
|
|
|
### By Category
|
|
|
|
| Category | Secrets |
|
|
|---------------------|-------------------------------------------------------------|
|
|
| Package Publishing | `NPM_TOKEN`, `DOCKER_USERNAME`, `DOCKER_PASSWORD` |
|
|
| Notifications | `QBOT_SLACK_TOKEN`, `RELEASE_HELPER_SLACK_TOKEN` |
|
|
| Code Quality | `CODECOV_TOKEN`, `CHROMATIC_PROJECT_TOKEN`, `CURRENTS_RECORD_KEY` |
|
|
| Error Tracking | `SENTRY_AUTH_TOKEN`, `SENTRY_ORG`, `SENTRY_*_PROJECT` |
|
|
| Cloud/CDN | `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID` |
|
|
| GitHub Automation | `N8N_ASSISTANT_APP_ID`, `N8N_ASSISTANT_PRIVATE_KEY` |
|
|
| Benchmarking | `BENCHMARK_ARM_*`, `N8N_BENCHMARK_LICENSE_CERT` |
|
|
| AI/Evals | `ANTHROPIC_API_KEY`, `EVALS_LANGSMITH_*` |
|
|
|
|
### Scoping
|
|
|
|
- **`secrets: inherit`** - passes all secrets to reusable workflows
|
|
- **Explicit passing** - for minimal exposure
|
|
- **Environment: `benchmarking`** - Azure OIDC credentials
|
|
|
|
---
|
|
|
|
## Future Vision
|
|
|
|
### Redundancy Review
|
|
|
|
Comment trigger (`/test-workflows`) is a workaround.
|
|
|
|
Long-term: Main CI should be reliable enough to not need these.
|
|
|
|
### Workflow Testability
|
|
|
|
- Tools like `act` for local testing
|
|
- Unit tests for `.github/scripts/*.mjs`
|
|
- Validation with `actionlint`
|