Understanding Terraform Null Resource: What It Does & How to Use It

Understanding Terraform Null Resource: What It Does & How to Use It blog post

Understanding Terraform Null Resource: What It Does & How to Use It

While using Terraform, there will be some situations where you might need to execute tasks that just go beyond what Terraform’s standard resources can perform. For example, once an EC2 instance is created, you might need to run a custom script on that particular instance to install some software like NGINX, or you might also need to execute a task that requires multiple resources to be ready before proceeding with the configuration. These tasks don’t always align well with Terraform’s standard resource types, which simply leads us to rely on some external scripts. For such scenarios, the Terraform null_resource is a useful solution for executing custom tasks, including those related to application code deployment and configuration.

In this blog, we’ll explain what a null_resource and trigger are, how to use them, and explore some real-world use cases for the null_resource. We will also provide some step by step examples of how to use these null_resources effectively within your terraform configurations. We will also simplify our infrastructure management with solutions such as Terrateam.

Sign up for the Terrateam’s free trial.

What is null_resource?

The null_resource in Terraform behaves like a regular resource, but it doesn’t create any resources within your infrastructure. Instead, it allows you to run tasks that aren’t linked to any specific cloud infrastructure resources, such as EC2 instances. Whether it’s executing a script or managing dependencies between different resources, the null_resource helps you handle these actions more efficiently.

For example, if you need to update the DNS records after creating a load balancer, the null_resource allows you to automate this task without creating any new resources.

Starting with Terraform 1.4, the terraform_data resource is recommended instead of the null_resource. In Terraform 1.9 and later, you can simply use the moved block to switch from null_resource to terraform_data. However, the null_resource is still valuable for many Terraform setups that handle any custom tasks and workflows that don’t need any actual infrastructure.

Creating a null resource

The null_resource in Terraform executes tasks immediately when you run terraform apply, but no state is saved afterward. This makes it useful for handling operations that don’t need to track changes over time, such as running one-time scripts or any commands within your workflow.

The null_resource is easy to implement and follows the same structure as other Terraform resource code blocks. Let’s look at how a null_resource is typically structured:

resource "null_resource" "local_7635" {
provisioner "local-exec" {
command = "echo 'Executing a one-time task with null_resource'"
}
}

Here, the null_resource allows you to run a command using the command line interface (CLI) within your Terraform workflow without deploying any resources. The provisioner local-exec runs the command on your machine, and the command argument specifies what to execute. Here, it’s an echo command, but it could be a script or any other command.

To better understand how null_resource works, let’s consider you need to deploy an S3 bucket by running a separate Terraform code before provisioning an EC2 instance. In this scenario, create a separate folder called bucket, which contains a main.tf file for creating the S3 bucket. We use the null_resource to navigate to this folder, initialize the Terraform configuration, and apply it to provision the bucket. Once the bucket is successfully created, proceed to provision the EC2 instance.

Here’s how the Terraform configuration looks for creating the null resource:

resource "null_resource" "run_bucket_terraform" {
provisioner "local-exec" {
command = "cd bucket && terraform init && terraform apply --auto-approve"
}
}
resource "aws_instance" "terrateam_in" {
ami = "ami-0e86e20dae9224db8"
instance_type = "t2.micro"
subnet_id = "subnet-02efa144df0a77c13"
depends_on = [null_resource.run_bucket_terraform]
}

In this code, the null_resource is being used to run a series of commands before provisioning the EC2 instance.

  • First, it navigates to the bucket directory, where the Terraform configuration for creating the S3 bucket is stored, allowing the process to proceed with bucket creation.
  • Then, it runs terraform init and terraform apply --auto-approve to create the bucket before proceeding to the EC2 instance setup.

After running terraform init and terraform apply for the null_resource configuration, the null_resource successfully executed its task. It navigated to the bucket directory and applied the separate Terraform configuration to create the S3 bucket.

The bucket, named terrateam-bucket-example, was created successfully.

Once the bucket was set up, the EC2 instance creation was triggered, and the instance was successfully created.

This simply shows that the workflow was executed in the correct order. First, create the S3 bucket using the separate Terraform code, then create the EC2 instance as specified in the main configuration.

What does the trigger block do for null_resource?

The null_resource itself doesn’t track any state. By default, it only runs once unless something explicitly changes in the configuration files that Terraform can detect. That’s where triggers come in; they let you specify the conditions that should trigger the null_resource to run again.

