CDK and Terraform, both powerful infrastructure-as-code (IaC) tools, are often compared, but the most surprising truth is that they aren’t direct competitors; they operate at fundamentally different abstraction levels, with CDK building on top of Terraform or other cloud providers’ APIs.
Let’s see CDK in action. Imagine you want to provision a simple S3 bucket with versioning and logging enabled.
from aws_cdk import (
aws_s3 as s3,
core
)
class MyS3Stack(core.Stack):
def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
bucket = s3.Bucket(self, "MyVersionedLoggingBucket",
versioned=True,
logging_bucket_name="my-cdk-log-bucket-12345",
block_public_access=s3.BlockPublicAccess.BLOCK_ALL)
When you run cdk synth, this Python code is translated into CloudFormation templates. If you then deploy this stack using cdk deploy, the CDK will interact with AWS CloudFormation, which in turn provisions the S3 bucket.
Now, consider Terraform. To achieve the same result, you’d write HCL (HashiCorp Configuration Language):
resource "aws_s3_bucket" "my_versioned_logging_bucket" {
bucket = "my-terraform-bucket-67890"
acl = "private"
versioning {
enabled = true
}
logging {
target_bucket = "my-terraform-log-bucket-67890"
target_prefix = "log/"
}
block_public_access {
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
}
Running terraform apply directly interacts with the AWS API to create the bucket.
The core problem both tools solve is the repeatable, version-controlled, and auditable management of cloud infrastructure. Instead of clicking around in a console, you define your desired state in code. This dramatically reduces human error, speeds up deployments, and makes it easy to revert changes or replicate environments.
The internal mechanics differ. CDK uses familiar programming languages (Python, TypeScript, Java, C#, Go) to define infrastructure. It has a concept of "constructs," which are higher-level abstractions. A single CDK construct might generate multiple underlying cloud resources. For example, a ServerlessFunction construct in CDK might create an AWS Lambda function, an IAM role, and an API Gateway endpoint. This "escape hatch" mechanism allows you to drop down to the CloudFormation level if needed, or even integrate with Terraform or other providers.
Terraform, on the other hand, uses its own declarative language, HCL. It has a provider model, where specific providers (like aws, azure, google) translate HCL into API calls for those cloud platforms. Terraform’s strength lies in its provider ecosystem and its state management, which tracks the real-world resources managed by a given configuration.
The mental model to grasp is that CDK is a synthesizer. It takes your high-level code and generates lower-level IaC definitions (like CloudFormation or Terraform HCL) that are then executed by a different engine. Terraform is an executor and state manager. It directly translates its declarative language into API calls and keeps track of what it has provisioned. This is why you can use the CDK to generate Terraform HCL, effectively using CDK’s programming language abstractions to drive Terraform’s execution engine.
What most people miss is that CDK applications can be configured to synthesize Terraform HCL. This isn’t just a theoretical possibility; it’s a powerful way to leverage CDK’s programming language benefits for defining infrastructure that is then managed by Terraform’s robust state and execution capabilities. You can define your infrastructure in Python, have CDK generate the Terraform configuration, and then use terraform plan and terraform apply to manage it.
The next concept you’ll likely explore is how to manage state effectively, especially when dealing with complex, multi-environment deployments across different cloud providers.