IaC in Isolated AWS Environments: Terraform, CDK, and the High Side Problem

Premise Link to heading

Amazon Dedicated Clouds (ADC) are isolated AWS regions designed for government customers with strict security and compliance requirements. Unlike standard commercial AWS regions, ADC regions operate on separate partitions — physical separations of AWS infrastructure that have their own independent service endpoints, APIs, and service availability. Think of a partition as a completely separate, physically distinct instance of AWS that happens to share the same underlying technology. AWS currently has 6 ADC regions, with 2 residing on different partitions. Most recently, AWS released a new ADC Secret West Region on the secret partition. If you want to learn more about what these environments look like, AWS has a public overview of the Top Secret cloud worth reading.

Deploying infrastructure into these environments comes with a unique challenge: getting your Terraform code to the “high side” — the classified or isolated environment that sits behind strict data transfer controls — without running afoul of the approval processes that govern what can cross that boundary. Every file that moves to the high side needs to be reviewed, validated, and approved. The less you have to transfer, the better.

The central challenge then is this: can we deploy infrastructure changes to the high side without physically transferring the full Terraform codebase across the boundary? This article explores whether Terraform’s plan file could be a solution to that problem, why it ultimately falls short, and how AWS CDK and CloudFormation solve it in a way Terraform simply cannot.


The Terraform Concept Link to heading

Terraform is an open source IaC tool that lets you define cloud infrastructure in HCL (HashiCorp Configuration Language) and deploy it across providers like AWS, Azure, and GCP. It’s the dominant IaC tool in most commercial environments.

Terraform has the concept of creating a plan file using the terraform plan -out <TFPLAN> command. This command is often overlooked because most people skip straight to terraform apply and call it a day. But the real power of generating a tfplan file is that you can later run terraform apply <TFPLAN> against it and know exactly what infrastructure will be built — no surprises. That file can be reviewed by a human, validated by a policy-as-code mechanism, or handed off through an approval gate before anything is deployed.

What makes it even more interesting is that the tfplan file can be consumed by a CI/CD pipeline and applied using a Terraform container without the underlying infrastructure code being present. You still need the Terraform backend and provider configuration, but the actual resource logic doesn’t need to travel with it. That’s a meaningful distinction when you’re operating in environments where what can cross the boundary is tightly controlled.

For environments like ADC regions where every file crossing the boundary requires approval, the idea of transferring a single plan file instead of an entire codebase is immediately appealing.


The Terraform Idea — Plan File Portability Link to heading

This brings us to the concept of plan file portability — the ability to generate a tfplan file in one environment and apply it in another, replicating infrastructure without needing to transfer the full codebase. In theory, this model is particularly well-suited for isolated environments, where approval processes are more stringent and the number of artifacts that can be passed across boundaries is limited. Shipping a single validated plan file is a much smaller attack surface than shipping an entire Terraform codebase.

However, there’s a fundamental complication when crossing partition boundaries. Each isolated partition has its own service endpoints and its own service availability. A region on the commercial partition reaches AWS services via amazonaws.com endpoints, while a region on the secret partition operates on entirely separate, isolated endpoints. Beyond that, not every AWS service exists in every partition — something available in a commercial region may not exist in an ADC region at all. This means a tfplan file generated in one partition carries partition-specific assumptions baked directly into it: endpoint URLs, resource ARNs, and the services being provisioned may have no valid equivalent on the other side.

That’s what makes this idea compelling in theory, and complicated in practice.


Why Terraform Falls Short Link to heading

The idea is appealing on the surface. Terraform allows you to generate a plan file using the following command:

terraform plan -out deployment.tfplan

That plan file captures exactly what infrastructure will be built. You can then apply it without the underlying code:

terraform apply deployment.tfplan

In theory, this means you could generate the plan file on the low side, transfer just that single artifact to the high side, and apply it via a Terraform container without ever moving the codebase. Consider a mock example where you’re deploying an S3 bucket and an EC2 instance in a commercial region:

# On the low side — generate the plan
terraform plan -out deployment.tfplan

# Transfer deployment.tfplan to the high side

# On the high side — apply the plan
terraform apply deployment.tfplan

Clean, simple, and minimal. Only one file crosses the boundary. So why doesn’t this work?

Problem 1 — The plan file is state dependent

When Terraform generates a plan, it evaluates your desired resources against the current tfstate file. That state file lives in the isolated partition and will not be identical to the one on the low side. Applying a plan generated against a different state risks duplicating resources, destroying existing ones, or both. In environments where those resources are operationally critical, that’s not a risk worth taking.

