<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>BuiltForProd blog</title><description>Practical writing on production engineering: security, reliability, recovery, delivery, compliance and the operational work that keeps systems running.</description><link>https://blog.builtforprod.com/</link><language>en-us</language><copyright>© 2026 Nexstella</copyright><managingEditor>hello@builtforprod.com (BuiltForProd)</managingEditor><item><title>The true cost of building your own landing zone</title><link>https://blog.builtforprod.com/posts/true-cost-of-building-your-own-landing-zone/</link><guid isPermaLink="true">https://blog.builtforprod.com/posts/true-cost-of-building-your-own-landing-zone/</guid><description>A cloud foundation built in-house costs far more than the estimate says. Here is the work nobody scopes, and when building it yourself is still right.</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;strong&gt;true cost of building your own landing zone&lt;/strong&gt; is not the quarter you put in the plan. It is the sum of eleven work streams, only three of which anyone estimates, plus an owner for as long as the company exists. Most teams can build a cloud foundation. The question worth arguing about is what else does not get built while they do.&lt;/p&gt;
&lt;p&gt;This post is the honest version of that arithmetic: the work items, the engineer-months, the recurring maintenance, the opportunity cost, and the cases where building it yourself is clearly the right call. No prices, because the figure that decides this is your engineering capacity, not anyone’s invoice.&lt;/p&gt;
&lt;h2 id=&quot;why-the-estimate-is-always-wrong&quot;&gt;Why the estimate is always wrong&lt;/h2&gt;
&lt;p&gt;Landing zone estimates are made from the parts you can picture. Someone sketches the account structure on a whiteboard, someone else draws the VPCs, and the two of them agree it is about a quarter of work. They are right about those two things.&lt;/p&gt;
&lt;p&gt;The estimate is wrong because a landing zone is not an architecture. It is &lt;strong&gt;a set of properties that have to hold across every account, forever&lt;/strong&gt;, and properties are finished only when nobody can accidentally break them. That is a different kind of work from drawing boxes, and it is the kind that does not compress.&lt;/p&gt;
&lt;p&gt;Three specific errors recur:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Confusing “deployed” with “done.”&lt;/strong&gt; Terraform applies cleanly, therefore the network is finished. It is not finished until a second engineer can add a spoke without asking the author what the address plan means.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pricing the happy path.&lt;/strong&gt; The estimate covers creating accounts. It does not cover deleting one, moving one between organizational units, or recovering the one where somebody deleted the role the pipeline assumes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Treating documentation as a phase.&lt;/strong&gt; Documentation is not a phase; it is the difference between an asset and a liability with one maintainer.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-actually-has-to-be-built&quot;&gt;What actually has to be built&lt;/h2&gt;
&lt;p&gt;Here is the full list, with what “done” honestly means for each. The engineer-month ranges are planning estimates for a competent team building from a blank repository — they are not measurements, and your numbers will differ. They are here to make the shape of the work visible, not to be quoted.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Work stream&lt;/th&gt;
&lt;th&gt;Usually scoped&lt;/th&gt;
&lt;th&gt;What “done” means&lt;/th&gt;
&lt;th&gt;Engineer-months (estimate)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Account structure and organizational units&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;The layout survives the first team you did not plan for&lt;/td&gt;
&lt;td&gt;0.5–1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Account factory&lt;/td&gt;
&lt;td&gt;Rarely&lt;/td&gt;
&lt;td&gt;Adding account fifteen is a pull request, not a project&lt;/td&gt;
&lt;td&gt;1–2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network topology and address plan&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Routing, isolation domains and egress, with addresses assigned by a system rather than a spreadsheet&lt;/td&gt;
&lt;td&gt;1.5–3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Identity model and permission sets&lt;/td&gt;
&lt;td&gt;Partly&lt;/td&gt;
&lt;td&gt;Every human role defined, production restricted, joiners and leavers handled in one place&lt;/td&gt;
&lt;td&gt;1.5–3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preventive guardrails&lt;/td&gt;
&lt;td&gt;Rarely&lt;/td&gt;
&lt;td&gt;Policies engineers cannot route around and do not want to&lt;/td&gt;
&lt;td&gt;1–2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Centralized logging and the audit archive&lt;/td&gt;
&lt;td&gt;Partly&lt;/td&gt;
&lt;td&gt;Logs land in an account the logged systems cannot reach, and nobody can turn them off&lt;/td&gt;
&lt;td&gt;1–2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security services and their findings pipeline&lt;/td&gt;
&lt;td&gt;Rarely&lt;/td&gt;
&lt;td&gt;Findings reach a human who is expected to act, with severity that means something&lt;/td&gt;
&lt;td&gt;1–2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI trust and deploy identity&lt;/td&gt;
&lt;td&gt;Rarely&lt;/td&gt;
&lt;td&gt;The pipeline holds no cloud keys, and its role is scoped to a repository and a branch&lt;/td&gt;
&lt;td&gt;0.5–1.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compliance control mapping and evidence&lt;/td&gt;
&lt;td&gt;Rarely&lt;/td&gt;
&lt;td&gt;An auditor’s question is a query, not a screenshot hunt&lt;/td&gt;
&lt;td&gt;1–2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documentation, runbooks and onboarding&lt;/td&gt;
&lt;td&gt;Rarely&lt;/td&gt;
&lt;td&gt;A new engineer ships an infrastructure change in week one&lt;/td&gt;
&lt;td&gt;1–2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day-2 procedures: drift, upgrades, incidents&lt;/td&gt;
&lt;td&gt;Almost never&lt;/td&gt;
&lt;td&gt;Drift is detected and triaged; provider and module upgrades have a reviewed path&lt;/td&gt;
&lt;td&gt;1–2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Add the ranges and the honest answer is somewhere between eleven and twenty-two engineer-months for a foundation you would defend in a security review. Teams that finish nearer the bottom of that range have usually done it before. Teams that finish above it have usually discovered identity and compliance halfway through.&lt;/p&gt;
&lt;figure class=&quot;bfp-figure&quot;&gt;
  &lt;span class=&quot;bfp-eyebrow&quot;&gt;Figure 1&lt;/span&gt;
  &lt;div class=&quot;bfp-figure__frame&quot;&gt;
    &lt;svg viewBox=&quot;0 0 560 260&quot; role=&quot;img&quot; aria-labelledby=&quot;cost-title cost-desc&quot;&gt;&lt;title id=&quot;cost-title&quot;&gt;Scoped and unscoped work in a landing zone build&lt;/title&gt;&lt;desc id=&quot;cost-desc&quot;&gt;&lt;p&gt;Three work streams are commonly estimated: account structure, network topology, and workload accounts. Eight are
