Designing cloud foundations the next engineer can extend
Most cloud foundations are judged on whether the first deployment works. It is the easiest question to answer and the least useful one, because almost any competent design passes it.
The question that decides whether the work was good is what happens eighteen months later, when someone who was in none of the original conversations needs to change something the design did not anticipate. They will either extend what you built or route around it. Routing around it is the real failure, and it never appears in a status report.
Everything below is written from that vantage point.
What I look for before choosing an architecture
Three things, and only one of them is technical.
Who owns this after I stop touching it, and how much context will they have. The honest answer is usually: someone under time pressure, at an inconvenient hour, with none of the reasoning that produced the design. That person is who the architecture is actually for. Designing for the version of yourself who already understands it is the most common mistake I see, and it is invisible while you are still there.
Which parts are expected to change, and which are being treated as permanent. These are rarely the parts teams assume. People carefully abstract the thing that feels expensive and casually hard-code the thing that turns out to move. Asking the question explicitly, out loud, before the diagram, surfaces the disagreement while it is still cheap.
What the team can operate, rather than what it can admire. A design the on-call rotation cannot reason about at three in the morning will be worked around, and the workarounds quietly become the real architecture. This has a corollary that people resist: the best tool is frequently not the best tool for a given team. I have chosen the toolchain a team already knew over the one I would have picked for myself, and I would make that trade again.
There is a harder version of the same principle, which is knowing when to stop extending something and rebuild it instead. The signal I trust is not complexity or age. It is whether I can fully reason about what it does. If I am maintaining a system I cannot predict, every change I make is a guess wearing a deployment pipeline. Rebuilding is expensive; guessing is more expensive, later, and to somebody else.
The corollary is that inheriting somebody else's opacity is not a head start. Exported and generated configurations are tempting because they run immediately, and they carry machinery you did not choose and cannot audit. A foundation you cannot explain line by line is not a foundation.
The tradeoffs that only appear later
The cost of an architectural decision is almost never paid at deployment. It is paid the first time someone needs to do something the design did not anticipate.
A few worth naming, with what each one actually buys.
Isolation versus sharing. Separating every environment onto its own network is the instinctive answer and often the wrong one. The isolation that matters is usually at the data and routing layer, not the network layer, and full separation multiplies the surface a later engineer has to hold in their head for a benefit nobody can articulate when asked. I have chosen shared infrastructure with hard separation at the data boundary, deliberately, more than once.
Managed where failure is expensive, disposable where it is not. Production databases earn automated backups, multi-zone failover, encryption at rest and deletion protection. Test and training environments earn the opposite: their real requirement is that somebody can destroy one without asking permission. Those are different products and paying for them identically is a failure of nerve in one direction or of seriousness in the other.
Keep the same engine across environments, including the ones that are not in the cloud. Not for elegance. Because a production export should load into test with no conversion step, and conversion steps are where data migrations fail at two in the morning.
Turn on anything that can say no in observation mode first. Web application firewalls, policy engines, admission controllers. Run the rules in count-only mode with sampling on, look at what they would have blocked, then enforce. I do this because I once enforced first and broke an application, and the lesson generalises further than firewalls.
Resolve secrets at runtime, and then write down what that costs you. Credentials should never appear in a template or a repository, which is well understood. The part that is not well understood is the operational consequence: already-running containers keep the credential they started with, because secrets are injected at task start. So if the application's own credential must ever be rotated, alternating-user rotation is the only pattern that avoids downtime. Discovering that is an afternoon. Knowing to warn the next person is the actual deliverable.
Neither side of any of these is the wrong call. Both become the wrong call when nobody wrote down what was being traded.
Ownership and handoff change the design
The difference between an installation and a platform is whether the second instance is cheaper than the first. That property has to be designed in before there is a second customer to justify it, which means arguing for it on faith. Two things make it real.
Enforce conventions with policy, not with discipline. Tagging is the clearest example. A tag standard that lives in a wiki decays immediately; the same standard enumerated centrally, with permitted values, so a typo fails the deployment rather than silently creating a new category, survives contact with a hurried engineer. Consistency should be a property of the tooling, never of anyone's diligence.
The non-obvious half of that work is not building it. It is explaining it. Shared infrastructure means per-customer cost is the customer's own environment plus their share of what is common, because the network, load balancers, storage and cluster serve everything. Query the obvious number alone and you undercount, in the direction that makes the platform look cheaper than it is. Somebody will make a budgeting decision on that number, and it will be wrong in a way nobody notices for a year. Building the tagging was a day. Making sure the finance team knew how to read it was the part that mattered.
Make every stage inspectable. When I have moved history between systems, the hard problem is never the transfer. It is identity: legacy systems record a username, modern ones require a name and an address, and every historical record made by someone who has since left has to be resolved or the history loses its attribution.
I build these as staged passes where each stage writes a file a human can open, and the stage I care most about is the one that separates partial matches from total misses. Service accounts resolve to a name with no address attached, which is neither a success nor a failure. Ambiguity with no category of its own gets silently rounded into one of the other two, and both of those answers are wrong. Giving it a name is most of the job. The final reconciliation ran several matching strategies in explicit priority order and reported by confidence tier rather than emitting a verdict, because a verdict hides the thing a human needed to look at.
I also rehearse migrations against a disposable copy of production before running them. Not because I expect failure, but because the rehearsal is what makes the risk acceptable to the people who have to approve it. Their confidence is a design requirement like any other, and treating it as a soft concern is how technically correct plans get refused.
What it comes down to
A foundation is not finished when it works. It is finished when somebody who was not there can look at it, understand why it is shaped the way it is, and change it without being afraid.
Most of the decisions above cost slightly more up front and buy exactly that. When I evaluate an architecture now, mine or anyone else's, the first question is not whether it is correct. It is whether the next person will extend it or route around it.