The infrastructure architecture decision record: why the cloud provider and compute abstraction you chose determines your operational ceiling and your vendor dependency surface

Infrastructure architecture decisions are made once — usually in the first weeks of a company's engineering life — and they constrain every subsequent technical decision for years without anyone explicitly recognizing the constraint. The cloud provider is chosen because a founder has a free credit offer or because the team's previous employer used that provider. The compute model is chosen because a blog post made containers sound like the right approach, or because a senior engineer wanted to learn Kubernetes. The account structure is a single AWS account because that's what the console creates when you sign up. The VPC uses the default CIDR because that's what Terraform creates when you don't specify one. None of these choices are wrong. None of them are documented anywhere as architectural decisions. And four years later, when an acquisition conversation requires a technical due diligence, or when the infrastructure costs are 40% of revenue and someone proposes replatforming to serverless, the team cannot reconstruct why the current architecture exists — only that it does.

Infrastructure architecture decisions are the foundational layer that all other infrastructure decisions reference. The capacity planning decision record depends on the compute abstraction model — you cannot plan capacity for a serverless function tier the same way you plan capacity for an EC2 autoscaling group. The cost optimization decision record depends on the cloud provider and the proprietary service adoption depth — the commitment model options available to a DynamoDB-heavy architecture differ from those available to a PostgreSQL-heavy architecture. The disaster recovery architecture depends on the account and network topology — a single-account architecture with multi-AZ RDS has different DR options than a multi-account architecture with cross-account replication. All of these downstream decisions are constrained by the infrastructure architecture decision, and all of them are harder to reason about correctly when the infrastructure architecture decision was never written down.

The infrastructure decision record is not a diagram of your current architecture. Diagrams document what exists; decision records document why it exists and what it prevents. The infrastructure decision record explains: why this cloud provider over the alternatives that were available at the time, which proprietary services have been adopted and what each one trades in portability for in operational simplification, what the account and network topology is and what blast radius model it creates, what compute abstraction model was chosen and what it prevents future workloads from doing. It is the document that lets a new engineering lead understand the infrastructure in an afternoon rather than a quarter — not by reading the Terraform state, but by reading the reasoning that produced the Terraform state.

Two things that happen when the decision is not written down

The acquisition incident: three weeks of technical due diligence that should have been three hours

A 40-person developer tools startup received an acquisition offer from a larger infrastructure software company at Series B. The deal had a 90-day due diligence window. During the first week, the acquirer's technical team sent a three-question infrastructure questionnaire: What cloud provider do you run on? Which services are managed cloud-provider services versus portable open-source? How long would a migration to a different cloud provider take?

The CTO could answer the first question immediately. The second and third questions required three weeks of investigation. The investigation produced a proprietary service inventory that no one had assembled before. The startup's backend used six AWS-specific managed services: DynamoDB as the primary data store, implemented using single-table design with composite sort keys and seven Global Secondary Indexes built around DynamoDB's specific access pattern model; SQS FIFO queues for job ordering guarantees across three background processing pipelines; EventBridge as the internal event router, with rules that used EventBridge's native filtering and transformation syntax; Cognito for user authentication and OAuth flow management; ElastiCache for Redis as the session cache and rate limiting layer; and ECS Fargate for container orchestration.

The portability assessment for each service took a different amount of time to produce. ElastiCache and ECS Fargate were portable with configuration changes — any managed Redis service and any container orchestration platform could serve the same functions. Cognito was portable with a user migration effort estimated at three to four weeks of engineering. SQS FIFO was portable to RabbitMQ or Pub/Sub with a rewrite of the message producer and consumer code and validation of the FIFO semantics equivalence — estimated at six to eight weeks. EventBridge was portable to Kafka with a rewrite of the event rule logic and the event schema translation layer — estimated at four to eight weeks. DynamoDB was the most complex assessment. The single-table design used access patterns — querying by customer ID across multiple entity types in a single table scan, querying by date range and customer status using composite sort keys, using Global Secondary Indexes to support alternate access patterns like querying by subscription tier or by last-active date — that assumed DynamoDB's specific data model. Migrating to PostgreSQL would require not just a data migration but a schema redesign: the data model would need to be normalized into relational tables with appropriate indexes, and the application code that constructed DynamoDB queries would need to be rewritten as SQL. The estimation team assessed the DynamoDB migration at sixteen to twenty weeks of engineering across two senior engineers, plus a data migration process that would require a write-through period during which the application would write to both DynamoDB and the new database concurrently.

