Overview
Two placement rules close the reference. First: containerisation definitions (Dockerfile
and the likes) live as much as possible alongside the deployment unit they belong to —
next to the application/<typology> assembly they package, never centralised. Second: a
deployment/ folder holds the infrastructure-as-code (OpenTofu or the like) — the
deployment definitions for the services, versioned with the code they deploy. In the
modulith both rules key off the assembly, which is the only runnable artifact:
modules/ never carries a Dockerfile.
Key points
- Dockerfile next to its assembly. The container definition is part of the
deployment unit, exactly like its POM: whoever changes the unit sees and owns its
packaging. A central
docker/directory decouples the two and rots. - One image per assembly, tagged and built independently — CI builds only the images whose transitive module set changed. Jib or Buildpacks may replace the raw Dockerfile; the placement rule stands regardless (the Jib config lives in that assembly's POM).
- Local orchestration at the root: a
compose.yamlat the service root wires the units plus their backing services (Postgres, Kafka) for local development — the root file may reference each unit's Dockerfile, it does not replace them. deployment/is the IaC home: OpenTofu modules describing what a deployment unit needs to run (service, scaling, queues, databases, permissions), composed into per-environment stacks. It ships in the same repo so a change to a unit and the change to its runtime footprint travel in one commit / one review.- Per-unit IaC modules mirror the application tree:
deployment/modules/api/corresponds toapplication/api/— the correspondence is by name, so the tree stays navigable in both directions. Note the collision of vocabulary:deployment/modules/holds OpenTofu modules, one per assembly, and has nothing to do with the bounded contexts under the repository'smodules/. - Extraction adds a stack, it does not rewrite one. Carving a context out produces
a new assembly, so it produces exactly one new Dockerfile, one new
deployment/modules/<assembly>/, and one new line per environment — the same checklist as any other deployment unit. - The line to hold:
deployment/describes runtime topology (what runs, where, with what resources). Application configuration (feature flags, port bindings) stays with the unit; secrets stay in the platform's secret store — referenced, never stored.
Details
Layout
acme-service/
├── modules/ # bounded contexts — no Dockerfile anywhere here
├── application/
│ ├── api/
│ │ └── Dockerfile # ← with its assembly
│ ├── consumer/
│ │ └── Dockerfile
│ └── cron/
│ └── Dockerfile
├── compose.yaml # local dev wiring of all assemblies
└── deployment/
├── AGENTS.md
├── modules/ # reusable OpenTofu modules, one per assembly
│ ├── api/ # mirrors application/api
│ ├── consumer/
│ └── shared/ # network, registry, common IAM
└── environments/
├── dev/ # stack composing the modules for dev
│ ├── main.tf
│ └── backend.tf
└── prod/
Working rules
- State per environment, remote backend, plan-on-PR / apply-on-merge; an environment
stack composes unit modules with per-env sizing — no copy-pasted resources between
dev/andprod/. - A new deployment unit is a checklist: an assembly under
application/, Dockerfile beside it, entry incompose.yaml, IaC module underdeployment/modules/, one line in each environment stack. The reference's uniformity is what makes this a checklist and not a design session — including when the new unit is an extracted context. - Monorepo boundary: if the organisation centralises IaC elsewhere,
deployment/still holds the service-owned layer (the unit modules) and the central repo composes them — the definition of what this service needs stays with the service.
Related
- Hexagonal architecture reference implementation — Java — the summary note this details.
- User-side adapters & assemblies — the deployment units these definitions package and deploy.
- Modulith & microservices — the extraction this checklist absorbs.
- Platform theme — cloud providers and delivery platforms the stacks target.