How Triggers Work

Triggers are essentially key-value pairs within the null_resource block, which you can set to any dynamic value, such as the ID of a resource that may change over time. Whenever the value of one of these key-value pairs changes, Terraform treats it as a signal to re-run the null_resource tasks.

Here’s a basic example of how the trigger works within the null_resource block:

resource "null_resource" "example_with_trigger" {
triggers = {
instance_id = aws_instance.terrateam_in.id
}
provisioner "local-exec" {
command = "echo 'Running null resource due to change in instance ID'"
}
}

In this code:

  • The triggers argument is set to the EC2 instance ID. If the instance is replaced or modified, the ID will change, triggering the null_resource to re-execute.
  • The local-exec provisioner will run the specified command or shell scripts whenever the instance_id changes, ensuring that any dependent tasks are automatically handled with the change.

This approach makes sure that tasks, like running any scripts or updating the configurations, are automatically re-executed whenever specific changes occur within your infrastructure.

Using the triggers argument, you can tie custom actions to dynamic updates in your Terraform setup, making sure that your Terraform workflow stays in sync with changes and operates more efficiently.

Use Cases for Null Resource

By now, we’ve seen how the null_resource works. Let’s take a look at some practical use cases of null resources:

  • Custom Software Installation: A common use case for the null_resource is installing specific software on an EC2 instance after it’s been created within your infrastructure. For example, after creating an EC2 instance, you may need to install Nginx for web hosting, MySQL for database management, or CloudWatch agents for monitoring. The null_resource allows you to run a script that will install this software on its own once the instance is up and running. This makes sure that any important services like web servers, security groups, databases, or monitoring tools are set up without any effort from your end, making the process faster.
  • Environment-Specific Application Configuration: Another important use case for the null_resource is setting up applications on an environment basis, such as development, staging, or production. Each environment needs specific settings or configurations. For example, in production, you might connect to the database, use API keys, and enable full logging. In development, you might use a test database, mock API keys, and limit logging. The null_resource lets you run scripts to apply these settings automatically. This ensures that the right configurations, like database connections and API keys, are applied to each environment without having to do anything from your end.
  • Patching and Updates: Regular patching and updating of software are important for maintaining the security and stability of your infrastructure. Using a null_resource, you can automate these tasks of applying updates to your instances after they are created. For example, after creating an EC2 instance, the null_resource can trigger a script to install the latest security patches and update key software packages like Nginx or MySQL. This makes sure that every instance is compliant with the latest security standards without the need for any intervention from your end.

Example Scenarios

Now, let’s take a look at some practical examples of how to use the null_resource with different provisioners. Typically, you would use something like this in scenarios where you need to execute some commands on your machine itself, run commands on an EC2 instance, or re-execute tasks when certain values change.

null_resource with Local Provisioner

In this example, we will use the null_resource with the local-exec provisioner to run a simple command after an EC2 instance is created. The goal here is to create a text file named terrateam.txt within the same repo once the instance is up and running.

Here is the Terraform code for the same:

resource "aws_instance" "infrasity_ec2" {
ami = "ami-0e86e20dae9224db8"
instance_type = "t2.micro"
subnet_id = "subnet-02efa144df0a77c13"
}
resource "null_resource" "null_resource_simple" {
triggers = {
id = aws_instance.infrasity_ec2.id
}
provisioner "local-exec" {
command = <<-EOT
touch terrateam.txt
EOT
}
}

In this code:

  • The null_resource is configured to run the local-exec provisioner, which executes the touch terrateam.txt command, creating a file named terrateam.txt locally after the EC2 instance is provisioned.
  • The triggers argument uses the EC2 instance ID (aws_instance.infrasity_ec2.id) to determine when the null_resource should run. If the instance is recreated or updated, the null_resource re-executes based on this change.

Now run terraform init to initialize and terraform apply to deploy the infrastructure and execute the null_resource block.

Once the EC2 instance is successfully created, the null_resource triggers the command to create the terrateam.txt file.

After the null_resource completes, the command is executed, and the terrateam.txt file is created in the same directory where your Terraform configuration is being run, confirming that the task has been executed as intended.

null_resource with Remote Provisioner

Before going to the example, let’s first understand the remote-exec provisioner. The remote-exec provisioner allows you to execute commands on a remote machine, such as an Amazon Web Services (AWS) EC2 instance, over an SSH connection. It’s particularly useful when you need to configure or modify a resource after it has been created within your infrastructure. By using the remote-exec provisioner, you can remotely run scripts, install software, or even perform system updates directly on the instance.

