The folder contract
Every mx project has the same shape. That is the whole mechanism behind “one
make dev works everywhere, one router serves everything, one MCP server
understands any project”.
The tree
Section titled “The tree”project-root/├── Makefile # the CLI: make dev / up / down / logs / sh ...├── make/ # make modules (common.mk, dev.mk, ...)├── scripts/ # shell scripts the make targets call├── apps/ # application source code│ └── <service>/└── docker/ ├── .config/ # env files: .env.shared, .env.secrets, .env.<service> ├── compose/ # atomic per-service compose: <service>.yml + <service>.dev.yml ├── system/ # 1:1 host-to-container mounts └── dockerfiles/ # one directory per serviceA project with --infra cloudflare also carries infra/cloudflare/ and
make/cloudflare.mk; see Cloudflare deploy.
Who owns what
Section titled “Who owns what”The split is not a style preference. mx upgrade reads it directly, and it
decides whether a file can be replaced under you.
| Path | Owner | Upgrade behaviour |
|---|---|---|
Makefile, make/*.mk, scripts/*.sh, scripts/*.mjs | mx | Tooling: upgrade offers to update these |
docker/compose/, docker/dockerfiles/, docker/system/ | you | Config: added if missing, never overwritten |
apps/<service>/ | you | untouched |
docker/.config/ | you | untouched; .env.secrets is gitignored |
infra/cloudflare/, make/cloudflare.mk, scripts/cf-*.sh | mx | Conditional: only if infra/cloudflare/ exists |
The practical reading: your code and your compose files are yours. The verb
layer belongs to the framework, which is what lets improvements to make dev
reach projects that were scaffolded a year ago. (mx upgrade is the command that
does this, and the categories in this table are the ones it honours. See
Upgrade.)
Why each directory exists
Section titled “Why each directory exists”Makefile + make/ are the verbs. make/ is one module per verb group
(dev.mk, logs.mk, build.mk, …), auto-included by a wildcard in the
Makefile. Adding a module is how a feature like Cloudflare adds targets without
touching the root file.
scripts/ holds the implementations. Make targets stay one line; the logic
lives in shell scripts that are readable, greppable and runnable on their own.
apps/<service>/ is one directory per service, named exactly as the service.
Compose, dockerfile and env file all key off that same name, so there is never a
mapping table to consult.
docker/compose/ is one baseline file per service plus a .dev.yml override.
This is what makes the subset the unit of work: make dev s=api composes only
the files that service needs. See
Compose & env conventions.
docker/.config/ has three env layers: .env.shared for everything,
.env.secrets (gitignored, generated from .env.secrets.template by
make init) for credentials, .env.<service> for per-service settings.
docker/system/ holds 1:1 host-to-container mounts. If a service needs a
config file, a seed dataset or a persisted directory, it lives here at the same
relative path the container sees.
docker/dockerfiles/<service>/ carries app for development and app.prod
for the shipped image. Multi-stage targets (development, production) let the
same file serve both.
Nesting
Section titled “Nesting”A project root is self-contained, which means an mx project can live inside
another repository. This site is exactly that: site/ in the mech-crate
repository is a complete mx project with its own Makefile, make/, scripts/
and docker/ tree, and the Astro app under site/apps/site/.
- CLI reference: the verbs the contract enables
- Compose & env conventions: the
docker/half in detail