rgoussu@goussu: ~/library/java/hexagonal-reference-implementation
~/library/java/hexagonal-reference-implementation cat deployment-and-iac.md

Deployment & IaC — containers beside their unit, stacks in one folder

# Containerisation definitions live alongside the assembly they package; a deployment folder holds the infrastructure-as-code (OpenTofu or the like) defining the services, mirroring the application tree.

Conceptsaved 2026-08-09updated 2026-08-14 #hexagonal-architecture#architecture#deployment#iac#opentofu#containers#modulith

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.yaml at 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 to application/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's modules/.
  • 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/ and prod/.
  • A new deployment unit is a checklist: an assembly under application/, Dockerfile beside it, entry in compose.yaml, IaC module under deployment/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