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.
The forge's own users are the mesh's to settle — making the builder's login
a site admin so private repos build (hq 229) — and the tools' token had no
write:admin. A token kept from before a scope was added lacks it, so the
client now treats the forge's 403 "required scope" like a 401: the source
re-mints by name with the whole list and retries once. The fake forge in the
tests learns /repos/search, which the client has used since 2026-09-28 and
which had left 9 of the 11 token tests failing on main.
listens said 4000 while the container publishes 8080; the old assignment's port setting hid it, and
the rename lost the setting, so the proxy dialled a port nothing answered (2026-09-30).
The rename changed the network resource's name and not the container's network, so the container
looked for a network that no longer exists and the site answered 502 (2026-09-30).
9090:9000 and 9443:9443 were the predecessor's machine numbers written into the
definition. The manifest now says 9000 and 9443 and the mesh assigns the
machine ports on each node (the portainer slice of #173, which is stale).
keycloak, minio and n8n are told their names from their route bindings; mailu takes its domain, site
name, website and proxy address as settings and its front's name from its route, and the provisioner
reads the domain from the merged config; builder and route-proxy package the controller from the git
seat; the applications built outside the mesh say so per container; matrix says which of the world's
servers it means; the site module is named website, and the why prose no longer names a name (novox/hq
ADR 0155, issues 122 and 134). module check passes over all 77.
#186 moved settings.js to /data for the image's health check but left
settings.json at /config; settings.js requires ./settings.json, so node-red
crashed at start. Both now mount under /data.
The image's /healthcheck.js requires /data/settings.js, so with the mesh's
settings mounted at /config the container ran fine but reported unhealthy
forever. Mount the same file at /data/settings.js and point --settings there.
The record is prose wrapped at a hundred columns; matched line by line, the first live search for a
sentence of ADR 0025 found nothing. A line is matched together with the next, emphasis marks are
ignored, and a hit still names the line it starts on.
The manifest declared its secrets as secrets.secret.<name> and required a
"secret" provision, so the controller saw no own secrets and every accept
was refused (the influxdb defect of #180). The directories functions and
storage shared their ids with the containers of the same name, which the
host refuses as two resources with one identity. The containers are now
edge-functions and storage-api; the placed directories keep their ids and
paths.
A module keeping a checkout of a repository of decisions, designs and issues from the git seat,
current on every announced merge and on a timer, answering records_search / records_read /
records_list / records_status / records_sync at the commit it read (novox/hq ADR 0025, ADR 0153).
The repository is a setting; it names no mesh.
The received grants file names each pair secret by its HOST path. A sidecar
that mounts the placed grants directory at another path inside its
container sees mesh.json and not the secrets beside it — mosquitto's
provisioner reported ENOENT for a file that was there, and nodered's mqtt
step failed against a login that was never created. Mounted at its own
path now, as postgres does.
The tool runtime's own client, mesh serve, started by the mesh on the credential it sealed to the
machine (novox/hq ADR 0152, design 34): invokes every tool, listens from the machine only, holds no
state. Checked with module check before it was ever registered (hq issue 148).
They came from `requires: secret`, minted by the vault — right for a fresh
setup (the image's INIT_* variables read them once), wrong for an instance
that already exists: setup is skipped, the minted values match nothing,
and the provisioner holds a token the server never issued (issue 100).
InfluxDB will not take a chosen token value, so the operator token must be
accepted from the instance (`secret accept ace influxdb admin-token`); the
password can be either. As own secrets both are minted for a fresh install
exactly as before, and accepted where the data already knows them.
Found migrating ace's influxdb.
A program that checks its resolver validates — Mailu's admin does, at
start — could not use the mesh's resolver, and the one it ships instead
knows no mesh name (novox/hq issue 171). proxy-dnssec copies the AD bit
from 1.1.1.1 and 8.8.8.8, both of which validate.
admin is the one container here that reaches something by a mesh name:
the database, bound in database.env. A mesh name is answered by the
machine's resolver, which the runtime hands every container that does
not name its own (novox/hq ADR 0148); Mailu's unbound knows no mesh
name, and since the mesh stopped copying names into containers admin
could not find its database. The others keep unbound — rspamd needs a
validating resolver for DNSBL lookups and asks for no mesh name.
dnsmasq admits a query by the interface it arrives on when told interface=,
and by the address it is sent to when told listen-address=. A container's
query is sent to the machine's private address but arrives on docker0, so
interface=mesh0 dropped it silently on every machine (novox/hq issue 110).
Name the address, not the interface.