Skip to content

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 mx folder contractAn mx project root contains a thin Makefile dispatcher, a make directory with one include per verb, a scripts directory holding the actual bash, an apps directory with one directory per service that mx never edits, and a docker directory split into dot-config for env files, compose for the baseline and dev override, system for infrastructure you own, and dockerfiles for per-service build files.

myproject/
an mx project root

Makefile
thin dispatcher — make dev · up · build · logs

make/
one .mk include per verb
dev.mk · build.mk · logs.mk · …

scripts/
the actual bash
dev.sh · build.sh · doctor.sh · …

apps/

docker/

{service}/
your application source
mx scaffolds it, then never edits it

.config/
.env.shared · .env.{service}
.env.secrets (gitignored)

compose/
{service}.yml — baseline
{service}.dev.yml — dev override

system/
infrastructure you own
proxies, certs, seed data

dockerfiles/
{service}/app — dev target
{service}/app.prod — release target

The mx folder contractAn mx project root contains a thin Makefile dispatcher, a make directory with one include per verb, a scripts directory holding the actual bash, an apps directory with one directory per service that mx never edits, and a docker directory split into dot-config for env files, compose for the baseline and dev override, system for infrastructure you own, and dockerfiles for per-service build files.

myproject/
an mx project root

Makefile
thin dispatcher — make dev · up · build · logs

make/
one .mk include per verb
dev.mk · build.mk · logs.mk · …

scripts/
the actual bash
dev.sh · build.sh · doctor.sh · …

apps/

docker/

{service}/
your application source
mx scaffolds it, then never edits it

.config/
.env.shared · .env.{service}
.env.secrets (gitignored)

compose/
{service}.yml — baseline
{service}.dev.yml — dev override

system/
infrastructure you own
proxies, certs, seed data

dockerfiles/
{service}/app — dev target
{service}/app.prod — release target

Every mx project has the same shape, so every project answers to the same verbs. mx owns the contract; you own apps/{service} and docker/system.
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 service

A project with --infra cloudflare also carries infra/cloudflare/ and make/cloudflare.mk; see Cloudflare deploy.

The split is not a style preference. mx upgrade reads it directly, and it decides whether a file can be replaced under you.

PathOwnerUpgrade behaviour
Makefile, make/*.mk, scripts/*.sh, scripts/*.mjsmxTooling: upgrade offers to update these
docker/compose/, docker/dockerfiles/, docker/system/youConfig: added if missing, never overwritten
apps/<service>/youuntouched
docker/.config/youuntouched; .env.secrets is gitignored
infra/cloudflare/, make/cloudflare.mk, scripts/cf-*.shmxConditional: 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.)

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.

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/.