The total replatforming estimate was twenty-eight to forty weeks of engineering, with six weeks of parallel operation. The acquirer's due diligence team classified the infrastructure as "high proprietary coupling." The acquirer understood that the coupling was not a defect — DynamoDB had been the right choice for a team that wanted to launch quickly without managing database infrastructure — but the coupling depth had not been apparent from the initial technical conversations and required the three-week assessment to quantify. The deal closed, but the valuation was adjusted to account for the replatforming cost as a post-acquisition investment. The adjustment was based on estimates that the CTO's team produced in three weeks under acquisition timeline pressure. If the proprietary service inventory had been maintained as a section of the infrastructure decision record, it would have taken three hours to answer the three questions, and the adjustment negotiation would have been based on documented and reasoned estimates rather than an emergency assessment.

The original DynamoDB choice had been made in an AI chat session in the first month of the company. The engineer had asked how to build a scalable backend database. The session recommended DynamoDB for a startup that didn't want to manage database servers, explained the single-table design pattern, and produced a first schema for the primary entities. The session did not ask: what is your team's familiarity with DynamoDB access pattern modeling versus relational modeling? What is the vendor dependency surface you are willing to accept? How important is schema flexibility versus query flexibility to your product model? These questions would have produced a different — or at least more informed — decision. The questions were not asked, the reasoning was not captured, and the infrastructure decision record did not exist.

The compute model dead end: a Kubernetes cluster that no one can explain and no one can leave

A 25-person B2C SaaS company had migrated from a single EC2 instance to containers two years before the incident. The migration was driven by a senior engineer who had joined from a company with a mature Kubernetes infrastructure and believed containers would allow the team to scale more effectively. The decision to use Kubernetes specifically — rather than ECS, ECS Fargate, or AWS App Runner, which had recently launched — was made in a week of implementation work and never documented as an architectural choice. The Kubernetes cluster on EKS was set up, the fourteen services were containerized and deployed to the cluster, and the platform moved to production.

Two years later, a new engineering lead ran a cost review as part of a board presentation. The infrastructure breakdown showed the EKS cluster as the largest single cost line: eight m5.xlarge worker nodes at $90/month each in EC2 costs, plus EKS control plane fees, NAT Gateway data processing fees for outbound traffic, and load balancer costs — approximately $1,200/month in direct infrastructure cost. Adding the two days per week of platform engineer time required to maintain the cluster (Kubernetes version upgrades on a 14-month cycle, node group rotation during version upgrades, cert-manager and ingress-nginx configuration management, pod security policy migration, monitoring stack maintenance), the fully-loaded cluster cost was approximately $6,500/month.

The engineering lead proposed migrating the fourteen services to AWS App Runner, which would handle container orchestration, scaling, and load balancing as a managed service. At the team's current traffic levels, App Runner would cost approximately $900 to $1,400/month — a savings of $5,000/month in infrastructure cost and the elimination of two days per week of platform engineering overhead. The proposal went to the engineering team for evaluation.

The evaluation stalled immediately because no one could establish the baseline for the analysis. Why was Kubernetes chosen over ECS or App Runner? What requirements did Kubernetes satisfy that the alternatives did not? Had those requirements changed in two years? The Kubernetes cluster was running fourteen services: three used Kubernetes-specific features (a CronJob for nightly data exports, a Horizontal Pod Autoscaler with custom metrics based on a Prometheus queue depth metric, and Istio traffic policies for canary deployments to two services). The other eleven were stateless HTTP services with no Kubernetes-specific configuration beyond their deployment manifests. The migration scope for App Runner was ambiguous: the three services with Kubernetes-specific feature dependencies would require rearchitecting their features before migration, at unknown cost. The eleven services were straightforward to migrate. But without documentation of why Kubernetes had been chosen, the evaluation team could not answer whether the three Kubernetes-specific features were deliberate architectural choices or incidental adoption of available tooling.