commonly omitted: account factory, identity and permission sets, guardrails, centralized logging, security
findings, continuous integration trust, compliance evidence, and documentation with day-2 procedures.&lt;/p&gt;&lt;/desc&gt;&lt;rect class=&quot;d-plane&quot; x=&quot;16&quot; y=&quot;14&quot; width=&quot;528&quot; height=&quot;82&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-title&quot; x=&quot;24&quot; y=&quot;32&quot;&gt;usually in the estimate&lt;/text&gt;&lt;rect class=&quot;d-node d-node--active&quot; x=&quot;24&quot; y=&quot;44&quot; width=&quot;160&quot; height=&quot;40&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;104&quot; y=&quot;62&quot;&gt;account structure&lt;/text&gt;&lt;text class=&quot;d-note d-note--mid&quot; x=&quot;104&quot; y=&quot;77&quot;&gt;0.5–1 engineer-months&lt;/text&gt;&lt;rect class=&quot;d-node d-node--active&quot; x=&quot;200&quot; y=&quot;44&quot; width=&quot;160&quot; height=&quot;40&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;280&quot; y=&quot;62&quot;&gt;network topology&lt;/text&gt;&lt;text class=&quot;d-note d-note--mid&quot; x=&quot;280&quot; y=&quot;77&quot;&gt;1.5–3 engineer-months&lt;/text&gt;&lt;rect class=&quot;d-node d-node--active&quot; x=&quot;376&quot; y=&quot;44&quot; width=&quot;160&quot; height=&quot;40&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;456&quot; y=&quot;62&quot;&gt;workload accounts&lt;/text&gt;&lt;text class=&quot;d-note d-note--mid&quot; x=&quot;456&quot; y=&quot;77&quot;&gt;0.5–1 engineer-months&lt;/text&gt;&lt;rect class=&quot;d-plane&quot; x=&quot;16&quot; y=&quot;108&quot; width=&quot;528&quot; height=&quot;140&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-title&quot; x=&quot;24&quot; y=&quot;126&quot;&gt;where the schedule actually goes&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;24&quot; y=&quot;138&quot; width=&quot;124&quot; height=&quot;32&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;86&quot; y=&quot;158&quot;&gt;account factory&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;158&quot; y=&quot;138&quot; width=&quot;124&quot; height=&quot;32&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;220&quot; y=&quot;158&quot;&gt;identity model&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;292&quot; y=&quot;138&quot; width=&quot;124&quot; height=&quot;32&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;354&quot; y=&quot;158&quot;&gt;guardrails&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;426&quot; y=&quot;138&quot; width=&quot;110&quot; height=&quot;32&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;481&quot; y=&quot;158&quot;&gt;log archive&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;24&quot; y=&quot;180&quot; width=&quot;124&quot; height=&quot;32&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;86&quot; y=&quot;200&quot;&gt;security findings&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;158&quot; y=&quot;180&quot; width=&quot;124&quot; height=&quot;32&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;220&quot; y=&quot;200&quot;&gt;CI trust&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;292&quot; y=&quot;180&quot; width=&quot;124&quot; height=&quot;32&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;354&quot; y=&quot;200&quot;&gt;audit evidence&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;426&quot; y=&quot;180&quot; width=&quot;110&quot; height=&quot;32&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;481&quot; y=&quot;200&quot;&gt;documentation&lt;/text&gt;&lt;text class=&quot;d-note&quot; x=&quot;24&quot; y=&quot;234&quot;&gt;every one of these is a standing obligation, not a one-time task&lt;/text&gt;&lt;/svg&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;The three work streams almost every estimate includes, and the eight that decide whether the foundation is defensible. The second group is where schedules are lost, because each item is finished only when someone other than its author can operate it.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;the-line-item-everybody-forgets-it-never-finishes&quot;&gt;The line item everybody forgets: it never finishes&lt;/h2&gt;
&lt;p&gt;A landing zone is not delivered; it is adopted. From the day the first workload lands on it, someone owns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Provider and module upgrades.&lt;/strong&gt; A cloud provider ships breaking changes on its own schedule. Somebody reads the changelog, plans the upgrade, and explains to a reviewer why the diff is safe.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drift triage.&lt;/strong&gt; Something in production does not match the code. Reverting it blindly is how a discrepancy becomes an outage — a point we argue at length in &lt;a href=&quot;/posts/drift-is-a-fact-not-a-failure/&quot;&gt;drift is a fact, not a failure&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Growth.&lt;/strong&gt; New team, new account. New market, new region. If the address plan was a spreadsheet, the second region is a redesign.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evidence.&lt;/strong&gt; Every enterprise deal now arrives with a security questionnaire. Answering it from the foundation’s own records is cheap; answering it from memory is a week.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The bus factor.&lt;/strong&gt; The foundation’s author leaves eventually. What they leave behind is either a documented system or a folklore system.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Call that one to two engineer-months a year in steady state, more in a year with a major version bump or a new region. It is not a large number. It is a &lt;em&gt;permanent&lt;/em&gt; number, and permanent numbers belong in the business case.&lt;/p&gt;
&lt;aside class=&quot;bfp-callout bfp-callout--warning&quot;&gt;
  &lt;p class=&quot;bfp-callout__title&quot;&gt;The estimate that ages worst&lt;/p&gt;
  &lt;p&gt;“We will document it after launch” is the single most expensive sentence in a foundation project. Documentation
written after the fact describes what someone remembers, not what the system does, and it is never reviewed against
the code. The foundation then has exactly one operator, which is the failure mode the foundation existed to prevent.&lt;/p&gt;
&lt;/aside&gt;
&lt;h2 id=&quot;opportunity-cost-is-the-number-that-decides-it&quot;&gt;Opportunity cost is the number that decides it&lt;/h2&gt;
&lt;p&gt;Engineering capacity is the scarcest thing a scale-up has, and a landing zone consumes it at exactly the wrong moment: after the first enterprise prospect asks for a security review, before the product has won the segment.&lt;/p&gt;
&lt;p&gt;Two senior engineers on a foundation for two quarters is roughly a year of senior product engineering that did not happen. Nothing about that year appears in a roadmap, which is why it never gets argued about properly. We made the general version of this argument in &lt;a href=&quot;/posts/production-readiness-is-a-velocity-multiplier/&quot;&gt;production readiness is a velocity multiplier&lt;/a&gt;: the tax is real whether or not you choose to see it, and the only choice is whether you pay it as a project or as recurring rework.&lt;/p&gt;
&lt;p&gt;There is also a sequencing cost. A foundation built while under deadline pressure gets its expensive properties — identity, guardrails, evidence — deferred, and deferred properties are the ones that are hardest to retrofit. Adding an account boundary later means new identities, new network paths, new logging destinations and a migration window.&lt;/p&gt;
&lt;h2 id=&quot;build-buy-or-hire-the-comparison-that-matters&quot;&gt;Build, buy or hire: the comparison that matters&lt;/h2&gt;
&lt;p&gt;A worked example, not a customer story: a 300-person B2B SaaS company running in two AWS accounts, with a first enterprise contract contingent on a security review in four months and no platform team. Here is how the three options actually compare for them.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Build in-house&lt;/th&gt;
&lt;th&gt;Buy a foundation&lt;/th&gt;
&lt;th&gt;Hire a platform team&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time to first production workload on it&lt;/td&gt;
&lt;td&gt;Two to four quarters&lt;/td&gt;
&lt;td&gt;Weeks of engagement time, engagement-specific&lt;/td&gt;
&lt;td&gt;One to two quarters before any code exists, then the build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who owns the code afterward&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;td&gt;You, if the delivery model hands over the repositories&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Engineering capacity consumed&lt;/td&gt;
&lt;td&gt;11–22 engineer-months, from your product team&lt;/td&gt;
&lt;td&gt;Review and decision time, plus the handover&lt;/td&gt;
&lt;td&gt;Recruiting, onboarding, then the same build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Risk concentration&lt;/td&gt;
&lt;td&gt;High: usually one or two authors&lt;/td&gt;
&lt;td&gt;Lower: the handover is the test of whether your team can run it&lt;/td&gt;
&lt;td&gt;High at first: a single hire from a blank repository&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fit to strange requirements&lt;/td&gt;
&lt;td&gt;Perfect, by construction&lt;/td&gt;
&lt;td&gt;Good if the foundation is customized and source-available; poor if it is a black box&lt;/td&gt;
&lt;td&gt;Perfect, eventually&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ongoing cost&lt;/td&gt;
&lt;td&gt;Yours, permanently&lt;/td&gt;
&lt;td&gt;Yours, permanently — optionally shared with a service&lt;/td&gt;
&lt;td&gt;Yours, permanently, plus salary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What it is bad at&lt;/td&gt;
&lt;td&gt;Deadlines, and documenting itself&lt;/td&gt;
&lt;td&gt;Being the right shape when your constraints are genuinely unusual&lt;/td&gt;
&lt;td&gt;Hitting a date that is one quarter away&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fails when&lt;/td&gt;
&lt;td&gt;The author leaves before the documentation exists&lt;/td&gt;
&lt;td&gt;Nobody on your team is made to operate it before handover&lt;/td&gt;
&lt;td&gt;The role is open for five months&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The columns are not mutually exclusive, and the strongest pattern for that worked example is usually the third combined with the second: a foundation in place, and the platform hire starting on top of it rather than under it. Their first quarter then goes to developer experience and product-facing platform work, which is what you actually hired them for.&lt;/p&gt;
&lt;h2 id=&quot;when-building-it-yourself-is-the-right-answer&quot;&gt;When building it yourself is the right answer&lt;/h2&gt;
&lt;p&gt;This is a genuine list, not a rhetorical one. Build it yourself when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The foundation is the product’s advantage.&lt;/strong&gt; If you sell infrastructure, run at a scale where standard topologies stop applying, or your margin depends on a bespoke network design, nobody else’s layout will fit and you should not want it to.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You already have the team and the library.&lt;/strong&gt; An existing platform team with a maintained internal module library, a working pipeline and a documented review culture is most of the way there. The marginal cost of a landing zone for them is far below the ranges above.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Your constraints are unusual.&lt;/strong&gt; Data residency in an uncommon combination of regions, an air-gapped environment, a regulator who dictates topology, a contract that dictates key custody. A standard layout is a starting point that you will spend longer arguing with than replacing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You are genuinely still early.&lt;/strong&gt; A single account with good hygiene is the correct architecture for a five-person company. The mistake is not starting there; it is staying there past the date you knew you should have moved.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You want the learning, and you have the time to buy it.&lt;/strong&gt; Building it teaches the team things reading never will. That is a legitimate reason, as long as it is a decision rather than an accident.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And the honest inverse: if your foundation looks like almost every other company’s foundation, building it from scratch is paying full price for a commodity. &lt;strong&gt;Your competitive advantage is your application, not your VPC design.&lt;/strong&gt;&lt;/p&gt;
&lt;aside class=&quot;takeaways bfp-panel bfp-panel--raised&quot; aria-labelledby=&quot;key-takeaways&quot;&gt;
      &lt;h2 class=&quot;bfp-eyebrow&quot; id=&quot;key-takeaways&quot;&gt;
        Questions that settle the decision in one meeting
      &lt;/h2&gt;
      &lt;ul class=&quot;bfp-checklist&quot; role=&quot;list&quot;&gt;
        &lt;li&gt;What date does an external party — a customer, an auditor, an investor — need this to be finished?&lt;/li&gt;&lt;li&gt;Which named engineers would build it, and what falls off the roadmap while they do?&lt;/li&gt;&lt;li&gt;Who operates it in eighteen months, and what happens the week they resign?&lt;/li&gt;&lt;li&gt;What is our answer when a prospect asks for evidence that production access is restricted and logged?&lt;/li&gt;&lt;li&gt;If we buy, do we own the repositories and the accounts afterward, and does anyone keep standing access?&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/aside&gt;
