jschoubben fbd3ecd84a n8n: its own image built from source, placed data, and what its workflows use
The module named /var/lib/n8n, /services/n8n/n8n-data and n8n.novox.be -
paths and a domain no definition may carry (ADR 0112). State and data are
placed directories; the public name is ${bound:route:name} (depends on
mesh-controller #149), for N8N_HOST and WEBHOOK_URL alike.

The endpoint said 5682 while the container publishes 5678. 5682 was one
machine's host port; the endpoint is the software's port and the mesh
assigns the machine's (ADR 0038).

n8n had been run from an image in a registry that no longer exists: the
upstream image plus shadow, a `media` group (2000) with `node` in it, and
a global `uuid`. That recipe is now this module's Dockerfile, built on the
upstream 1.71.3 image named in build.on by digest, with uuid pinned to the
version the running image carries (14.0.1) - Code nodes require() it. The
media group is how the container writes into the shared media library, a
read-write `access` (ADR 0051), mounted where workflows expect it,
/media-library.

The workflows also use a redis (the Redis nodes of the chat workflows) and a
Selenium Chrome (the scraper), which the previous deployment ran beside n8n.
Both are containers on the module's own network, publishing nothing, pinned
to the digests in use; redis keeps its append-only file in a placed
directory.

The basic-auth secret is gone: N8N_BASIC_AUTH_* was removed in n8n 1.0 and
did nothing. The grant's password is a 0400 file owned by `node`, read
through DB_POSTGRESDB_PASSWORD_FILE, so nothing secret is in the
environment. The credentials' encryption key is n8n's own, in the data
directory (config), and moves with it - nothing to mint or accept.

Verified: catalogue tests with MESH_CATALOGUE set; the Dockerfile built
against the pinned base gives n8n 1.71.3, uid 1000 in group 2000, uuid
14.0.1 - the running image's shape. Throwaway containers: an instance on
PostgreSQL 15 with an owner, a workflow and an encrypted credential;
stopped, copied, dumped from the copy, restored (--no-owner --role, the
uuid-ossp extension pre-made by the superuser) into a grant-shaped
database on the postgres module's pgvector image (PG17); the new shape
(password from the file, data dir copied) serves /healthz, the owner logs
in, the workflow is listed, and the credential decrypts with the carried
key. The node user writes into a root:2000 0775 library through the media
group; redis and Selenium resolve by name on the module network and
Selenium reports ready. Test containers and data removed.
2026-09-30 12:25:44 +02:00

mesh-catalog

The Novox Mesh catalogue. The modules the mesh builds, provisions and runs — as manifests, one per module under modules/.

This is data, not a control-plane concern. The manifests describe what a module is: what it provides, what it requires, the seats it claims, the resources the host applies for it. The engine that reads them — parsing, eligibility resolution, sealing, declaration emission — lives in the control plane (novox/mesh-controller, internal/catalogue), which consumes this repository as a build source. The host (novox/mesh-host) applies the declarations the control plane emits. Neither is here.

What a module is, and is not

A module is one thing the mesh can run, named once, described completely by its manifest. A manifest names its image (pinned by digest), the resources the host owns for it (directories, files, the container, the private network it joins), what it requires from a provider and what it provides to consumers, and the sealed secrets it needs filled on the machine.

  • Core mesh components are not modules. The node host, the foundation, the control-plane contexts and the surfaces are the mesh itself; they ship as their own repositories (mesh-host, mesh-foundation, mesh-controller, mesh-surfaces, mesh-sdk), not from here.
  • Standalone applications are not here either. A larger application lives in its own repository with its manifest at the root, registered with the mesh as a build source (novox/hq ADR 0010). This repository holds the modules the mesh maintains as its shared catalogue; an application the mesh merely hosts keeps its manifest beside its own code.

So there is one home for the catalogue the mesh owns, and every application that runs on the mesh rather than being of it carries its own — both reach the pipeline the same way, as a registered source.

Layout

modules/<name>.json      one manifest per module

Flat, because the catalogue's shape carries no meaning: a module is found by its name and described by its manifest, and what relates two modules — a shared seat, a claim, a provider/consumer edge — is data inside the manifests, not a directory the tree encodes (novox/hq, the domain-grouping question closed in favour of seats, claims and tags).

The manifest contract

The shape a manifest must satisfy is owned by the control plane's catalogue engine and is what validates a manifest before a machine ever sees it — a stray key, a consumer contributing the wrong provision field, an image that nothing builds. That validation belongs with this repository and is being re-homed here from mesh-controller; until it is, the pipeline is the gate — it builds each module and refuses a manifest it cannot resolve.

Where the reasoning lives

Design and decisions are in novox/hq:

  • 02-DECISIONS/0002-everything-is-a-module.md — one unit, no second mechanism
  • 02-DECISIONS/0010-applications-live-in-their-own-repository.md — why applications are not here
  • 02-DECISIONS/0030-the-repository-structure.md — the repositories, and the open tier-4 question this repository answers
  • 03-DESIGN/00-as-is/10-module-catalogue.md — the catalogue's shape, and what it records
S
Description
Novox Mesh — the catalogue. Module manifests the mesh builds, provisions and runs. Data, not a control-plane concern.
Readme
2 MiB
Languages
TypeScript 90.3%
Dockerfile 8.8%
Shell 0.9%