This is the most common question that frequently pops up once in a while – why use a third-party tool if AWS services have done essentially the same thing.
There was argument been made in past with fewer compelling reasons on the said topic, but now the work of cloud formation has improved tremendously over the last couple of years. However, if to choose again today, I think I would still stick with Terraform. Here are some of the reasons for this.
Terraform uses HCL (HashiCorp Configuration Language), developed to strike a balance between being human readable as well as machine-friendly.
CloudFormation, on the other hand, uses either JSON or YAML.
In general, YAML is significantly easier to read and author than JSON, but it still forces you to have multiple nested scopes and everything goes horribly wrong if you mix up indentation somewhere. In contrast, HCL normally only has one or two scopes and enforces some basic Go-inspired formatting hygiene that makes it easier on the eyes.
Terraform has a rich set of string interpolations and built-in functions, including conditionals and (to be natively supported soon) loops, which allow modeling quite complex logic in DSL without having to resort to a fully-fledged programming language (although this can be done transparently with external data sources). The intrinsic functions of CloudFormation are noticeably limited in comparison.
Another thing in favor of Terraform is that it makes it easy to reuse code using modules and gives you a lot of leeway in structuring your projects the way it makes the most sense to you. CloudFormation supports nested stacks, but in general, you have to keep all your infrastructure code in a gigantic file (or several) in one project repository, whereas with Terraform you can slice and dice however you want. For regular humans, working with several 100-line files is usually much easier than with a single 5,000+ line file. Modules can be stored on GitHub or public Terraform Module Registry and be easily versioned and shared across multiple projects.
Terraform keeps its state in a file. This file can be stored on disk, committed to source control (not recommended), or kept in S3 or some configuration management system. Whatever you choose, you must make sure that you don’t lose or corrupt your state file, or don’t accidentally use a state from the production environment while deploying to test. It’s more difficult to shoot yourself in the foot after the introduction of workspaces (previously called ‘environments’), but in the early days, we did get a few battle scars dealing with the Terraform state.
In contrast, CloudFormation runs on AWS infrastructure and manages the state for you, so you don’t really care how it does that. Unlike Terraform, CloudFormation also tries to roll back the changes if they could not be applied (well, unless it gets stuck in UPDATE_ROLLBACK_FAILED state…). In addition, CloudFormation backend can receive signals from your resources, which enables an extremely useful option of configuring rolling updates for an Auto Scaling group. CloudFormation also doesn’t mind if a machine that triggered a deployment suddenly went down (as can happen with fungible CI/CD servers), whereas Terraform would need to recover from a partial update.
So, on the face of it, this looks like a case where CloudFormation wins. If we dig a little bit deeper though we’ll see that Terraform’s state file is a very powerful concept and one of its main selling points. It allows us to import, adopt, or move around resources, which means we can refactor our infrastructure at will without having to tear down and recreate resources.
Another thing that is possible in Terraform is managing configuration drift. Every time we run ‘terraform plan’ it fetches the latest actual state of the infrastructure and compares it to the desired state, defined in the configuration files, and the known state, which mirrors the actual state when we last ran ‘terraform apply’ and is stored in the state file. If our infrastructure no longer matches the desired state (usually due to some out-of-band changes).
Terraform would compute the diff and suggest to bring it back in line with what’s described in our configuration by reverting the manual changes. As long as we maintain idempotent deployments, running periodic Terraform plans is a convenient way to detect and revert the configuration drift, and is, in fact, something that is done on schedule in our infrastructure provisioning pipelines.
Meanwhile, CloudFormation would stay blissfully unaware of any changes made outside its state. Try this quick experiment.
First, deploy a new CloudFormation stack to create a new VPC and a security group with no ingress rules:
|AWSTemplateFormatVersion: ‘2010-09-09’ Resources: VPC: Type: “AWS::EC2::VPC” Properties: CidrBlock: 10.0.0.0/16 SecurityGroup: Type: “AWS::EC2::SecurityGroup” Properties: GroupName: TestSG GroupDescription: Test security group VpcId: !Ref VPC|
Parameters make it possible to re-use the same template in various environments. For example, you may want to use cheaper EC2 instances in your test environment, and something more powerful in production.
Cloud formation supports up to 60 parameters that must be provided at runtime. There are several ways to do this. The simplest way is to simply pass parameters one by one and default them to something sensible. Alternatively, you can supply parameters in bulk from a file (you need to make sure that you are passing the correct file), import values from the output of another stack, or read them from Parameter Store. You can also decide to use a Mapping section and to look up parameters by some key (for instance, by environment).
Quality of life stuff (aka miscellanea)
Terraform validates the configuration files before trying to run the updates. It checks not only that all files use the correct syntax (CloudFormation has similar validation), but also that all parameters are accessible and the configuration as a whole is valid.
In Terraform, you can (and should) run a ‘plan’ step before applying any changes. This step tells you precisely what is going to change and why (with nice colors to boot!).
CloudFormation supports Change Sets, which are considerably less human-friendly and hard to make sense of.
Terraform can automatically format code, helping to maintain a consistent code style and making pull requests easier to read. JSON (or now YAML) fatigue which plagues CloudFormation developers is hard to imagine with Terraform.
Neither Terraform nor CloudFormation cover 100% of available AWS resources, but generally Terraform is known to support newly available resources months earlier than they are made available in CloudFormation. All new AWS functionality must have REST API and SDK support for the main languages when it is released. CloudFormation support, on the other hand, is optional. The open-source software shines in this case, as Terraform developers quickly jump in to add the functionality that they want to use.
So, does this all mean that Terraform is always superior to CloudFormation? Not really. There are some use cases where using CloudFormation makes more sense.