Problem 2 — The plan file is partition specific

A plan file generated in a commercial region carries commercial partition assumptions baked directly into it — amazonaws.com endpoints, commercial region AMI IDs, and services that may not exist in the isolated partition at all. When that plan hits the high side, those assumptions don’t translate. The endpoints are wrong, the AMIs don’t exist, and services referenced in the plan may not be available in the isolated partition.

Problem 3 — The plan file alone is not enough

Even if you solve the state and partition issues, the tfplan file cannot stand on its own on the high side. Terraform still needs the backend configuration to locate the state file and the provider configuration to interact with AWS. These aren’t the full infrastructure codebase, but they are additional artifacts that have to cross the boundary — which undermines the core appeal of the portability idea. The promise was one file crosses the boundary. The reality is several.

Problem 4 — The .terraform directory has to cross the boundary too

This is where airgapped environments make Terraform particularly painful. In a standard environment, running terraform init pulls providers and modules directly from the Terraform registry. In an airgapped isolated environment there is no internet access on the high side, which means that can’t happen. Every provider and module dependency has to be manually transferred across the boundary as part of the .terraform directory before any Terraform commands will even run. When you add it all up, the full list of artifacts that need to cross the boundary with Terraform looks like this:

  • The backend and provider configuration
  • The .terraform directory containing all downloaded providers and modules
  • The tfstate file for reconciliation

That’s a significant amount of artifacts going through an approval gate — and it compounds every time your providers or modules are updated. Compare that to a single CloudFormation YAML file and the gap becomes hard to ignore.


The CDK Concept Link to heading

AWS CDK (Cloud Development Kit) is an open source framework that lets you define cloud infrastructure using real programming languages like TypeScript, Python, or Java. Instead of writing HCL like Terraform or raw YAML like CloudFormation, you write actual code — with all the logic, conditionals, and abstractions that come with a real programming language.

The key command in the CDK workflow is cdk synth. When you run it, CDK synthesizes your code into CloudFormation YAML templates — the actual artifact that gets deployed. Think of it as a compilation step. Your TypeScript code goes in, and CloudFormation YAML comes out.

That distinction matters more than it might seem. The YAML that gets generated is static, human readable, and self-contained. It doesn’t need the CDK code to deploy — CloudFormation can pick it up and run with it directly. That separation of the code from the deployable artifact is exactly what makes CDK compelling in the context of isolated environments.


The CDK Workflow Link to heading

When deploying to ADC isolated regions, the workflow looks something like this at a high level:

Step 1 — Write CDK in TypeScript

Infrastructure is defined as TypeScript code. This is where the partition-aware logic lives — more on that in the next section.

Step 2 — Build and Synthesize

An internal build system triggers the CDK synth process, compiling the TypeScript code into partition-specific CloudFormation YAML files. Different YAML files are generated for different partitions based on the conditionals in the code.

Step 3 — Transfer the Artifacts

An automated pipeline passes the generated YAML files through a one-way transfer mechanism into the isolated environment. Only the artifacts cross the boundary — the CDK code stays on the low side.

Step 4 — Deploy via CloudFormation

On the high side, CloudFormation picks up the YAML file for the respective region and deploys the infrastructure. No code, no state file reconciliation, no manual intervention.

That’s it. Four steps, one artifact crosses the boundary, and the deployment is clean and auditable on the other side.


Why CDK Works Link to heading

This workflow solves the core problems that made Terraform’s plan file approach unworkable in isolated environments.

Partition-aware logic at synthesis time

Because CDK uses a real programming language, you can write conditional logic that handles partition differences before the YAML is ever generated. If a service isn’t available in a particular partition, or if the endpoint is different, you handle that in code:

if (partition === 'aws-secret') {
  // use isolated partition endpoint
} else {
  // use standard commercial endpoint
}

By the time the YAML is synthesized, those decisions are already resolved. The artifact that crosses the boundary to the high side already knows exactly what endpoints and services to use for that partition. There’s no ambiguity on the other side.

No external state file

CloudFormation manages its own state natively within the partition. There’s no external tfstate file to reconcile, no drift between environments, and no risk of duplicating or destroying resources because the plan was generated against a different state. CloudFormation knows what it deployed because it deployed it.

A single auditable artifact

The CloudFormation YAML that crosses the boundary is human readable and reviewable. Approval gates in isolated environments can inspect exactly what is being deployed before it crosses. That’s a meaningful advantage over a binary tfplan file, which requires tooling to interpret and is harder to audit at a glance.