&lt;h2 id=&quot;what-buying-should-mean-and-what-it-should-not&quot;&gt;What “buying” should mean, and what it should not&lt;/h2&gt;
&lt;p&gt;The failure mode of buying is renting. If the foundation is a managed black box, you have swapped an engineering problem for a dependency, and you will discover the terms of that trade during an incident.&lt;/p&gt;
&lt;p&gt;The version worth considering has three properties, and they are the ones to test any vendor against:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The accounts and the repositories are yours.&lt;/strong&gt; The code lives in your version control, in a language your team can read, and you can change it without asking.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nobody keeps standing access.&lt;/strong&gt; After delivery, no external identity, credential or role should remain. Ask the question directly and ask what removes it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The handover is a test, not a presentation.&lt;/strong&gt; Your engineers should be required to perform real tasks unaided — make a change, review it, apply it, promote it, respond to drift — before anyone calls it delivered.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That is the shape of the &lt;a href=&quot;https://builtforprod.com/baseline/&quot;&gt;BuiltForProd Baseline&lt;/a&gt;: a multi-account foundation deployed into your own accounts, customized, with the repositories and documentation handed over. The AWS Enterprise Baseline edition deploys 14 accounts across 2 organizational units and closes with an 84-item checklist, 17 verification checks mapped to SOC 2 controls run twice, and a hands-on handover in which your team performs 11 tasks unaided. The code is source-available under the PolyForm Internal Use License, so your team can read, modify, fork and extend it inside your organization.&lt;/p&gt;
&lt;h2 id=&quot;the-next-step-whichever-way-you-go&quot;&gt;The next step, whichever way you go&lt;/h2&gt;
&lt;p&gt;If you are about to commission a build, do this first: take the eleven work streams in the table above into a room with the two engineers who would do the work, and have them write “done means…” for each row in their own words. The rows they argue about longest are the rows that will overrun.&lt;/p&gt;
&lt;p&gt;If you would rather get an outside read on what you already have before committing a quarter to it, that is what the &lt;a href=&quot;https://builtforprod.com/assessment/&quot;&gt;BuiltForProd Assessment&lt;/a&gt; is for — a measurement against the &lt;a href=&quot;https://builtforprod.com/standard/&quot;&gt;BuiltForProd Standard&lt;/a&gt;, which is the same eight properties we describe in &lt;a href=&quot;/posts/what-production-ready-actually-means/&quot;&gt;what production-ready actually means&lt;/a&gt;. Pricing is quoted per engagement and starts from the &lt;a href=&quot;https://builtforprod.com/pricing/&quot;&gt;pricing page&lt;/a&gt;. And if we are not the right fit for you, &lt;a href=&quot;https://builtforprod.com/why-builtforprod/&quot;&gt;we will say so&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>Architecture</category><category>engineering leadership</category><category>infrastructure as code</category><category>governance</category><category>operations</category><category>change management</category><author>BuiltForProd</author></item><item><title>SOC 2: what your infrastructure has to produce</title><link>https://blog.builtforprod.com/posts/soc-2-cloud-infrastructure-evidence/</link><guid isPermaLink="true">https://blog.builtforprod.com/posts/soc-2-cloud-infrastructure-evidence/</guid><description>An audit is an evidence problem. Here are the evidence sources cloud infrastructure should generate on its own, and why readiness is not certification.</description><pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A &lt;strong&gt;SOC 2 audit&lt;/strong&gt; tests whether the controls you say you have actually operated over a period of time. It is, before it is anything else, an evidence problem. The auditor is not grading your architecture. They are asking a narrow question — &lt;em&gt;show me that this control was working on these dates&lt;/em&gt; — several hundred times, and writing down whether you could.&lt;/p&gt;
&lt;p&gt;That framing changes what infrastructure is for in an audit. The useful question is not “is our cloud secure enough to pass”. It is &lt;strong&gt;“what does our cloud produce, by itself, that answers an auditor’s question without anyone taking a screenshot?”&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;readiness-is-not-certification&quot;&gt;Readiness is not certification&lt;/h2&gt;
&lt;p&gt;This distinction is worth being pedantic about, because the market blurs it constantly.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Readiness&lt;/strong&gt; means the controls exist, operate continuously, and generate records you can produce on demand. It is engineering work. It can be delivered, inherited or bought.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Certification&lt;/strong&gt; — strictly, an attestation report — is a licensed auditor forming an opinion after testing those controls. It covers your organization, not your infrastructure: policies, onboarding, vendor management, background checks, incident response and the humans who run all of it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No product makes you certified. A product can make the evidence trivial to produce, which removes most of the cost and nearly all of the panic. That is the honest claim, and it is the only one &lt;a href=&quot;https://builtforprod.com/compliance/&quot;&gt;we make&lt;/a&gt;.&lt;/p&gt;
&lt;aside class=&quot;bfp-callout bfp-callout--signal&quot;&gt;
  &lt;p class=&quot;bfp-callout__title&quot;&gt;The line we hold&lt;/p&gt;
  &lt;p&gt;“Certified” is your auditor’s word. “Ready” is your infrastructure’s job. Anyone selling you the first one is selling