The HPA with custom Prometheus metrics had been added because an engineer had read a blog post about queue-depth-based autoscaling and implemented it for one service; it was then applied to two others by a different engineer who saw the pattern in the codebase and assumed it was intentional. The Istio canary deployment had been set up for a major API version migration and had not been removed afterward. The CronJob existed because the application's cron scheduler had been running as a subprocess of the main API server, and someone had moved it to a CronJob during a refactor. None of these were deliberate architectural decisions — they were incidental adoptions of Kubernetes capabilities that happened to be available.

The engineering lead spent three weeks assembling this history through code archaeology and conversations with engineers who had been at the company for more than eighteen months. The migration proposal was eventually approved, but the three-week investigation cost more in engineering time than the first two months of App Runner savings would recover. If the original compute model decision had been documented — the choice of Kubernetes over ECS with the reasoning, the constraints Kubernetes was meant to address, and a clause noting that each Kubernetes-specific feature adoption should be logged as it occurs — the evaluation would have taken a day, not three weeks.

Three structural properties that are set at infrastructure decision time

The cloud provider adoption depth and the proprietary dependency surface

Every cloud provider offers two categories of services: commodity services that implement standard protocols and have portable equivalents on other platforms, and proprietary managed services that implement the provider's specific data model or API design. The distinction matters because proprietary service adoption is gradual and self-reinforcing. A team that adopts DynamoDB in year one because it eliminates database administration has a single proprietary service dependency. In year two, the same team adds SQS because the DynamoDB streams handler needs a reliable message queue; adds EventBridge because the monolith is being broken into services and an event router reduces coupling; adds Cognito because it integrates natively with the existing IAM roles. Each addition is a local optimization that makes sense in isolation. The cumulative effect is a proprietary dependency surface that grows with each managed service adoption, without any team member having made a deliberate decision about the acceptable coupling depth.