In this example, we will use the null_resource along with the remote-exec provisioner to run a command on an existing EC2 instance. The goal is to connect to the instance via SSH and create a file named hello.txt on that remote server with the help of Terraform null resource.

Here’s the Terraform configuration for this example:

data "aws_instance" "existing_ec2" {
instance_id = "i-0c97bf1d2fb7e36ac"
}
resource "null_resource" "install_nginx" {
triggers = {
instance_id = data.aws_instance.existing_ec2.id
}
connection {
type = "ssh"
host = data.aws_instance.existing_ec2.public_ip
user = "ubuntu"
private_key = file("C:/Users/Saksham/Downloads/null.pem")
}
provisioner "remote-exec" {
inline = [
"touch hello.txt"
]
}
}
output "instance_public_ip" {
value = data.aws_instance.existing_ec2.public_ip
}

In this code:

  • The null_resource is triggered by changes to the EC2 instance ID, using the triggers argument to monitor the instance’s ID
  • The connection block sets up the SSH connection to the existing EC2 instance. It uses the instance’s public IP, along with the ubuntu user and a private key stored locally, to securely connect to the instance through the AWS management console.
  • The remote-exec provisioner runs the command touch hello.txt, creating a file on the remote EC2 instance. This allows you to automate tasks that run directly on the infrastructure once it’s provisioned.

Once the infrastructure is provisioned using terraform init and terraform apply, the null_resource will then connect to the remote EC2 instance via SSH and execute the touch hello.txt command to create the file on that remote machine.

This example shows how null_resource can automate tasks on an existing infrastructure by using the remote-exec provisioner. It’s particularly useful when you need to run scripts or perform configuration tasks on remote servers after they’ve been provisioned.

Using Trigger to Execute null_resource Every Time

We’ve seen how the null_resource can handle both local tasks and remote commands, depending on the setup and the workflow requirements. Now let’s move on to another example where we will use triggers to control the re-execution of null_resource.

In this example, we’ll use the null_resource with a dynamic trigger that ensures it re-executes every time a specific key-value pair changes. This is useful when you need to rerun tasks regularly or when any specific values in your setup change.

Here’s how the trigger works:

resource "null_resource" "timestamp_with_trigger" {
triggers = {
id = timestamp()
}
provisioner "local-exec" {
command = "echo 'This is running due to a trigger change'"
}
}
resource "aws_instance" "terrateam_in" {
ami = "ami-0e86e20dae9224db8"
instance_type = "t2.micro"
subnet_id = "subnet-02efa144df0a77c13"
tags = {
Name = "TerrateamInstance"
}
}

In this code:

  • The triggers argument uses the timestamp() function as the value. Since timestamp() returns the current time, it changes every time terraform apply is run, making sure that the null_resource will always re-execute. This forces the null_resource to be replaced on every deployment due to the trigger change.
  • The local-exec provisioner runs the command echo 'This is running due to a trigger change', confirming that the task is executed every time the timestamp() changes. In each terraform apply, the null_resource is destroyed and recreated, re-executing the local command each time.

Once you have initialized the configuration with terraform init, you can run the terraform apply multiple times. Each time you run terraform apply, the null_resource will execute again, as the timestamp() function changes with every apply.

You can see that with each terraform apply, the id is being changed again and again due to the dynamic timestamp(). This forces the null_resource to be replaced, triggering the task to run each time.

All of the examples above just show how null_resource can be used to run local tasks, perform commands on remote instances, and trigger tasks whenever something changes, making it a useful tool for automating different actions in Terraform.

Now, as your infrastructure scales and you collaborate with multiple team members, managing your Terraform configuration, such as deploying cloud services and null_resource tasks, becomes a bit complex. Meanwhile, null resources are frequently used to run custom scripts, manage dependencies, or handle tasks that fall outside of the standard Terraform resources. It is crucial to review the changes deployed to the infrastructure with each pull request which might simply lead you to some errors, especially when ensuring that everything is properly triggered and executed if left unchecked. Now, to simplify the management of the deployments and ensure that the code is correctly reviewed before deployments are made, teams use solutions such as Terrateam

Null Resources Creation with Terrateam

