Files
hq/04-ISSUES/026-the-data-directories-are-not-declared/00-report.md
T
jschoubben e75683c01f 026 reopened — the rule is right about data, wrong about facilities
Enforcing "declare what you mount" refuses the builder, which mounts the
container runtime's socket. That socket is not the builder's data: it
exists already, the machine owns it, and declaring it as one of the
module's directories would be a lie the host would act on.

Two kinds of mount are spelled identically today — the directory my data
lives in, and a machine facility I was granted. Until a manifest can say
which, the fourteen declared mounts are right by coincidence, which is
what this issue was opened about.

Recorded rather than decided: separating them is new vocabulary, and
inventing it to turn a check green is how a mechanism nobody chose ends
up load-bearing.
2026-09-01 19:45:55 +02:00

97 lines
4.8 KiB
Markdown

---
status: located
opened: 2026-09-01
located-in: [mesh-control]
fixed-by: partly — mesh-control 53eb000, withdrawn in 83c6a2f
amended-design:
---
# 026 — The data directories are mounted and never declared
## Symptom
Four modules mount **fourteen host paths** that no resource in those modules declares:
```
gitea /services/gitea/gitea
postgres /services/postgres/db-data
minio /services/minio/data/data1-1
mailu eleven more, including the mail spool and the admin database
```
Each is a bind mount on a container. None is a `directory` resource. The mesh has never heard of
any of them.
## What that costs
**They are created by the container runtime, as root.** A bind mount whose source does not exist
is created for you, owned by root, with whatever mode the runtime picks. So `owner` and `mode` —
which exist precisely so a module can say who its data belongs to — are silently not applied to
the only directories that hold data.
**The protection that exists for exactly this does not reach them.** A directory the mesh declared
and no longer wants is *kept*, not removed, when it holds anything the mesh did not put there
([ADR 0030](../../02-DECISIONS/0030-data-outlives-the-mesh-that-declared-it.md)). That rule is the
answer to *what happens to my data when a module goes away*, and it is written in terms of
declared directories. **An undeclared one is not protected by it, because the mesh does not know
it is there.**
So the single rule guarding against data loss covers the configuration directories, which are
cheap to lose, and not the data directories, which are the reason the rule exists.
## Where it came from
These manifests were written by reading the arrangement being replaced and carrying its
`docker-compose` files across — service, image, ports, volumes, environment — into the new
manifest's container shape. That shape can express all of it, which is what made the
transliteration feel like progress.
**A container shape that can express a compose file will be filled in like a compose file.** The
mesh's model is larger than that: a directory is a thing the mesh owns, with an owner and a mode
and a rule about what happens when it is no longer wanted. A volume line borrowed from compose
declares none of it, and nothing complains, because a bind mount source is a string.
## What a fix has to keep
- **Every host path a container mounts is declared.** If a module wants a directory on the
machine, it says so, with who owns it and what mode — and gets the removal rule with it.
- **The check is mechanical.** A person comparing volumes against declared directories by hand is
the process that produced this. It is a few lines against the manifest and belongs beside the
other manifest checks.
- **Not by inventing directories at apply time.** The host creating what a mount needs would make
the mesh's ownership of a directory depend on which resource mentioned it first, and would put
the same undeclared path back a layer down.
## Not yet answered
**Where a module's data should live at all.** These paths were inherited whole from the
arrangement being replaced, which put everything under one directory per service. Whether that is
right here is a separate question, and a bigger one — it decides what a person backs up, and what
survives a module being removed.
## Half fixed
**All fourteen are declared**, across the forge, the mail system, the store and the object store —
each mount now resolves to a `directory` or to a file the module already names.
**Enforcing it was tried and withdrawn**, and the withdrawal is the interesting half. A refusal
for any mount no resource declares refuses the **builder**, which mounts the container runtime's
socket. That socket is not the builder's data. It does not belong to the module, it already exists,
and declaring it as one of the module's own directories would be a lie that the host would act on.
So the rule is right about data and wrong about everything else, because the manifest cannot
currently say which a path is. Two kinds of mount are spelled identically:
- **the directory my data lives in** — created if absent, owned by the module, protected by
[ADR 0030](../../02-DECISIONS/0030-data-outlives-the-mesh-that-declared-it.md)
- **a machine facility I was granted** — a socket, a device; it exists, the machine owns it, and
the module is being given access to it
`capabilities` is the closest existing thing to the second and names no paths. Inventing a field
to separate them is a design decision, so it is recorded here rather than made to get a check
green.
**Until then the manifests are right by coincidence**, which is the state this issue was opened
about. What is kept is a check that every real manifest still parses — worth nothing against this
fault, and the reason the next attempt finds out in a second rather than in a fifteen-minute run.