Caveats — CDK Is Not a Silver Bullet

CDK solves the high side deployment problem more cleanly than Terraform, but it comes with its own gotchas worth understanding before you commit to this approach.

Environment drift on the high side

Because CloudFormation synthesizes and deploys without a preliminary state check the way Terraform does, the high side environment can drift over time. If a deployment fails partway through — and partial failures do happen — you’re left with an environment in an inconsistent state that requires manual intervention to resolve. Terraform’s explicit plan-then-apply cycle, for all its limitations in this context, at least gives you a clear picture of what will change before anything touches the environment.

Testing partition differences is hard

The conditional logic in your CDK code that handles endpoint and service differences across partitions can only be validated in one of two ways: either you have complete knowledge of every endpoint and service difference between partitions upfront — which is difficult to maintain — or you find out through trial and error on the high side. Neither is ideal, and both require a level of operational discipline that shouldn’t be underestimated.

CDK is less elegant than it first appears, but it is more practical. That distinction matters when you’re operating in environments where practical beats elegant every time.


Terraform vs CDK — An Honest Comparison Link to heading

Neither of these tools is universally better than the other. The right choice depends heavily on the environment you’re operating in.

Terraform

  • ✅ Easier to learn and write — HCL is approachable for most engineers
  • ✅ Massive documentation and community support
  • ✅ Excellent for standard multi-cloud and commercial AWS deployments
  • ✅ Wide ecosystem of modules and providers
  • ❌ Plan file portability breaks down at partition boundaries
  • ❌ External state management becomes a liability in isolated environments
  • ❌ Limited ability to handle partition-specific logic elegantly
  • ❌ The .terraform directory containing all providers and modules must be manually transferred across the boundary in airgapped environments — and updated every time dependencies change

AWS CDK

  • ✅ Real programming language enables partition-aware conditional logic
  • ✅ Synthesized YAML artifacts are portable, auditable, and self-contained
  • ✅ CloudFormation manages state natively within the partition
  • ✅ Only a single YAML file needs to cross the boundary — no providers, no modules, no state files
  • ✅ Solves the high side deployment problem cleanly
  • ❌ Steeper learning curve — requires proficiency in TypeScript, Python, or another supported language
  • ❌ Smaller community and thinner documentation compared to Terraform
  • ❌ Overkill for straightforward deployments that don’t require partition-aware logic
  • ❌ No preliminary state check means the high side environment can drift, and partial deployment failures require manual remediation
  • ❌ Partition differences in endpoints and service availability can only be validated through full upfront knowledge or trial and error on the high side

The honest conclusion: Terraform is the better tool for most environments. CDK is the better tool for this specific problem.


Conclusion Link to heading

The high side deployment problem is one of the more underappreciated challenges in working with isolated AWS environments. Getting infrastructure changes across the boundary cleanly — without dragging an entire codebase, a directory full of providers, and a state file through an approval gate — is harder than it looks.

Terraform’s plan file is an elegant idea that almost gets there, but the combination of state dependency, partition-specific assumptions, and the .terraform directory requirement means it ultimately falls short in airgapped isolated environments.

AWS CDK and CloudFormation solve the problem in a way that feels almost purpose-built for it. Write your partition-aware logic once in TypeScript, synthesize it into a clean YAML artifact, pass that artifact through the approval gate, and let CloudFormation handle the rest on the high side. The code never crosses the boundary. The state is managed natively. The artifact is human readable and auditable.

If there is one takeaway from this article it’s this: understand your environment before you choose your tool. In most AWS deployments, Terraform wins on simplicity and community. In ADC isolated regions, CDK and CloudFormation win on everything that actually matters.

Neither tool is a silver bullet. CDK is the better option for this specific problem, but better is not the same as easy — and anyone who has actually deployed to the high side knows the difference.

I want to be clear that this is my current thinking based on my own experience, not a definitive answer. I’m still working through some of this in practice — and that’s okay. Learn and Be Curious is an AWS Leadership Principle for a reason: the best answers in this space come from staying curious, asking questions, and being honest about what you don’t know yet. I’m genuinely curious how other teams handle high-side deployments. If you’ve solved this differently — whether that’s a Terraform workflow that actually works across partitions, a custom toolchain, or something else entirely — I’d love to hear about it. This is a space where the community knowledge is thin and the operational experience is hard-won.


References Link to heading