you the second one with a better adjective.&lt;/p&gt;
&lt;/aside&gt;
&lt;h2 id=&quot;the-two-week-screenshot-scramble-and-why-it-happens&quot;&gt;The two-week screenshot scramble, and why it happens&lt;/h2&gt;
&lt;p&gt;The familiar failure looks like this. Four weeks before fieldwork, someone opens a spreadsheet of control requests. Engineers start logging into consoles, screenshotting settings, exporting user lists, and pasting them into a shared drive. A week is lost. The screenshots prove the setting was true on the day it was taken, which is the one thing a Type II audit is not asking.&lt;/p&gt;
&lt;p&gt;The cause is structural, not a discipline problem. If evidence is something you &lt;em&gt;collect&lt;/em&gt;, collecting it will always be a project. If evidence is something the system &lt;em&gt;emits&lt;/em&gt;, the audit becomes a query. The difference is whether the recording was switched on before the period started.&lt;/p&gt;
&lt;h2 id=&quot;the-evidence-sources-worth-building-first&quot;&gt;The evidence sources worth building first&lt;/h2&gt;
&lt;p&gt;Six sources answer most of what an auditor asks. Each is a capability, not a product, and every major cloud has an equivalent.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evidence source&lt;/th&gt;
&lt;th&gt;The questions it answers&lt;/th&gt;
&lt;th&gt;Why an auditor accepts it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Immutable API audit log&lt;/td&gt;
&lt;td&gt;Who did what, where, when, from which identity&lt;/td&gt;
&lt;td&gt;Complete, timestamped, and outside the control of the person being tested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration recorder and history&lt;/td&gt;
&lt;td&gt;Was this resource configured correctly &lt;em&gt;throughout&lt;/em&gt; the period&lt;/td&gt;
&lt;td&gt;Shows state over time, not on the day of the screenshot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Continuous control checks&lt;/td&gt;
&lt;td&gt;Which controls are passing right now, and which drifted&lt;/td&gt;
&lt;td&gt;Turns a control into a measurement with a history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Identity and access records&lt;/td&gt;
&lt;td&gt;Who had access, when it was granted, when it was revoked&lt;/td&gt;
&lt;td&gt;Directly answers the largest control family in the framework&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Threat detection findings&lt;/td&gt;
&lt;td&gt;Were anomalies detected, triaged and resolved&lt;/td&gt;
&lt;td&gt;Evidence of monitoring &lt;em&gt;and&lt;/em&gt; of response, which is usually the weaker half&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Version control and review history&lt;/td&gt;
&lt;td&gt;What changed in the environment, who approved it, when it shipped&lt;/td&gt;
&lt;td&gt;Change management evidence, produced as a by-product of doing the work&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The last row is the one teams underestimate. If every infrastructure change is a reviewed pull request applied by a pipeline, your change management evidence already exists, in order, with approvals attached, for the entire period. No separate change log to maintain and no reconciliation between what the log says and what actually happened.&lt;/p&gt;
&lt;h2 id=&quot;make-the-log-archive-untouchable&quot;&gt;Make the log archive untouchable&lt;/h2&gt;
&lt;p&gt;One control deserves special attention because auditors probe it and incidents depend on it: the integrity of the audit log itself.&lt;/p&gt;
&lt;p&gt;Logs that can be deleted by the same identity that operates the system are weak evidence, and worse, they are weak forensics. Three properties make the difference:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The archive lives in a separate account&lt;/strong&gt; from the systems it records, with access limited to a small number of principals.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stopping or deleting the logging configuration is denied at the organization level&lt;/strong&gt;, above every account, so it is not a matter of nobody choosing to do it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integrity validation is enabled&lt;/strong&gt;, so tampering is detectable rather than merely discouraged.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the &lt;strong&gt;AWS Enterprise Baseline&lt;/strong&gt;, that is how the audit account is set up: an organization-wide multi-region CloudTrail with log file validation and customer-managed encryption keys, delivered into a dedicated log archive account, with a service control policy that denies stopping or deleting CloudTrail and Config anywhere in the organization. Those specifics describe the AWS edition; the property is part of the &lt;a href=&quot;https://builtforprod.com/standard/&quot;&gt;BuiltForProd Standard&lt;/a&gt; in every edition.&lt;/p&gt;
&lt;h2 id=&quot;give-auditors-access--to-the-configuration-not-the-data&quot;&gt;Give auditors access — to the configuration, not the data&lt;/h2&gt;
&lt;p&gt;Auditors need to verify that controls are in place. That is a configuration question. It is not a reason for them to be able to read your customers’ records.&lt;/p&gt;
&lt;p&gt;The pattern that works is a dedicated read-only role with an &lt;strong&gt;explicit denial on data-plane reads&lt;/strong&gt;: they can see that a storage bucket is encrypted, that a database requires TLS, that access reviews happened. They cannot open the objects or query the rows. This is easier to grant, easier to defend to your own customers, and it makes the access review of your auditors uneventful.&lt;/p&gt;
&lt;h2 id=&quot;what-ready-looks-like-in-practice&quot;&gt;What “ready” looks like in practice&lt;/h2&gt;
&lt;p&gt;Some numbers from the AWS edition, offered as a worked example rather than a target. The AWS Enterprise Baseline ships a SOC 2 baseline conformance pack enabled by default — thirteen automated configuration rules mapped to specific Trust Services criteria — with a further twenty-eight conformance pack templates behind a single flag each, covering frameworks including HIPAA, PCI DSS 4.0, NIST CSF, CMMC 2.0 and CIS Controls. Eleven frameworks have documented control mappings. The deployment itself finishes with seventeen verification checks mapped to SOC 2 controls, run twice, and the handover includes a quarterly evidence-snapshot procedure and a monthly compliance checklist.&lt;/p&gt;
&lt;p&gt;Two honest caveats. Most of those packs ship &lt;strong&gt;off&lt;/strong&gt;, because they cost money to run and the right set depends on which frameworks you are actually in scope for; each switch states its price next to it so the decision is informed rather than surprising. And a mapped control is not a passing control — the mapping tells you where to look, and the finding tells you whether you are there.&lt;/p&gt;
&lt;h2 id=&quot;a-sequence-that-works&quot;&gt;A sequence that works&lt;/h2&gt;
&lt;p&gt;If you are starting from a single cloud account and a deadline, this order produces evidence fastest:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Turn on the recorders.&lt;/strong&gt; Audit logging, configuration history, threat detection. Do this first, today if possible: the clock on your observation window starts when they do, and nothing else you do this quarter matters as much.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fix identity.&lt;/strong&gt; Single sign-on, no long-lived credentials, defined roles, and a revocation path that actually revokes. This is the largest control family and the one where evidence is hardest to fake. We wrote about the credential half of it in &lt;a href=&quot;/posts/zero-long-lived-cloud-credentials-oidc/&quot;&gt;zero long-lived cloud credentials&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Route every change through review.&lt;/strong&gt; Infrastructure as code with pull requests and an approval gate on production. Your change management evidence then generates itself.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Separate the log archive&lt;/strong&gt; and deny anyone the ability to stop it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Map controls to the framework&lt;/strong&gt; and let the continuous checks tell you what is failing, rather than discovering it during fieldwork.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rehearse.&lt;/strong&gt; Pull a quarter’s evidence as though the auditor asked. Whatever takes more than an hour is a gap.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Steps one through three cover a surprising share of what gets tested, and all three are things you want anyway.&lt;/p&gt;
&lt;h2 id=&quot;the-part-infrastructure-cannot-do&quot;&gt;The part infrastructure cannot do&lt;/h2&gt;
&lt;p&gt;Your report will also cover policies, employee onboarding and offboarding, security awareness training, vendor risk, business continuity and incident response. None of that lives in a cloud account. A compliance automation platform helps track it, and a good auditor will tell you where the gaps are early if you ask them early.&lt;/p&gt;
&lt;p&gt;What infrastructure can do is make its own half boring — so that the week before fieldwork is spent on the human controls, which is the half that usually needs the attention.&lt;/p&gt;
&lt;p&gt;If you want the infrastructure half handled as part of building the foundation rather than as a project afterward, that is what the &lt;a href=&quot;https://builtforprod.com/baseline/&quot;&gt;BuiltForProd Baseline&lt;/a&gt; does, and &lt;a href=&quot;https://builtforprod.com/solutions/soc2-readiness/&quot;&gt;SOC 2 readiness&lt;/a&gt; is the situation it is most often bought for.&lt;/p&gt;</content:encoded><category>Compliance</category><category>compliance</category><category>audit</category><category>governance</category><category>identity</category><category>operations</category><author>BuiltForProd</author></item><item><title>Zero long-lived cloud credentials: OIDC in CI/CD</title><link>https://blog.builtforprod.com/posts/zero-long-lived-cloud-credentials-oidc/</link><guid isPermaLink="true">https://blog.builtforprod.com/posts/zero-long-lived-cloud-credentials-oidc/</guid><description>Static cloud keys in CI are a liability you chose to keep. OIDC federation replaces them with short-lived tokens your pipeline requests per job.</description><pubDate>Tue, 15 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;OIDC federation&lt;/strong&gt; is a way for a CI/CD pipeline to obtain cloud credentials without storing any. Instead of holding a secret key, the pipeline receives a short-lived, signed token from its CI provider describing the job that is running — which repository, which workflow, which branch — and exchanges that token for a temporary cloud credential. The credential is issued for the run and expires with it. Nothing durable is kept anywhere.&lt;/p&gt;
&lt;p&gt;The reason this matters is not that key storage is hard. It is that a long-lived cloud access key is a password with no expiry, living in a system whose entire purpose is to make things available to automation. It is copied into a local environment file by a developer who is debugging a pipeline. It ends up in a screenshot in a support ticket. It survives the contractor who created it. And unlike a password, nobody is ever prompted to change it.&lt;/p&gt;
&lt;h2 id=&quot;what-a-stored-key-actually-costs&quot;&gt;What a stored key actually costs&lt;/h2&gt;
&lt;p&gt;The clean way to think about a static credential is to ask what an attacker gets from a single copy of it. The answer is: everything that credential can do, from anywhere on the internet, until somebody notices.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Stored access key&lt;/th&gt;
&lt;th&gt;Federated short-lived credential&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lifetime&lt;/td&gt;
&lt;td&gt;Until revoked, which usually means until an incident&lt;/td&gt;
&lt;td&gt;Minutes to an hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where it can be used from&lt;/td&gt;
&lt;td&gt;Anywhere&lt;/td&gt;
&lt;td&gt;Only from a job that can prove the right claims&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What it is scoped to&lt;/td&gt;
&lt;td&gt;Whatever the identity can do&lt;/td&gt;
&lt;td&gt;The role the trust policy allows for that specific job&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rotation&lt;/td&gt;
&lt;td&gt;A manual process someone owns and postpones&lt;/td&gt;
&lt;td&gt;Not applicable; there is nothing to rotate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blast radius of a leak&lt;/td&gt;
&lt;td&gt;Full, silent, indefinite&lt;/td&gt;
&lt;td&gt;Bounded by the remaining minutes of a session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evidence trail&lt;/td&gt;
&lt;td&gt;A key was used&lt;/td&gt;
&lt;td&gt;A named job in a named repository assumed a named role&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That last row is underrated. With federation, every action in the cloud traces back to a workflow run, which traces back to a commit and a reviewer. Attribution stops being a forensic exercise.&lt;/p&gt;
&lt;h2 id=&quot;how-the-exchange-works&quot;&gt;How the exchange works&lt;/h2&gt;
&lt;p&gt;There are four parties and one handshake.&lt;/p&gt;
&lt;figure class=&quot;bfp-figure&quot;&gt;
  &lt;span class=&quot;bfp-eyebrow&quot;&gt;Figure 1&lt;/span&gt;
  &lt;div class=&quot;bfp-figure__frame&quot;&gt;
    &lt;svg viewBox=&quot;0 0 560 208&quot; role=&quot;img&quot; aria-labelledby=&quot;oidc-title oidc-desc&quot;&gt;&lt;title id=&quot;oidc-title&quot;&gt;The OIDC credential exchange&lt;/title&gt;&lt;desc id=&quot;oidc-desc&quot;&gt;&lt;p&gt;A pipeline job requests an identity token from its CI provider. It presents that token to the cloud security token
