The first article in this series argued that "works on my machine" is an architecture problem, not a personal habit. Nine parts later, the answer to that problem is no longer a principle—it is a specific, reproducible system: a versioned workspace, a process the team actually follows, automated checks, disposable previews, disciplined migrations, scoped credentials, and a deliberate release path. This closing part steps back and generalizes that system into a structure that travels to the next project, rather than living only in the one it was built for.
What the series actually built, in order
Each part added one layer, and each layer depended on the one before it holding up.
| Part | Layer | What it guarantees |
|---|---|---|
| 1–2 | Local environment | Docker Compose services and a clear local-versus-production boundary |
| 3 | Dev container | The editor workspace itself is versioned, not personal |
| 4 | Workflow and branch protection | Changes flow through review, not direct pushes |
| 5 | CI pipeline | Lint, typecheck, test, and build run identically on every pull request |
| 6 | Preview environments | Reviewers see the running change, not just the diff |
| 7 | Migration discipline | Schema changes are backward-compatible and checkpointed |
| 8 | Secrets and environment protection | Deployment access is scoped, short-lived, and reviewable |
| 9 | Staging and production release path | Releases are deliberate, health-checked, and reversible |
No single layer is impressive on its own. The value is that each one closes a specific gap the previous layers left open, and together they remove the need for any one person to hold undocumented knowledge about how the product actually ships.
The architecture, end to end
Read top to bottom, this is a single answer to a single question: what has to be true for a change, from a first line of code to production traffic, to be trustworthy without anyone having to trust a specific person's memory. Every arrow in the diagram corresponds to an automated check or an explicit review gate from an earlier part in the series—nothing in the path depends on someone remembering an unwritten step.
A reference repository layout
Stripped of any product-specific detail, the resulting repository shape is compact enough to start a new project from directly:
. ├── .devcontainer/ │ └── devcontainer.json ├── .github/ │ ├── workflows/ │ │ ├── ci.yml │ │ ├── preview.yml │ │ ├── deploy-staging.yml │ │ └── deploy-production.yml │ └── pull_request_template.md ├── app/ # or src/, depending on framework ├── prisma/ │ ├── schema.prisma │ └── migrations/ ├── scripts/ │ └── migrate-with-retry.sh ├── compose.yaml ├── Dockerfile ├── fly.toml ├── fly.staging.toml ├── package.json └── README.md
Nothing in this layout is unusual by itself. What makes it a reference architecture is that every file has a job assigned to it by name in one of the previous nine parts, and none of the jobs are duplicated or left implicit. A new contributor—or a future version of the same team, eighteen months later—can reconstruct the entire delivery system by reading these files, without a separate onboarding document that can drift out of date.
What to keep, what to adapt per project
Not every decision in this series is load-bearing for every project. Some choices are structural and should transfer directly; others are specific to the stack used as the running example and should be swapped for whatever the next project actually uses.
- Keep, regardless of stack: the separation between expand and contract migrations, environment-scoped secrets, health checks that verify a real dependency, required status checks matched to local commands, and preview environments seeded with safe data.
- Adapt to the stack: the specific commands (
prisma migrate deploybecomes whatever the chosen ORM or migration tool uses), the base image in the Dockerfile, and the deployment platform's exact configuration syntax. - Adapt to team size: required review counts, whether
enforce_adminsmakes sense yet, and how much approval friction production deploys carry.
Treating the first group as fixed and the second and third as intentionally variable is what keeps this a reference architecture rather than a rigid template that fights the next project's actual constraints.
Sizing the pattern down for a smaller project
A solo project or an early prototype does not need all nine layers on day one, and forcing them in prematurely can slow down exactly the exploratory work a prototype exists for. A reasonable minimum viable version of this system is: a Dockerfile and compose.yaml for local consistency, a single CI workflow running lint and test, and a single deploy target with no staging environment yet. The dev container, preview environments, and the staging/production split are the layers most safely deferred—not because they do not matter, but because they earn their cost once more than one person touches the code, or once the cost of a bad production deploy exceeds the cost of building the safety rail.
Sizing the pattern up for a larger project
A larger team adds layers this series intentionally left out to stay generalizable: a second reviewer requirement on sensitive paths via CODEOWNERS, a matrix of supported runtime versions if the project ships a library rather than a single deployed application, canary or blue-green deploy strategies beyond the rolling strategy covered in part nine, and a dedicated on-call rotation with its own incident-response documentation. The architecture in this series is a floor, not a ceiling—every layer here is designed to have a next layer added on top of it without being rebuilt.
When not to use this whole pattern
This system assumes a team, a codebase that will be maintained past its first release, and a product where a bad deploy has a real cost. It is overkill for a one-off script, a short-lived internal tool with a single user, or a true throwaway prototype meant to answer a question and then be deleted. Applying nine layers of process to a project that will not outlive the week is not rigor—it is friction with no corresponding payoff. Match the system to the project's actual lifespan and blast radius, not to a fixed idea of what "doing it properly" looks like.
Common mistakes
- Copying every workflow file from a previous project without reconsidering whether this project's team size and risk profile actually justify each protection rule.
- Treating this architecture as a one-time setup task rather than a system that gets reviewed and adjusted as the team and product change.
- Adopting the staging/production split and skipping the migration discipline that makes it safe—the layers depend on each other, not just on being present individually.
- Letting the reference structure drift from what a new project actually needs, so it becomes cargo-culted rather than understood.
Practical checklist
- Every file in the reference layout has an owner who can explain what job it does and why.
- The stack-specific commands are swapped in deliberately, not copied verbatim from an unrelated project.
- The system's scope matches the project's actual lifespan and blast radius, not a fixed template.
- Layers deferred for a smaller project are written down as deferred, with a trigger condition for adding them back.
- The full path—dev container through production rollback—has been walked end to end at least once, not just assembled file by file.
Previous: Staging and Production on Fly.io: A Practical Release Path

