Terragrunt is a thin wrapper for the Terraform client that provides a distributed locking mechanism which allows multiple people to collaborate on the same Terraform state without overwriting each other's changes. Terragrunt currently uses Amazon's DynamoDB to acquire and release locks. DynamoDB is part of the AWS free tier, so if you're already using AWS, this locking mechanism should be completely free. Other locking mechanisms may be added in the future.
When you use Terraform to provision infrastructure, it records the state of your infrastructure in state
files. In order to make changes to your infrastructure, everyone on your
team needs access to these state files. You could check the files into version control (not a great idea, as the state
files may contain secrets) or use a supported remote state
backend to store the state files in a shared location such as
S3,
Consul,
or etcd. The problem is that none of these options provide
locking, so if two team members run terraform apply
on the same state files at the same time, they may overwrite
each other's changes. The official solution to this problem is to use Hashicorp's
Atlas, but that requires using a SaaS platform for all Terraform operations and
can cost a lot of money.
The goal of Terragrunt is to provide a simple, free locking mechanism that allows multiple people to safely collaborate on Terraform state.
- Install Terraform.
- Install Terragrunt by going to the Releases Page, downloading the binary for your OS, and adding it to your PATH.
Go into the folder with your Terraform templates and create a .terragrunt
file. This file uses the same
HCL syntax as Terraform and is used to configure Terragrunt and tell it how to do
locking. To use DynamoDB for locking (see Locking using DynamoDB), .terragrunt
should
have the following contents:
lockType = "dynamodb"
stateFileId = "my-app"
Now everyone on your team can use Terragrunt to run all the standard Terraform commands:
terragrunt get
terragrunt plan
terragrunt apply
terragrunt output
terragrunt destroy
Terragrunt forwards most commands directly to Terraform. However, for the apply
and destroy
commands, it will first
acquire a locking using DynamoDB:
terragrunt apply
[terragrunt] 2016/05/27 00:39:18 Attempting to acquire lock for state file my-app in DynamoDB
[terragrunt] 2016/05/27 00:39:19 Attempting to create lock item for state file my-app in DynamoDB table terragrunt_locks
[terragrunt] 2016/05/27 00:39:19 Lock acquired!
terraform apply
aws_instance.example: Creating...
ami: "" => "ami-0d729a60"
instance_type: "" => "t2.micro"
[...]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
[terragrunt] 2016/05/27 00:39:19 Attempting to release lock for state file my-app in DynamoDB
[terragrunt] 2016/05/27 00:39:19 Lock released!
Terragrunt can use Amazon's DynamoDB to acquire and release locks. DynamoDB supports strongly consistent reads as well as conditional writes, which are all the primitives we need for a very basic distributed lock system. It's also part of AWS's free tier, and given the tiny amount of data we are working with and the relatively small number of times per day you're likely to run Terraform, it should be a free option for teams already using AWS.
To use DynamoDB for locking, you must:
-
Already have an AWS account.
-
Set your AWS credentials in the environment using one of the following options:
- Set your credentials as the environment variables
AWS_ACCESS_KEY_ID
andAWS_SECRET_ACCESS_KEY
. - Run
aws configure
and fill in the details it asks for. - Run Terragrunt on an EC2 instance with an IAM Role.
- Set your credentials as the environment variables
-
Your AWS user must have an IAM policy granting all DynamoDB actions (
dynamodb:*
) on the tableterragrunt_locks
(see the DynamoDB locking configuration for how to configure this table name). Here is an example IAM policy that grants the necessary permissions on theterragrunt_locks
in regionus-west-2
for an account with account id1234567890
:{ "Version": "2012-10-17", "Statement": [{ "Sid": "", "Effect": "Allow", "Action": "dynamodb:*", "Resource": "arn:aws:dynamodb:us-west-2:1234567890:table/terragrunt_locks" }] }
For DynamoDB locking, Terragrunt supports the following settings in .terragrunt
:
lockType = "dynamodb"
stateFileId = "my-app"
dynamoLock = {
awsRegion = "us-east-1"
tableName = "terragrunt_locks"
maxLockRetries = 360
}
lockType
: (Required) Must be set todynamodb
.stateFileId
: (Required) A unique id for the state file for these Terraform templates. Many teams have more than one set of templates, and therefore more than one state file, so this setting is used to disambiguate locks for one state file from another.awsRegion
: (Optional) The AWS region to use. Default:us-east-1
.tableName
: (Optional) The name of the table in DynamoDB to use to store lock information. Default:terragrunt_locks
.maxLockRetries
: (Optional) The maximum number of times to retry acquiring a lock. Terragrunt waits 10 seconds between retries. Default: 360 retries (one hour).
When you run terragrunt apply
or terragrunt destroy
, Terragrunt does the following:
- Create the
terragrunt_locks
if it doesn't already exist. - Try to write an item to the
terragrunt_locks
withstateFileId
equal to the id specified in your.terragrunt
file. This item will include useful metadata about the lock, such as who created it (e.g. your username) and when. - Note that the write is a conditional write that will fail if an item with the same
stateFileId
already exists.- If the write succeeds, it means we have a lock!
- If the write does not succeed, it means someone else has a lock. Keep retrying every 30 seconds until we get a lock.
- Run
terraform apply
orterraform destroy
. - When Terraform is done, delete the item from the
terragrunt_locks
to release the lock.
If you shut down Terragrunt (e.g. via CTRL+C
) before it releases a lock, the lock hangs around forever, and will
prevent future changes to your state files. To clean up old locks, you can use the release-lock
command:
terragrunt release-lock
Are you sure you want to forcibly remove the lock for stateFileId "my-app"? (y/n):
Note: The tests for Terragrunt run against a real AWS account and will add and remove real data from DynamoDB.
To run all the tests:
- Configure your AWS credentials as explained in the DynamoDB locking prerequisites section.
./_ci/run-tests.sh
To run a single test, go into the folder with the test and use the go test
command. Example:
cd config
go test -v -parallel 128
export VERSION=0.0.1
gox -os "darwin linux" -output "bin/${APP_NAME}_{{.OS}}_{{.Arch}}" -ldflags="-X main.VERSION=$VERSION"
- CI job to run the tests and build and publish new binaries
- Implement best-practices in Terragrunt, such as checking if all changes are committed, calling
terraform get
, callingterraform configure
, etc. - Consider implementing alternative locking mechanisms, such as using Git instead of DynamoDB.
- Consider embedding the Terraform Go code within Terragrunt instead of calling out to it.