The difference between portable and proprietary usage of the same cloud provider is large. A team running PostgreSQL on RDS (AWS's managed relational database service) is using a proprietary infrastructure service but a portable database engine — the PostgreSQL wire protocol, SQL syntax, and extension ecosystem are identical to self-hosted PostgreSQL or to PostgreSQL on GCP Cloud SQL. Migrating to a different provider requires changing a connection string and possibly reconfiguring a few provider-specific RDS parameters, but the application code is unchanged. A team running DynamoDB with single-table design is using a proprietary database engine whose access pattern model — partition keys, sort keys, Global Secondary Indexes — has no direct equivalent in relational or document databases. Migrating requires redesigning the data model, not just changing a connection string. This distinction — portable service implementation vs. proprietary data model — determines the practical migration cost, but it is invisible in the infrastructure dashboard that shows both services as "AWS managed database."

The infrastructure decision record's proprietary service inventory is not an argument against using proprietary services. Proprietary managed services eliminate operational overhead in ways that are genuinely valuable, especially for small teams. The argument is that the coupling should be a documented decision, not an accumulated default. The decision record should enumerate each proprietary service, describe what it trades portability for, and estimate the migration effort class: configuration change (hours), code migration (weeks), or data model redesign (months). This inventory answers the "how long would a migration take?" question that acquisition due diligence will eventually ask, and it makes the team aware of the cumulative coupling before it is discovered under time pressure. This connects to the database vendor decision record: the database choice is the single highest-coupling infrastructure decision for most applications, and the infrastructure decision record's proprietary service inventory should reference the database vendor ADR for the full reasoning behind the database selection. The database vendor ADR covers the data model and query access pattern trade-offs; the infrastructure decision record's proprietary service section covers the database choice's contribution to the overall coupling surface alongside the other proprietary services.

Multi-cloud as a risk mitigation is a common proposal and a rarely implemented one for good reason: the operational overhead of maintaining deployment pipelines, infrastructure-as-code, and testing across multiple cloud providers is substantial, and for most teams the risk of any single cloud provider shutting down or making a pricing change that requires migration is low enough that the mitigation cost exceeds the risk cost. The more practical multi-cloud strategy is portable-first service selection: choosing managed services that implement standard protocols (managed PostgreSQL over DynamoDB for relational data; managed Kafka over SQS for message queuing; OIDC-compliant authentication over Cognito) wherever the operational overhead difference is acceptable. Portable-first reduces the proprietary dependency surface incrementally, making future migrations lower cost without requiring active multi-cloud maintenance. The decision record should document the team's position on this trade-off explicitly — not because the position needs to be the same for every team, but because the position needs to be the same for every engineer making service selections, and the only way to achieve that consistency is to write it down.

The account and network topology and the blast radius model

The cloud account structure determines the blast radius of the worst-case action an adversary or an operator can take within the infrastructure. In a single AWS account, an IAM user or role with sufficient permissions can read, modify, or delete resources across all services and all environments. A leaked access key for a CI/CD pipeline that has broad permissions to deploy to production can potentially be used to read production database credentials, exfiltrate customer data, or destroy infrastructure. A mistaken Terraform destroy command targeting a development environment but run in a production AWS profile destroys production infrastructure. These failure modes are not theoretical — they occur regularly at teams with single-account structures and insufficiently scoped IAM permissions.

Multi-account isolation addresses blast radius at the account boundary. When production and development are in separate AWS accounts with no cross-account IAM roles that allow development credentials to reach production resources, a compromised development account cannot access production data. Cross-account access — for CI/CD pipelines that deploy to production, for logging aggregation across accounts, for security monitoring — is explicit and auditable: it requires a cross-account IAM role with a documented trust relationship, visible in the account's IAM configuration. Implicit permissions that span environments disappear when the environments are in separate accounts.

AWS Organizations provides the governance layer for multi-account structures. Service Control Policies (SCPs) — Organization-level IAM policies that apply to all IAM actions in member accounts — allow the management account to enforce constraints that individual account administrators cannot override: preventing production data from being stored outside approved regions (a GDPR compliance requirement), requiring that all IAM roles in the production account have a specific tag before creation (an audit requirement), or preventing the deletion of CloudTrail logging (a security baseline). SCPs are the mechanism that makes multi-account structures auditable rather than just segmented. The account structure decision record must document which SCPs are applied at the Organization level and what each prevents, because SCPs that deny actions without documentation produce confusing error messages that new engineers cannot diagnose without context.

The network topology — VPC design, subnet structure, and inter-service communication paths — determines the security posture and the data transfer cost model. The standard three-tier subnet model (public subnets for internet-facing load balancers, private subnets for application services, isolated subnets for databases with no internet-facing route) is the baseline for most production architectures. The critical undocumented decision in this model is the NAT Gateway configuration: application services in private subnets that need to make outbound internet calls (to third-party APIs, to package repositories, to SaaS tools) must route through a NAT Gateway. AWS charges $0.045/GB for data processed through a NAT Gateway, which is invisible at low traffic volumes and visible at high volumes. A service that streams 10 GB/hour of external API data through a NAT Gateway costs $21,000/month in data processing fees alone. The NAT Gateway data processing cost is not a line item in most teams' cost reviews until it is already significant because the cost category appears as "NAT Gateway" in the AWS bill, not as "outbound traffic from your-service-name." The network topology decision record must document which services route through the NAT Gateway and the expected outbound data volume for each, so that services with high outbound data volumes can be designed with the NAT Gateway cost in their unit economics from the start. This connects to the data pipeline decision record: data pipelines that pull from external sources in private subnets route all of their source data through the NAT Gateway unless the pipeline is explicitly placed in a public subnet or the data source offers a VPC endpoint — an architectural detail that determines whether the pipeline's monthly cost is $500 or $15,000.

VPC peering and Transit Gateway topology decisions become load-bearing when services are split across multiple VPCs — either by environment (a production VPC and a staging VPC that share some services) or by security boundary (a PCI-scoped VPC that contains payment processing services isolated from the main application VPC). VPC peering creates a direct network connection between two VPCs; Transit Gateway creates a hub-and-spoke routing model that can connect many VPCs through a central hub. The choice between them is not documented as a decision in most teams' architectures, but it determines the operational overhead of adding new VPC connections (adding a new VPC to a Transit Gateway is a configuration operation; adding it to an existing peering mesh requires adding a peering connection to every existing VPC it needs to reach), the routing complexity (peering is peer-to-peer with explicit route table entries; Transit Gateway centralizes routing with propagated route tables), and the data transfer cost (Transit Gateway attachment and data processing fees add approximately $0.02/GB to inter-VPC traffic; VPC peering within a region has no data transfer fee for traffic that stays in the same Availability Zone, and the inter-AZ data transfer cost for peered traffic at $0.01/GB is lower than Transit Gateway for low-throughput connections).

The compute abstraction model and the operational ceiling

The compute abstraction model — the level of infrastructure management that the team is responsible for — determines the operational overhead per service, the constraints on what workloads can run on the platform, and the minimum engineering headcount needed to maintain the infrastructure. These three properties interact: a higher-abstraction compute model (serverless) has lower operational overhead per service and fewer constraints on team size, but imposes constraints on workload behavior (execution duration, concurrency model, cold start latency). A lower-abstraction model (EC2 with self-managed configuration) has higher operational overhead per service and no workload behavior constraints, but requires engineering investment in infrastructure management that scales with the number of services and the velocity of changes.

Virtual machines (EC2, Compute Engine, Azure VMs) are the lowest abstraction level in common use: the team is responsible for the operating system, security patching, disk management, instance lifecycle, and any service-level software. The operational overhead for a single VM-based service includes patching the OS, monitoring disk utilization, managing the application startup and restart lifecycle (systemd, supervisord, or equivalent), and handling instance replacement when the underlying host fails. For a team running three services, this overhead is manageable. For a team running thirty services, it becomes a significant portion of engineering capacity. VM-based compute also has the slowest horizontal scaling path: autoscaling a VM-based service requires launching new instances (30–90 seconds for cloud provider provisioning), booting the OS (15–60 seconds), and starting the application (seconds for a Go binary, 30–90 seconds for a JVM application). The total horizontal scaling latency ranges from 1 to 5 minutes, which determines the minimum burst headroom required to absorb traffic spikes without degradation.

Container-based compute on managed orchestration (ECS Fargate, Cloud Run, App Runner) eliminates the OS management overhead by providing a managed execution environment for container images. The team is responsible for the container image and its dependencies, but not for the underlying OS lifecycle. Horizontal scaling latency drops to 15–60 seconds (container image pull plus application startup), enabling a smaller headroom buffer. Container-based compute on self-managed Kubernetes adds back an operational layer: the Kubernetes control plane, node groups, and cluster add-ons (ingress controller, cert-manager, metrics server, CNI plugin) require maintenance, version management, and operational expertise. The team running self-managed Kubernetes must budget for Kubernetes control plane upgrades (14-month minor version support window), node group rotation during version upgrades, and the ongoing management of cluster-level resources. This overhead is fixed — it applies regardless of how many applications run on the cluster — which makes Kubernetes relatively more cost-effective at high service counts and less cost-effective at low service counts. A five-service application running on EKS with a cluster maintained by two engineers spending 20% of their time on cluster operations is paying a high overhead per service; the same cluster infrastructure running fifty services at the same maintenance overhead is much more efficient per service.

Serverless compute (Lambda, Cloud Functions, Cloud Run) eliminates the operational infrastructure layer entirely: the team writes function code and the cloud provider handles execution environment provisioning, scaling, and maintenance. The trade-offs are workload constraints: Lambda functions have a 15-minute maximum execution duration (a constraint that rules out long-running computations, streaming jobs, and stateful connection management), a cold start latency for new execution environments that ranges from 100ms for small Node.js functions to several seconds for large JVM functions (a constraint for latency-sensitive workloads where the cold start affects response time percentiles), and a concurrency model where each function invocation runs in an isolated execution context (a constraint that prevents in-memory shared state, connection pools shared across requests, and background goroutines or threads that persist between invocations). The concurrency model constraint is the most frequently underestimated: a team that migrates a service with a long-lived database connection pool to Lambda discovers that each Lambda invocation opens a new database connection and closes it at the end of the invocation (or retains it for the lifetime of the warm execution environment, which is unpredictable). At 1000 concurrent Lambda invocations, the service may attempt to open 1000 simultaneous database connections — a concurrency level that exceeds most database connection limits and produces connection exhaustion errors under load. This connects to the capacity planning decision record: capacity planning for serverless compute requires planning for concurrency (the number of simultaneous function executions at peak) rather than instance count, and the database connection model must be compatible with the concurrency level the function tier can generate — typically requiring a connection proxy (RDS Proxy, PgBouncer) between Lambda and the database. This constraint is not a defect; it is a design property that must be documented in the infrastructure decision record so that every team building on the serverless platform knows that they cannot use connection-pooling patterns that assume a small fixed connection count.

This connects to the background job infrastructure decision record: the compute abstraction model determines which job infrastructure options are available. A Lambda-based platform can run scheduled functions (Lambda with EventBridge rules for cron triggers) and event-driven processing (Lambda with SQS or Kinesis triggers), but cannot run long-duration jobs, jobs requiring shared file system access, or jobs that benefit from multi-threaded parallelism within a single execution context. A Kubernetes-based platform can run CronJobs, Jobs with parallelism, and long-duration batch processing, but each job type adds orchestration complexity and requires explicit resource requests and limits. The background job infrastructure decision record references the compute abstraction model as its constraint layer; the infrastructure decision record must document the compute abstraction model first for that reference to be meaningful.

Five sections the infrastructure architecture decision record should address

1. Cloud provider selection and proprietary service inventory

Document the primary cloud provider and the reasoning behind the selection at the time the selection was made — not the reasoning that makes the selection look inevitable in retrospect, but the actual reasoning: team familiarity with the platform, startup credit availability, a required service that was only available on one provider, a specific region availability requirement. The reasoning matters because it reveals the stability of the choice: a selection based on startup credits is worth revisiting when the credits expire; a selection based on team familiarity is worth revisiting when the team composition changes; a selection based on a specific required service is worth revisiting when an equivalent service becomes available on other platforms.

Document the proprietary service inventory as a table: service name, the proprietary feature or data model aspect that creates the coupling, the portable alternative that was rejected or not considered, and the migration effort class (configuration change / code migration / data model redesign). Update this table each time a new managed service is adopted — not annually in a review, but at the time of adoption, when the person making the service selection has the reasoning fresh and can document the trade-off accurately. A proprietary service inventory that is maintained at adoption time takes approximately 15 minutes per service addition to keep current; a proprietary service inventory assembled retrospectively from code archaeology and conversation reconstruction takes weeks and is less accurate.

Document the position on portable-first versus best-of-breed service selection: whether the team deliberately prefers services with portable equivalents (accepting some operational overhead or feature gaps in exchange for lower coupling), prefers the most capable managed service regardless of coupling (accepting higher migration cost in exchange for operational simplification), or evaluates on a case-by-case basis (the most common position, which produces inconsistent coupling depth across the service inventory and requires documenting the individual trade-off for each service). None of these positions is wrong; all three produce different coupling surfaces over time, and the position must be known to future service adopters so that their individual choices accumulate toward a consistent architecture rather than toward an inconsistent one by default. This connects to the decisions never written down pattern: service selection decisions are made in AI chat sessions that recommend the best tool for the immediate problem — which is almost always the most capable managed service — without asking whether the team's preferred coupling depth has been considered.

2. Account and organizational structure and blast radius boundary

Document the account structure at the time of the decision: single account, environment-separated accounts (production, staging, development as separate accounts), or fully isolated accounts (each microservice domain in a separate account, or each regulated data type in a separate account). For each structure, document the blast radius model explicitly: given the current IAM permission grants and cross-account relationships, what is the maximum scope of impact of a fully compromised account or a mistaken destructive operation?

For multi-account structures, document the AWS Organizations configuration: the organizational unit hierarchy, which Service Control Policies are applied at each OU level and what each prevents, the cross-account IAM roles that exist and their trust policies, and the account vending process for creating new accounts with the required baseline configuration. SCP documentation is the most commonly omitted element of multi-account decision records: engineers who encounter an IAM denied error in a multi-account environment often cannot diagnose whether the denial comes from the resource's IAM policy, the principal's IAM policy, or an SCP applied at the Organizations level. Without the SCP inventory in the decision record, diagnosing SCP-related permission issues requires navigating the Organizations console to find the relevant SCP, which is a longer investigation path than it should be. Document the SCP by name, the actions it denies, and the rationale — "Deny CloudTrail StopLogging and DeleteTrail in all member accounts — prevents adversaries with account-level access from disabling audit logging before exfiltrating data" is a complete SCP entry that lets an engineer understand the SCP at a glance.

Document the migration path from the current account structure to the next level of isolation: if the team is on a single account and intends to migrate to environment-separated accounts, document the estimated effort and the trigger condition that would initiate the migration. If the team is on environment-separated accounts and is evaluating fully isolated accounts, document what specific blast radius concern the additional isolation would address. This prevents the account structure from becoming a permanent default that is never reassessed — the single account that was appropriate at 5 engineers may not be appropriate at 50 engineers with a compliance audit underway. This connects to the security and compliance decision record: the account structure's blast radius model should be derived from the threat model — the specific adversary capabilities and assets to protect that drive the security architecture. A team whose threat model includes sophisticated adversaries with targeted access (financial services, healthcare) requires stronger account isolation than a team whose threat model is primarily misconfiguration and opportunistic credential compromise.

3. Network topology and data transfer cost model

Document the VPC design: the number of VPCs and the rationale for each VPC boundary (a single VPC for the entire application is simpler to operate and has no inter-VPC data transfer costs; multiple VPCs enable stronger security isolation at the cost of peering or Transit Gateway configuration). For each VPC, document the CIDR allocation and the rationale — the CIDR block determines the maximum number of resources that can be addressed in the VPC and constrains future peering arrangements (two VPCs with overlapping CIDRs cannot be directly peered). Document the subnet structure: which subnet tier each category of resource is deployed in (internet-facing load balancers in public subnets, application services in private subnets, databases in isolated subnets), and which subnets have NAT Gateway routes for outbound internet access.

Document the NAT Gateway configuration and the data transfer cost model. For each service or service category that routes outbound internet traffic through the NAT Gateway, document the expected outbound data volume per month and the resulting NAT Gateway processing cost at $0.045/GB. This exercise frequently reveals that the team has never estimated NAT Gateway costs by service — the costs appear as a single "NAT Gateway" line in the AWS bill — and that one or two services account for the majority of the outbound data volume. Services that receive large external datasets (data ingestion pipelines, third-party API integrations that transfer large payloads), services that perform outbound replication (database backup exports to S3 via NAT rather than through S3 VPC Endpoint), and services that use self-hosted package registries or container registries accessible only via the internet generate NAT Gateway costs that are often larger than the compute cost for the same service. Document whether S3 VPC Endpoints, DynamoDB VPC Endpoints, and other AWS service VPC Endpoints are deployed — these route traffic to AWS services through the VPC network backbone rather than through the NAT Gateway, eliminating NAT Gateway processing fees for AWS service traffic and often reducing latency.

Document the inter-AZ data transfer strategy. AWS charges $0.01/GB for data transferred between Availability Zones within a region. For most services this is a minor cost; for services that transfer large data volumes between components in different AZs — a replication stream, a distributed cache with cross-AZ node communication, a microservice pair that transfers large request bodies — the inter-AZ cost can be significant. The architectural choice of whether to co-locate service instances within a single AZ for cost (lower inter-AZ transfer cost, higher blast radius on AZ failure) or distribute across AZs for resilience (higher inter-AZ transfer cost, lower blast radius on AZ failure) is a documented trade-off, not a configuration detail. It connects to the disaster recovery decision record: the AZ distribution strategy for each service tier is part of the DR architecture, and the cost of the distribution (inter-AZ transfer fees, additional load balancer targets, replication overhead) is part of the DR cost model.

4. Compute abstraction model selection and workload constraints

Document the compute abstraction model for each major workload category, not as a single infrastructure-wide choice. Most production architectures use different abstraction levels for different workload types: serverless for event-driven, latency-tolerant, short-duration handlers; managed containers for long-running stateless API services; managed VMs or bare metal for stateful, IO-intensive, or latency-critical services. The decision record should enumerate the workload categories and the compute abstraction chosen for each, with the reasoning that justifies the choice for that specific category.

For each compute abstraction, document the constraints it imposes on workloads deployed to it. Lambda function constraints: maximum execution duration (15 minutes), maximum deployment package size (50 MB zipped), maximum memory allocation (10 GB), cold start latency range for the team's typical runtimes and package sizes, concurrency model (each invocation is isolated — no shared in-memory state, no persistent background processes, no connection pool sharing across invocations). Container-based compute constraints: resource requests and limits that must be set for each container (CPU and memory), horizontal scaling latency (image pull time plus application startup time for cold containers), the maximum number of simultaneous containers per node based on node capacity and resource requests, and any cluster-level resource quotas that cap scaling. VM-based compute constraints: scaling latency (AMI boot time plus application startup), OS patching cadence (the maintenance window that requires instance replacement or in-place patching), and the instance family selection rationale (why m6i over r6i, or why Graviton over x86).

Document the operational overhead per abstraction tier: the approximate engineering time per week required to maintain the compute infrastructure at the team's current service count and deployment velocity. For managed services (Fargate, App Runner, Lambda), this is typically near zero for operational maintenance (the provider handles patching and scaling infrastructure), with cost primarily in configuration management and capacity planning. For self-managed Kubernetes, document the maintenance tasks and their frequency: Kubernetes version upgrade frequency and duration, node group rotation procedures, cluster add-on update cadence, and the on-call load from cluster-level incidents (node not-ready, pod eviction storms, PVC attachment failures). For VM-based compute, document the OS patching schedule and procedure, the AMI refresh cadence, and the monitoring and replacement procedure for unhealthy instances. Making the operational overhead explicit in the decision record is the prerequisite for the operational scaling analysis: as service count grows, does the operational overhead grow linearly with services (VM-based compute without infrastructure-as-code) or remain approximately fixed (fully managed compute services)?

5. Provider coupling review cadence and replatforming cost estimate

Document the cadence at which the proprietary service inventory and the compute abstraction model are reviewed for continued appropriateness. Annual is sufficient for most teams; the review is triggered by the questions: Has the team's size or operational maturity changed in a way that makes a different compute abstraction more appropriate? Have portable equivalents appeared for proprietary services we currently use? Has the cloud provider made pricing or availability changes that affect our coupling assessment? Has an acquisition conversation, a compliance audit, or a competitive benchmark made portability a higher priority than it was at the last review?

Document a rough replatforming cost estimate, updated annually. The estimate does not need to be precise — it needs to be directionally correct to inform business decisions. For each major infrastructure component, classify the migration effort: A (days), B (weeks), C (months), D (quarters or more). The component classifications produce a total migration effort estimate that can be used in acquisition due diligence, cloud provider negotiation (a team that can demonstrate a credible migration path has more leverage in enterprise discount negotiations), or internal investment planning (migrating to a lower-coupling architecture before a financing event reduces the coupling risk that due diligence will identify). The estimate should be specific enough to be useful: "migrating DynamoDB to PostgreSQL requires schema redesign for three tables with complex GSI access patterns (estimated 16 weeks with 2 senior engineers) plus a data migration with a 4-week write-through period (estimated 2 weeks of engineering overhead)" is a useful estimate that informs decisions. "High coupling" is not a useful estimate.

Document the trigger conditions that would initiate a replatforming evaluation outside the annual review cycle: a cloud provider pricing announcement that increases costs above a threshold, a provider deprecation notice for a service in the proprietary inventory, an acquisition or merger conversation, a compliance audit that requires portability evidence, or an internal decision to pursue a multi-cloud or hybrid cloud architecture for resilience. These trigger conditions prevent the replatforming cost estimate from being a document that is reviewed annually and never acted on — they define the specific events that make the replatforming cost estimate decision-relevant. The WhyChose extractor can surface the original infrastructure architecture discussions from ChatGPT or Claude conversation history: the sessions where the team debated cloud providers, the conversations where specific managed services were selected, the incident retrospective where someone said "we should document our infrastructure choices better" — all of these contain the reasoning that should have been captured in the infrastructure decision record but was not. The new-CTO onboarding problem is most acute with infrastructure: the questions "why are we on AWS?", "why Kubernetes?", "why DynamoDB for the primary store?" are unanswerable from Terraform state alone. They require the reasoning that was present in conversations at the time the decisions were made and has since been lost. The ADR template provides the structure: decision (the specific choice made), rationale (the factors that drove the choice and the alternatives considered), consequences (the constraints the choice imposes on future decisions), and review date (when this decision should be revisited). An infrastructure decision record structured this way is not a compliance artifact — it is the reference that makes every subsequent infrastructure decision faster and more coherent, because the foundational constraints are written down rather than reconstructed from memory each time.

Further reading