Terrateam easily manages your infrastructure as code, collaborates with your team, and automates your deployment processes by integrating directly with your GitHub repository. Terrateam automates the plan and apply steps while providing a built-in review process. This ensures that every change, whether it’s a null_resource task or another infrastructure update, must undergo a thorough review before deployment. This allows teams to collaborate efficiently while minimizing risks and ensuring consistency across environments. With Terrateam, you can push your changes, review them in the pull request, and let the platform take care of the rest, ensuring that even complex workflows run smoothly without manual intervention.

Terrateam Installation

Now, Let’s see how to get Terrateam up and running with your Terraform configurations.

To begin, head over to the Terrateam GitHub application, and you can easily integrate Terrateam into your github workflow.

Once installed, you’ll choose the GitHub organizations and repositories that contain your Terraform code, and you’ll be ready to go in minutes.

Next, you’ll need to set up Terrateam’s GitHub Actions workflow. Download the terrateam.yml file and place it in the .github/workflows directory of your repository’s default branch, which is main or master. If the directory doesn’t exist, create one, and you’re set.

Once that’s in place, you’ll need to configure the AWS resources required by Terrateam. A Terraform module and a CloudFormation template are available to easily create everything Terrateam needs. You can choose the setup method that works best for you:

  • Terraform
  • AWS CLI
  • AWS Console

To keep it simple, let’s look at the AWS CLI method for creating the required resources.

Run the following command to create the CloudFormation stack for Terrateam. Be sure to replace GITHUB_ORG with your GitHub organization name:

Terminal window
aws cloudformation create-stack \
--stack-name terrateam-setup \
--template-url https://terrateam-io-public.s3.us-east-2.amazonaws.com/terrateam-setup-cloudformation.yml \
--parameters ParameterKey=GithubOrg,ParameterValue=GITHUB_ORG \
ParameterKey=RoleArn,ParameterValue=arn:aws:iam::aws:policy/PowerUserAccess \
ParameterKey=CreateGithubOIDCProvider,ParameterValue=true \
ParameterKey=RoleName,ParameterValue=terrateam \
--capabilities CAPABILITY_NAMED_IAM

Once the stack is created, you’ll need to configure Terrateam for OIDC authentication.

To set up Terrateam to use OIDC with AWS, create a .terrateam/config.yml file at the root of your Terraform repository and simply add this yaml file, and remember to replace AWS_ACCOUNT_ID with your actual AWS account ID:

hooks:
all:
pre:
- type: oidc
provider: aws
role_arn: "arn:aws:iam::AWS_ACCOUNT_ID:role/terrateam"

This configures Terrateam to use AWS OIDC authentication before running any actions. With the setup complete, we can now move forward with using Terrateam to manage our Terraform deployments.

Deploying your resources with Terrateam

Let’s start by deploying a null_resource using Terrateam, by which you can observe how Terrateam manages plan and apply processes through GitHub without any extra effort. Within your GitHub repository where Terrateam is installed, create a new branch. This branch will be where we place our Terraform configuration. In this branch, create a file called main.tf and add the following null_resource configuration:

resource "null_resource" "terrateam_test" {
provisioner "local-exec" {
command = "echo 'Terrateam deployment in progress!'"
}
}

This code will simply echo “Terrateam deployment in progress!” as a test to ensure the process works smoothly. After adding the code, commit and push the main.tf file to the master/main branch of your repository. Then, create a pull request from your branch to the default branch. This is where Terrateam steps in to manage the Terraform process.

Once the pull request is created, Terrateam will run a terraform plan to show you a preview of the changes. No need for manual commands; Terrateam handles this for you.

To apply the changes, simply comment terrateam apply on the pull request.

Terrateam will trigger the terraform apply process, executing the null_resource and printing the “Terrateam deployment in progress!” message.

After the changes are applied and everything looks good, you can merge the pull request.

By following these steps, you’ve now completed your first Terrateam deployment. This simple setup shows how Terrateam can take care of your Terraform workflows, from planning to applying changes, directly through GitHub.

Sign up for Terrateam now and simplify your Terraform automation!

Conclusion

By now, you should have a solid understanding of how to use Terraform’s null_resource for tasks, from running local commands to executing scripts on remote instances and handling tasks that need to be re-executed with triggers. We’ve covered practical use cases that show the flexibility of null_resource and walked through examples that demonstrate how it can fit into your Terraform workflow and simplify it using tools such as Terrateam.

Infrastructure as Code. Optimized.

Ready to get started?

Experience powerful infrastructure orchestration, seamlessly integrated with GitHub.