service, which verifies the signature against the provider’s public keys and checks the token’s claims against the
deploy role’s trust policy. The service returns a short-lived credential that the job uses and that expires when
the job ends.&lt;/p&gt;&lt;/desc&gt;&lt;rect class=&quot;d-node d-node--active&quot; x=&quot;18&quot; y=&quot;60&quot; width=&quot;118&quot; height=&quot;44&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;77&quot; y=&quot;80&quot;&gt;pipeline job&lt;/text&gt;&lt;text class=&quot;d-note d-note--mid&quot; x=&quot;77&quot; y=&quot;96&quot;&gt;repo · branch&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;176&quot; y=&quot;16&quot; width=&quot;130&quot; height=&quot;40&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;241&quot; y=&quot;34&quot;&gt;CI provider&lt;/text&gt;&lt;text class=&quot;d-note d-note--mid&quot; x=&quot;241&quot; y=&quot;48&quot;&gt;signs the claim&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;204&quot; y=&quot;116&quot; width=&quot;150&quot; height=&quot;44&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;279&quot; y=&quot;136&quot;&gt;token service&lt;/text&gt;&lt;text class=&quot;d-note d-note--mid&quot; x=&quot;279&quot; y=&quot;152&quot;&gt;verifies · checks policy&lt;/text&gt;&lt;rect class=&quot;d-node d-node--active&quot; x=&quot;404&quot; y=&quot;60&quot; width=&quot;138&quot; height=&quot;44&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;473&quot; y=&quot;80&quot;&gt;deploy role&lt;/text&gt;&lt;text class=&quot;d-note d-note--mid&quot; x=&quot;473&quot; y=&quot;96&quot;&gt;least privilege&lt;/text&gt;&lt;path class=&quot;d-edge d-edge--active&quot; d=&quot;M120 60 L200 56&quot;&gt;&lt;/path&gt;&lt;text class=&quot;d-note&quot; x=&quot;128&quot; y=&quot;44&quot;&gt;1. request token&lt;/text&gt;&lt;path class=&quot;d-edge d-edge--active&quot; d=&quot;M241 56 L250 116&quot;&gt;&lt;/path&gt;&lt;text class=&quot;d-note&quot; x=&quot;258&quot; y=&quot;92&quot;&gt;2. present claim&lt;/text&gt;&lt;path class=&quot;d-edge d-edge--dashed&quot; d=&quot;M354 132 L473 110&quot;&gt;&lt;/path&gt;&lt;text class=&quot;d-note&quot; x=&quot;360&quot; y=&quot;176&quot;&gt;3. trust policy match&lt;/text&gt;&lt;path class=&quot;d-edge d-edge--active&quot; d=&quot;M404 86 L136 86&quot;&gt;&lt;/path&gt;&lt;text class=&quot;d-note d-note--mid&quot; x=&quot;270&quot; y=&quot;196&quot;&gt;4. short-lived credential, scoped to this job&lt;/text&gt;&lt;/svg&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;The pipeline never holds a secret. It presents a signed claim about itself; the cloud verifies the signature against the provider&amp;#39;s public keys, checks the claim against the role&amp;#39;s trust policy, and issues a credential that expires with the job.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The job asks its CI provider for an identity token.&lt;/strong&gt; The provider mints a JSON Web Token describing the run: the issuer, the audience, and a subject claim naming the repository and the branch, tag or environment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The job presents that token to the cloud.&lt;/strong&gt; No secret is involved; the token is the evidence.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The cloud verifies it.&lt;/strong&gt; The signature is checked against the provider’s published keys, and the claims are checked against the trust policy attached to the role being requested.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A temporary credential comes back&lt;/strong&gt;, scoped to that role and expiring shortly.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The design is unremarkable. The failure modes are where the interesting work is.&lt;/p&gt;
&lt;h2 id=&quot;the-trust-policy-is-the-whole-control&quot;&gt;The trust policy is the whole control&lt;/h2&gt;
&lt;p&gt;Everything that makes federation safe lives in one document, and it is easy to write a version that is worse than the key it replaced.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pin the subject claim to the smallest thing that is true.&lt;/strong&gt; A condition that matches &lt;code&gt;repo:your-org/*:*&lt;/code&gt; trusts every repository in the organization, including one a contractor created this morning, including one an attacker can open a pull request against. Pin it to the repository, and then to the branch, tag or deployment environment that is allowed to deploy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prefer environments to branches for production.&lt;/strong&gt; A branch condition is satisfied by anything that can push to that branch. An environment condition can additionally require a human approval before the token is issued at all — which is the difference between a pipeline that &lt;em&gt;can&lt;/em&gt; reach production and one that reaches production only when someone says so.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Check the audience.&lt;/strong&gt; It is one line and it prevents a token minted for one relying party from being replayed at another.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use separate roles per environment.&lt;/strong&gt; One role that can deploy anywhere is a convenience that removes the benefit you just bought. A role per environment, each with only the permissions that environment needs, keeps the blast radius aligned with the account boundaries described in &lt;a href=&quot;/posts/multi-account-landing-zone-explained/&quot;&gt;the multi-account landing zone&lt;/a&gt;.&lt;/p&gt;
&lt;aside class=&quot;bfp-callout bfp-callout--warning&quot;&gt;
  &lt;p class=&quot;bfp-callout__title&quot;&gt;The misconfiguration to look for first&lt;/p&gt;
  &lt;p&gt;If you inherit an existing federation setup, read the subject condition before anything else. A wildcard there is the
most common serious mistake in this pattern, and it converts “no stored keys” into “every repository in the
organization can deploy to production”.&lt;/p&gt;
&lt;/aside&gt;
&lt;h2 id=&quot;deleting-the-keys-is-the-point&quot;&gt;Deleting the keys is the point&lt;/h2&gt;
&lt;p&gt;The step teams skip is the only one that changes the risk. Running federation &lt;em&gt;alongside&lt;/em&gt; the old access keys means you have added a mechanism and removed nothing. The keys are still there, still valid, still copied into whatever they were copied into two years ago.&lt;/p&gt;
&lt;p&gt;A migration that finishes looks like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Stand up federation and move one non-production pipeline to it.&lt;/li&gt;
&lt;li&gt;Move the rest, environment by environment, production last.&lt;/li&gt;
&lt;li&gt;Inventory every long-lived credential in the organization. Note who created each one and what it is used for. The list will contain surprises.&lt;/li&gt;
&lt;li&gt;Disable them, watch for a defined window, then delete them.&lt;/li&gt;
&lt;li&gt;Prevent new ones structurally.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Step five is what makes it stick. A policy at the organization level that denies the creation of long-lived credentials outright turns “we don’t use access keys” from a convention someone has to remember into a property of the environment. Conventions decay at the first deadline; structure does not.&lt;/p&gt;
&lt;h2 id=&quot;what-this-looks-like-when-it-is-finished&quot;&gt;What this looks like when it is finished&lt;/h2&gt;
&lt;p&gt;In the &lt;strong&gt;AWS Enterprise Baseline&lt;/strong&gt; — the AWS edition of the &lt;a href=&quot;https://builtforprod.com/baseline/&quot;&gt;BuiltForProd Baseline&lt;/a&gt; — there are no IAM users anywhere and no long-lived AWS access keys for anyone, human or machine. People sign in through IAM Identity Center against one of eleven permission sets, with multi-factor authentication required and sessions that expire — the human half of the same model, covered in &lt;a href=&quot;/posts/zero-trust-access-for-engineers/&quot;&gt;zero trust access for engineers&lt;/a&gt;. Pipelines federate through GitHub OIDC into per-environment deployer roles in a dedicated automation account. A service control policy denies the creation of IAM users and access keys across the organization, which means the property is enforced above every account rather than agreed inside each one. Production is read-only for everyone except the platform and DevOps leads, and every apply to production passes a repository environment gate first.&lt;/p&gt;
&lt;p&gt;Those specifics describe the AWS edition. The property they deliver — no standing credential exists for routine work — is part of the &lt;a href=&quot;https://builtforprod.com/standard/&quot;&gt;BuiltForProd Standard&lt;/a&gt; and applies to every edition.&lt;/p&gt;
&lt;h2 id=&quot;a-short-checklist&quot;&gt;A short checklist&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;No long-lived cloud access key exists for a human or for a pipeline.&lt;/li&gt;
&lt;li&gt;Every trust policy pins issuer, audience and a subject claim narrowed to a repository and a branch, tag or environment.&lt;/li&gt;
&lt;li&gt;Production deployment requires an approval that gates token issuance, not only the merge.&lt;/li&gt;
&lt;li&gt;Roles are per environment and least privilege, not one role that can reach everything.&lt;/li&gt;
&lt;li&gt;Creating a long-lived credential is denied by policy, not discouraged by documentation.&lt;/li&gt;
&lt;li&gt;A break-glass path exists, is time-boxed, is alerted on, and has been exercised this year.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If more than two of those are open, the work is worth a sprint. If all six are open, it is worth a conversation — &lt;a href=&quot;https://builtforprod.com/contact/&quot;&gt;with us&lt;/a&gt; or with anyone, but soon.&lt;/p&gt;</content:encoded><category>Security</category><category>identity</category><category>credentials</category><category>CI/CD</category><category>automation</category><category>AWS</category><category>governance</category><author>BuiltForProd</author></item><item><title>The multi-account landing zone, explained</title><link>https://blog.builtforprod.com/posts/multi-account-landing-zone-explained/</link><guid isPermaLink="true">https://blog.builtforprod.com/posts/multi-account-landing-zone-explained/</guid><description>Account boundaries are the first security control you get and the hardest to add later. Here is what each account in a landing zone is for, and why.</description><pubDate>Tue, 08 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A &lt;strong&gt;landing zone&lt;/strong&gt; is the account structure, identity model, network topology, logging pipeline and guardrails that everything else in a cloud organization is built on top of. It is the layer no workload owns and every workload inherits. Get it right and most security and compliance questions have boring answers. Get it wrong and every subsequent decision is made inside a constraint nobody chose.&lt;/p&gt;
&lt;p&gt;The reason it matters more than it looks is simple: &lt;strong&gt;the account boundary is the strongest isolation a cloud provider gives you.&lt;/strong&gt; A security group is a rule inside an environment. An IAM policy is a rule inside an account. An account boundary is enforced above both, by the provider, and it holds even when the thing inside it is compromised. It is also the boundary that billing, quotas, audit scope and blast radius all happen to follow.&lt;/p&gt;
&lt;h2 id=&quot;why-account-structure-is-a-security-control&quot;&gt;Why account structure is a security control&lt;/h2&gt;
&lt;p&gt;Most teams arrive at the multi-account question from the wrong direction. They start with one account because one account is simple, add a second when production and development collide, and end up with a structure that records their history rather than their requirements.&lt;/p&gt;
&lt;p&gt;Three properties make the account boundary different from anything inside it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;It is enforced by the provider.&lt;/strong&gt; Cross-account access has to be granted explicitly. There is no default path, no shared network, no accidental permission inherited from a policy someone wrote in a hurry.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It is the natural audit scope.&lt;/strong&gt; “Which systems handle regulated data” becomes a list of accounts instead of a survey of tags.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It fails safe.&lt;/strong&gt; A misconfiguration inside an account is contained by the account. A misconfiguration in a single shared account is contained by nothing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The trade-off is real: more accounts mean more automation is required, because manual management does not scale past about three. That is the actual decision. Not “how many accounts” but “are we willing to automate the boundary we need”.&lt;/p&gt;
&lt;h2 id=&quot;a-worked-example-14-accounts-2-organizational-units&quot;&gt;A worked example: 14 accounts, 2 organizational units&lt;/h2&gt;
&lt;p&gt;Abstract advice about landing zones is easy to agree with and hard to use. So here is a concrete one. The &lt;strong&gt;AWS Enterprise Baseline&lt;/strong&gt; — the AWS edition of the &lt;a href=&quot;https://builtforprod.com/baseline/&quot;&gt;BuiltForProd Baseline&lt;/a&gt; — deploys one AWS Organization with 14 accounts across 2 organizational units. The numbers below describe that edition specifically.&lt;/p&gt;
&lt;figure class=&quot;bfp-figure&quot;&gt;
  &lt;span class=&quot;bfp-eyebrow&quot;&gt;Figure 1&lt;/span&gt;
  &lt;div class=&quot;bfp-figure__frame&quot;&gt;
    &lt;svg viewBox=&quot;0 0 560 210&quot; role=&quot;img&quot; aria-labelledby=&quot;lz-title lz-desc&quot;&gt;&lt;title id=&quot;lz-title&quot;&gt;A 14-account organization in two organizational units&lt;/title&gt;&lt;desc id=&quot;lz-desc&quot;&gt;&lt;p&gt;A management account sits at the top, outside both organizational units. Below it, the core organizational unit
holds nine accounts: security, audit, identity, network, dns, artifacts, auto, corp and public. The plat
organizational unit holds four workload accounts: sandbox, dev, staging and prod.&lt;/p&gt;&lt;/desc&gt;&lt;rect class=&quot;d-node&quot; x=&quot;210&quot; y=&quot;14&quot; width=&quot;140&quot; height=&quot;28&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;280&quot; y=&quot;32&quot;&gt;core-root&lt;/text&gt;&lt;text class=&quot;d-note&quot; x=&quot;364&quot; y=&quot;24&quot;&gt;management account&lt;/text&gt;&lt;text class=&quot;d-note&quot; x=&quot;364&quot; y=&quot;38&quot;&gt;empty by design&lt;/text&gt;&lt;path class=&quot;d-edge&quot; d=&quot;M280 42 L165 62&quot;&gt;&lt;/path&gt;&lt;path class=&quot;d-edge d-edge--active&quot; d=&quot;M280 42 L423 62&quot;&gt;&lt;/path&gt;&lt;rect class=&quot;d-plane&quot; x=&quot;32&quot; y=&quot;62&quot; width=&quot;266&quot; height=&quot;132&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-title&quot; x=&quot;40&quot; y=&quot;78&quot;&gt;core&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;40&quot; y=&quot;88&quot; width=&quot;74&quot; height=&quot;24&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;77&quot; y=&quot;104&quot;&gt;security&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;128&quot; y=&quot;88&quot; width=&quot;74&quot; height=&quot;24&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;165&quot; y=&quot;104&quot;&gt;audit&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;216&quot; y=&quot;88&quot; width=&quot;74&quot; height=&quot;24&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;253&quot; y=&quot;104&quot;&gt;identity&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;40&quot; y=&quot;120&quot; width=&quot;74&quot; height=&quot;24&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;77&quot; y=&quot;136&quot;&gt;network&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;128&quot; y=&quot;120&quot; width=&quot;74&quot; height=&quot;24&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;165&quot; y=&quot;136&quot;&gt;dns&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;216&quot; y=&quot;120&quot; width=&quot;74&quot; height=&quot;24&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;253&quot; y=&quot;136&quot;&gt;artifacts&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;40&quot; y=&quot;152&quot; width=&quot;74&quot; height=&quot;24&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;77&quot; y=&quot;168&quot;&gt;auto&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;128&quot; y=&quot;152&quot; width=&quot;74&quot; height=&quot;24&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;165&quot; y=&quot;168&quot;&gt;corp&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;216&quot; y=&quot;152&quot; width=&quot;74&quot; height=&quot;24&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;253&quot; y=&quot;168&quot;&gt;public&lt;/text&gt;&lt;rect class=&quot;d-plane&quot; x=&quot;318&quot; y=&quot;62&quot; width=&quot;210&quot; height=&quot;132&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-title&quot; x=&quot;326&quot; y=&quot;78&quot;&gt;plat&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;326&quot; y=&quot;88&quot; width=&quot;90&quot; height=&quot;24&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;371&quot; y=&quot;104&quot;&gt;sandbox&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;428&quot; y=&quot;88&quot; width=&quot;90&quot; height=&quot;24&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;473&quot; y=&quot;104&quot;&gt;dev&lt;/text&gt;&lt;rect class=&quot;d-node&quot; x=&quot;326&quot; y=&quot;120&quot; width=&quot;90&quot; height=&quot;24&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;371&quot; y=&quot;136&quot;&gt;staging&lt;/text&gt;&lt;rect class=&quot;d-node d-node--active&quot; x=&quot;428&quot; y=&quot;120&quot; width=&quot;90&quot; height=&quot;24&quot; rx=&quot;2&quot;&gt;&lt;/rect&gt;&lt;text class=&quot;d-label&quot; x=&quot;473&quot; y=&quot;136&quot;&gt;prod&lt;/text&gt;&lt;text class=&quot;d-note&quot; x=&quot;326&quot; y=&quot;182&quot;&gt;one template, four environments&lt;/text&gt;&lt;/svg&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;The AWS Enterprise Baseline: an empty management account outside the organizational units, nine core accounts that provide shared capabilities, and four workload accounts generated from one template.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id=&quot;the-management-account-is-empty&quot;&gt;The management account is empty&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;core-root&lt;/code&gt; sits outside both organizational units and runs nothing. It exists to own the organization: the account tree, the service control policies, the tag policy. That is all.&lt;/p&gt;
&lt;p&gt;This is the decision in the layout that pays back the most for the least effort. The management account is the one account that can change the rules everything else lives under. If a workload runs there, then compromising that workload means compromising the guardrails, and every other control in the organization is downstream of a thing an attacker now owns. Keeping it empty is not fastidiousness. It removes an entire class of incident.&lt;/p&gt;
&lt;h3 id=&quot;nine-core-accounts-each-with-one-job&quot;&gt;Nine core accounts, each with one job&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Account&lt;/th&gt;
&lt;th&gt;What lives there&lt;/th&gt;
&lt;th&gt;Why it is separate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;security&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delegated administration for the security services, and the observability sink&lt;/td&gt;
&lt;td&gt;The account that watches everything should not be an account that runs anything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;audit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The log archive and the configuration aggregator&lt;/td&gt;
&lt;td&gt;Evidence must survive a compromise of the thing it is evidence about&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;identity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The single sign-on directory and permission sets&lt;/td&gt;
&lt;td&gt;One place to grant and, more importantly, revoke&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;network&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The hub network, address management, private DNS&lt;/td&gt;
&lt;td&gt;Network topology is shared infrastructure, not a workload concern&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dns&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Public DNS zones&lt;/td&gt;
&lt;td&gt;Public name resolution has a different risk profile from private&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;artifacts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Container registries&lt;/td&gt;
&lt;td&gt;Build outputs are shared across environments and outlive any one of them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;auto&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CI/CD federation, deployer roles, runners&lt;/td&gt;
&lt;td&gt;The automation identity is a high-value target; it gets its own boundary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;corp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Internal tooling&lt;/td&gt;
&lt;td&gt;Employee-facing systems are not customer-facing systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;public&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Assets that are deliberately public&lt;/td&gt;
&lt;td&gt;Making “public” an explicit place stops it becoming an accident elsewhere&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The pattern is the same in every row: a capability that belongs to &lt;em&gt;the organization&lt;/em&gt; rather than to &lt;em&gt;a workload&lt;/em&gt; gets its own boundary, so that granting access to it is a deliberate act.&lt;/p&gt;
&lt;h3 id=&quot;four-workload-accounts-from-one-template&quot;&gt;Four workload accounts from one template&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;sandbox&lt;/code&gt;, &lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;staging&lt;/code&gt; and &lt;code&gt;prod&lt;/code&gt; are generated from a single definition. They differ in sizing, retention and who may act in them — not in structure. That is what makes “it works in staging” a meaningful sentence: staging and production are the same code with different inputs, so a difference between them is a bug rather than a fact of life.&lt;/p&gt;
&lt;p&gt;Prod is also where the network stops being permissive. In this edition, the transit gateway carries deliberate blackhole routes between the production and non-production isolation domains: they cannot reach each other, by construction rather than by firewall rule.&lt;/p&gt;
&lt;aside class=&quot;bfp-callout bfp-callout--signal&quot;&gt;
  &lt;p class=&quot;bfp-callout__title&quot;&gt;Attribution matters&lt;/p&gt;
  &lt;p&gt;The numbers above — 14 accounts, 2 organizational units, the account names — describe the AWS Enterprise Baseline. The
Azure and GCP editions are built to the same BuiltForProd Standard and aligned to their own provider frameworks, and
we do not quote AWS figures for them.&lt;/p&gt;
&lt;/aside&gt;
&lt;h2 id=&quot;the-test-what-does-adding-an-account-cost&quot;&gt;The test: what does adding an account cost?&lt;/h2&gt;
&lt;p&gt;A landing zone is healthy when growth is cheap. The question to ask about any structure — yours or a vendor’s — is not how many accounts it has today. It is what happens when you need the fifteenth.&lt;/p&gt;
&lt;p&gt;In the AWS Enterprise Baseline, a new account is an entry in a map and a folder in a repository: two pull requests, reviewed and applied by the same pipeline as everything else. The same property holds for regions. A region is a folder; the address plan, the region-restriction policy and the tag policy discover it automatically. The address plan itself is sized for 16 regions with 8 workload stages each, so expansion is a decision rather than a redesign.&lt;/p&gt;
&lt;p&gt;When adding an account is a project, three things follow, and they follow every time: teams share accounts they should not share, environments diverge because nobody wants to touch the structure, and the eventual restructure lands during the quarter when something urgent is already happening.&lt;/p&gt;
&lt;h2 id=&quot;what-a-landing-zone-does-not-solve&quot;&gt;What a landing zone does not solve&lt;/h2&gt;
&lt;p&gt;Being honest about the boundary of the idea:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It does not make your application secure. It makes the failure of your application survivable.&lt;/li&gt;
&lt;li&gt;It does not produce compliance. It produces the evidence sources that make compliance work possible — a different and much smaller claim. We wrote about that distinction in &lt;a href=&quot;/posts/soc-2-cloud-infrastructure-evidence/&quot;&gt;SOC 2 on cloud infrastructure&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;It does not remove the need for people who understand it. It reduces the number of things they have to hold in their heads, and it writes the rest down.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;where-to-start&quot;&gt;Where to start&lt;/h2&gt;
&lt;p&gt;If you are on a single account today, the useful first move is not a migration. It is a map: list every system, the data it touches, who may change it, and what would happen if it were compromised. The account boundaries usually draw themselves from that list, and the ones that do not are the ones worth arguing about.&lt;/p&gt;
&lt;p&gt;If you would rather inherit a structure than design one, that is what the &lt;a href=&quot;https://builtforprod.com/baseline/&quot;&gt;BuiltForProd Baseline&lt;/a&gt; is: a foundation deployed into your own accounts, with the repositories and documentation handed over, so the thing you run is yours rather than rented. If you want an outside read first, the &lt;a href=&quot;https://builtforprod.com/assessment/&quot;&gt;BuiltForProd Assessment&lt;/a&gt; exists for that, and &lt;a href=&quot;https://builtforprod.com/why-builtforprod/&quot;&gt;we will tell you if we are not the right fit&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>Architecture</category><category>networking</category><category>governance</category><category>identity</category><category>AWS</category><category>infrastructure as code</category><author>BuiltForProd</author></item><item><title>Backups you haven&apos;t restored are assumptions</title><link>https://blog.builtforprod.com/posts/backups-you-havent-restored-are-assumptions/</link><guid isPermaLink="true">https://blog.builtforprod.com/posts/backups-you-havent-restored-are-assumptions/</guid><description>A backup nobody has restored is a hypothesis, not a control. Here is how to run a restore drill and set an RTO and an RPO you can defend.</description><pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A &lt;strong&gt;restore test&lt;/strong&gt; is an exercise in which a team recovers a real system from its backups, against a clock, and verifies that the recovered system works. Until that exercise has been run, a backup is a hypothesis: there is a job, the job reports success, and everything else about it is assumption. The backup industry has trained us to check the wrong thing. A green backup job proves a job ran. It does not prove the data is complete, that the restore path exists, that anyone has permission to run it, or that the restored system comes back in an order that works.&lt;/p&gt;
&lt;p&gt;This post is about closing that gap: what recovery objectives actually promise, why untested restores fail, and how to run a drill that produces a number you can put in front of a board or an auditor.&lt;/p&gt;
&lt;h2 id=&quot;what-rto-and-rpo-actually-promise&quot;&gt;What RTO and RPO actually promise&lt;/h2&gt;
&lt;p&gt;Two numbers describe recovery, and they point in opposite directions from the moment of failure.&lt;/p&gt;
&lt;figure class=&quot;bfp-figure&quot;&gt;
  &lt;span class=&quot;bfp-eyebrow&quot;&gt;Figure 1&lt;/span&gt;
  &lt;div class=&quot;bfp-figure__frame&quot;&gt;
    &lt;svg viewBox=&quot;0 0 560 196&quot; role=&quot;img&quot; aria-labelledby=&quot;rpo-title rpo-desc&quot;&gt;&lt;title id=&quot;rpo-title&quot;&gt;RPO and RTO on one timeline&lt;/title&gt;&lt;desc id=&quot;rpo-desc&quot;&gt;&lt;p&gt;A timeline with three marks: the last recoverable point, the failure, and verified service. The span from the last
recoverable point to the failure is the recovery point objective, the data lost. The span from the failure to
verified service is the recovery time objective, the time the system is down.&lt;/p&gt;&lt;/desc&gt;&lt;line class=&quot;d-edge&quot; x1=&quot;24&quot; y1=&quot;120&quot; x2=&quot;536&quot; y2=&quot;120&quot;&gt;&lt;/line&gt;&lt;line class=&quot;d-edge&quot; x1=&quot;120&quot; y1=&quot;104&quot; x2=&quot;120&quot; y2=&quot;136&quot;&gt;&lt;/line&gt;&lt;line class=&quot;d-edge d-edge--active&quot; x1=&quot;280&quot; y1=&quot;94&quot; x2=&quot;280&quot; y2=&quot;146&quot;&gt;&lt;/line&gt;&lt;line class=&quot;d-edge&quot; x1=&quot;464&quot; y1=&quot;104&quot; x2=&quot;464&quot; y2=&quot;136&quot;&gt;&lt;/line&gt;&lt;text class=&quot;d-note d-note--mid&quot; x=&quot;120&quot; y=&quot;92&quot;&gt;Last recoverable point&lt;/text&gt;&lt;text class=&quot;d-label&quot; x=&quot;280&quot; y=&quot;82&quot;&gt;Failure&lt;/text&gt;&lt;text class=&quot;d-note d-note--mid&quot; x=&quot;464&quot; y=&quot;92&quot;&gt;Verified service&lt;/text&gt;&lt;path class=&quot;d-edge d-edge--dashed&quot; d=&quot;M120 154 L120 166 M120 160 L280 160 M280 154 L280 166&quot;&gt;&lt;/path&gt;&lt;path class=&quot;d-edge d-edge--active&quot; d=&quot;M280 154 L280 166 M280 160 L464 160 M464 154 L464 166&quot;&gt;&lt;/path&gt;&lt;text class=&quot;d-note d-note--mid&quot; x=&quot;200&quot; y=&quot;186&quot;&gt;RPO — data you lose&lt;/text&gt;&lt;text class=&quot;d-note d-note--mid&quot; x=&quot;372&quot; y=&quot;186&quot;&gt;RTO — time you are down&lt;/text&gt;&lt;/svg&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;RPO is measured backward from the failure and is paid in lost data. RTO is measured forward and is paid in downtime. Both are business decisions; only the delivery is an engineering problem.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;RPO, the recovery point objective&lt;/strong&gt;, is how much data you have agreed to lose. If your database is snapshotted every six hours, your RPO cannot be better than six hours no matter how fast the restore runs. &lt;strong&gt;RTO, the recovery time objective&lt;/strong&gt;, is how long you have agreed to be unavailable, measured from the failure to verified working service — not to “the restore command finished”.&lt;/p&gt;
&lt;p&gt;Both numbers are set by the business and delivered by engineering, and the most common failure in the conversation is that only one side ever says a number out loud. An RPO of fifteen minutes and an RTO of an hour are perfectly reasonable requests, and they imply a specific, costed set of mechanisms. Write both down for each system that matters, then check what you actually have.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Typical RPO&lt;/th&gt;
&lt;th&gt;Typical RTO&lt;/th&gt;
&lt;th&gt;What it does not protect against&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Synchronous replica&lt;/td&gt;
&lt;td&gt;Near zero&lt;/td&gt;
&lt;td&gt;Minutes&lt;/td&gt;
&lt;td&gt;Deletion, bad migration, ransomware — the replica copies them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Daily snapshot&lt;/td&gt;
&lt;td&gt;Up to 24 hours&lt;/td&gt;
&lt;td&gt;Hours&lt;/td&gt;
&lt;td&gt;Anything that happened since the snapshot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Point-in-time recovery&lt;/td&gt;
&lt;td&gt;Minutes&lt;/td&gt;
&lt;td&gt;Hours&lt;/td&gt;
&lt;td&gt;Loss of the storage account or the key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object versioning&lt;/td&gt;
&lt;td&gt;Near zero per object&lt;/td&gt;
&lt;td&gt;Hours to days at scale&lt;/td&gt;
&lt;td&gt;A bulk delete that also removes versions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rebuild from code&lt;/td&gt;
&lt;td&gt;Not applicable — infrastructure carries no data&lt;/td&gt;
&lt;td&gt;Hours, if rehearsed&lt;/td&gt;
&lt;td&gt;Application state, which still needs its own copy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The last row is the one teams forget. Recovering &lt;em&gt;data&lt;/em&gt; and recovering &lt;em&gt;the environment the data lives in&lt;/em&gt; are different problems with different mechanisms, and a real incident often needs both.&lt;/p&gt;
&lt;h2 id=&quot;the-four-ways-an-untested-restore-fails&quot;&gt;The four ways an untested restore fails&lt;/h2&gt;
&lt;p&gt;Across postmortems, failed restores cluster into four causes. None of them are exotic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The backup was incomplete.&lt;/strong&gt; The database was captured; the object store holding user uploads was not. The application comes back with a consistent database and broken references. This is the most common finding in a first drill, and the only reliable way to find it is to restore and then use the system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The restore path was undocumented.&lt;/strong&gt; Someone knew how to do it in the old architecture. The current architecture has different encryption, a different network path and a different bootstrap order. The knowledge did not survive the migration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A dependency was missing.&lt;/strong&gt; The restore needs a KMS key that lived in the account that was deleted, a DNS record that no longer exists, or a secret that was rotated after the backup was taken. Encrypted backups are only as recoverable as the key material, and key material has its own lifecycle.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nobody had permission.&lt;/strong&gt; In a well-governed environment, production is read-only for almost everyone. That is correct, until the one person with the restore role is on a plane. A break-glass path that has never been exercised is not a path.&lt;/p&gt;
&lt;aside class=&quot;bfp-callout bfp-callout--warning&quot;&gt;
  &lt;p class=&quot;bfp-callout__title&quot;&gt;The uncomfortable version&lt;/p&gt;
  &lt;p&gt;Every one of these is discovered in minutes by a drill and in hours by an outage. The only variable is which one you
choose.&lt;/p&gt;
&lt;/aside&gt;
&lt;h2 id=&quot;running-the-drill&quot;&gt;Running the drill&lt;/h2&gt;
&lt;p&gt;A restore drill is not a tabletop exercise. Somebody types the commands.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Pick a real target and a real clock.&lt;/strong&gt; Choose the system whose loss would stop the business. Announce a window, then start a timer that does not stop until someone other than the operator confirms the system works.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Restore into a separate, isolated environment.&lt;/strong&gt; A sandbox account or subscription, not production and not staging-that-someone-depends-on. The point is to prove the path, not to create a second incident.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Restore everything the system needs, in order.&lt;/strong&gt; Data store, object storage, secrets, configuration, DNS, and whatever else the application reads at startup. Write down the order you discovered, because next time that order is the runbook.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verify with a business transaction, not a health check.&lt;/strong&gt; A health endpoint returning 200 proves the process started. Place an order, run a report, log in as a real user. That is the definition of “restored”.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Record the number you actually achieved.&lt;/strong&gt; Not the target. The achieved figure is the only honest input to the next planning conversation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fix one thing.&lt;/strong&gt; Every drill surfaces more than one problem. Pick the one that most reduces the achieved RTO and fix it before the next drill, rather than filing all of them and doing none.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first drill will be slow and slightly humiliating. That is the point: it is the cheapest possible version of the bad day. Teams routinely find that a promised four-hour RTO is a nine-hour RTO the first time, and a three-hour RTO the third time.&lt;/p&gt;
&lt;h2 id=&quot;what-the-drill-produces&quot;&gt;What the drill produces&lt;/h2&gt;
&lt;p&gt;The output of a drill is not a feeling of relief. It is three artifacts.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A runbook&lt;/strong&gt; written during the exercise, by the person doing the work, in the order the work actually happened. Runbooks written in advance describe an imagined system; runbooks written during a drill describe the real one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An achieved RTO and RPO&lt;/strong&gt; for that system, dated. Two consecutive drills give you a trend, which is far more useful than a single number.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evidence.&lt;/strong&gt; Most control frameworks ask whether recovery has been tested and when. A dated record naming the system, the operator, the duration and the outcome answers that question directly. Compliance evidence produced as a by-product of engineering work is the only kind that does not require a scramble later — the same principle applies to &lt;a href=&quot;/posts/soc-2-cloud-infrastructure-evidence/&quot;&gt;SOC 2 evidence from infrastructure&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;rebuilding-the-environment-is-also-recovery&quot;&gt;Rebuilding the environment is also recovery&lt;/h2&gt;
&lt;p&gt;Data restore gets the attention, but a large share of real incidents are not “we lost the data”. They are “we lost the environment”: an account was misconfigured beyond repair, a region became unusable, a change cascaded. In that case the question is not what your backup software can do. It is whether the environment exists anywhere other than in the cloud console.&lt;/p&gt;
&lt;p&gt;Infrastructure defined entirely in code changes the shape of that answer. When every network, role, policy and cluster is a reviewed file in a repository, rebuilding is an apply, not an archaeology project. It is not free — you still need the data, the DNS, the certificates and the secrets — but it converts the most improvisational part of a recovery into something a second engineer can rehearse.&lt;/p&gt;
&lt;p&gt;That is the property the &lt;a href=&quot;https://builtforprod.com/standard/&quot;&gt;BuiltForProd Standard&lt;/a&gt; calls &lt;strong&gt;recoverable&lt;/strong&gt;: recovery is engineered and tested, not assumed. In the &lt;a href=&quot;https://builtforprod.com/baseline/&quot;&gt;AWS Enterprise Baseline&lt;/a&gt;, the foundation itself is 100% infrastructure as code, the audit log archive lives in a separate account that the organization’s guardrails prevent anyone from stopping, and drift detection runs on a schedule so the code and the cloud do not quietly diverge between drills. None of that replaces a data backup strategy, and we are explicit with customers that it does not. It removes one large source of improvisation from the worst hour of the year.&lt;/p&gt;
&lt;h2 id=&quot;a-calendar-you-can-actually-keep&quot;&gt;A calendar you can actually keep&lt;/h2&gt;
&lt;p&gt;Ambitious recovery programs die in month two. A modest one survives:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cadence&lt;/th&gt;
&lt;th&gt;Exercise&lt;/th&gt;
&lt;th&gt;Time cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Quarterly&lt;/td&gt;
&lt;td&gt;Full restore of the one system that would stop the business&lt;/td&gt;
&lt;td&gt;Half a day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quarterly&lt;/td&gt;
&lt;td&gt;Break-glass access exercised by someone who does not normally use it&lt;/td&gt;
&lt;td&gt;30 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On change&lt;/td&gt;
&lt;td&gt;Re-test after any change to backup configuration, storage location or keys&lt;/td&gt;
&lt;td&gt;1 hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Annually&lt;/td&gt;
&lt;td&gt;Environment rebuild from code into a clean account&lt;/td&gt;
&lt;td&gt;One day&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Four entries. Put them in the calendar with names attached, not team names. An unowned drill does not happen.&lt;/p&gt;
&lt;h2 id=&quot;where-to-start-this-week&quot;&gt;Where to start this week&lt;/h2&gt;
&lt;p&gt;Pick one system. Ask three questions and insist on dated answers: &lt;em&gt;When did we last restore this? How long did it take? Who did it?&lt;/em&gt; If any answer is “I don’t know” or “we haven’t”, you have found your first drill, and you have learned something important before it cost you anything.&lt;/p&gt;
&lt;p&gt;If you want an outside read on where the gaps are, that is exactly what a &lt;a href=&quot;https://builtforprod.com/assessment/&quot;&gt;BuiltForProd Assessment&lt;/a&gt; is for — and if your systems are already in better shape than you feared, we will tell you that too.&lt;/p&gt;</content:encoded><category>Reliability</category><category>recovery</category><category>incident response</category><category>operations</category><category>runbooks</category><category>governance</category><category>on-call</category><author>BuiltForProd</author></item></channel></rss>