What is user_data in Terraform?

Table of Content

  1. What is user_data in Terraform?
  2. When to use user_data in Terraform?
  3. Terraform user_data : How to execute shell script inside EC2 instance using?
  4. How to accomplish common tasks - Folder creation, Package installation with Terraform user_data
  5. How to use ${file("")} function with user_data?
  6. Using template_file with user_data
  7. Conclusion


What is user_data in Terraform?

In Terraform, the user_data attribute is used to pass data to an instance at launch time. It is specified as a string in the Terraform configuration file and can contain any valid data that is base64-encoded when the instance launches.

Here is an example of user_data attribute when I am setting up an EC2 instance -

 1#maint.tf 
 2
 3resource "aws_instance" "example" {
 4  ami = "ami-0767046d1677be5a0"
 5  instance_type = "t2.micro"
 6  
 7  # user_data attribute - Create a txt file inside EC2 instance
 8  user_data     = <<-EOF
 9                    #!/bin/bash
10                    echo "Hello, World!" > hello.txt
11                  EOF
12} 

Here is how the above example works -

  1. The above example will create an EC2 instance using the specified AMI and instance type, and passes a script to the instance as user data.
  2. The script will run when the instance launches, creating a file called hello.txt that contains the string Hello, World!.
  3. The user_data attribute is only used at the time the instance is launched

When to use user_data in Terraform?

Here are five common use cases for the user_data attribute in Terraform:

  1. Installing software or packages: Installing software or packages on an instance at launch time. For example, you might use user_data to install a web server or database server when an instance is launched.
  2. Configuring the instance: Configuring the instance to run a specific application or service. For example, you might use user_data to configure an instance to run a web application.
  3. Setting the hostname or other instance metadata: Setting the hostname or other instance metadata. This can be useful for identifying instances in a cluster or for customizing the instance's environment.
  4. Configuring the instance: Configuring the instance to communicate with other resources, such as an S3 bucket or a database.
  5. Automating the setup: Automating the setup of an instance when it is launched, rather than manually configuring it after it has been launched. This can save time and effort when launching multiple instances.

Terraform user_data : How to execute shell script inside EC2 instance using?

Let's take a very basic example of user_data attribute in which we are going to execute the script inside an EC2 instance on AWS.

Here is an example code and here is the sequence of execution -

  1. First we will write user_data attribute block inside the terraform main.tf
  2. Inside the user_data block we are going to write a shell command to generate the text file - echo "Hello World" > hello.txt
  3. At last we are going to execute the code using terraform init, terraform plan and terraform apply command
  4. End Result: The shell script will create a hello.txt file at root directory of EC2 Instance.
 1#maint.tf 
 2
 3resource "aws_instance" "example" {
 4  ami = "ami-0767046d1677be5a0"
 5  instance_type = "t2.micro"
 6  
 7  # user_data attribute - Create a txt file inside the EC2 instance
 8  user_data     = <<-EOF
 9                    #!/bin/bash
10                    echo "Hello, World!" > hello.txt
11                  EOF
12} 

Here is a screenshot from the root directory of the EC2 Instance and you can see we have created a file hello.txt -

Terraform user_data block created

Terraform user_data create text file on startup

How to accomplish common tasks - Folder creation, Package installation with Terraform user_data

In the previous point we have seen a very basic example of how to execute a very simple shell script using the user_data block of terraform.

Let's take one step further and this time we will try to accommodate some more common stuff for example - folder creation, Linux package installation

Task: Install python and Nginx server using user_data

Here is how we are gonna perform common tasks with user_data -

  1. Create a directory using the command - mkdir /opt/myapp
  2. Install python with the command - sudo apt install python3
  3. Install nginx using the command - sudo apt install nginx

Let's create one bash script for all the above common tasks-

Here is the bash script version of all the common tasks which I have mentioned above

 1#!/bin/bash
 2
 3# Create a new directory
 4mkdir /opt/myapp
 5
 6# Install python
 7sudo apt install python3
 8
 9# Install nginx 
10sudo apt install nginx

Create main.tf with user_data : In this main.tf first we are going to create aws_instance then in the user_data section common tasks script will be executed.

 1# main.tf
 2
 3resource "aws_instance" "example" {
 4  ami = "ami-0767046d1677be5a0"
 5  instance_type = "t2.micro"
 6  
 7  # user_data attribute performing common tasks
 8  # 1. Create a new directory
 9  # 2. Install python
10  # 3. Install nginx
11  user_data     = <<-EOF
12                    #!/bin/bash
13                    
14                    # Create a new directory
15                    mkdir /opt/myapp
16                    
17                    # Install python
18                    sudo apt install python3
19                    
20                    # Install nginx 
21                    sudo apt install nginx
22                  EOF
23}  

How to use ${file("")} function with user_data?

In this section, we are going to use the file function in Terraform to read the contents of a file and pass it to the user_data attribute of an instance resource. Here is an example of how to use the file function with user_data:

 1#main.tf 
 2
 3provider "aws" {
 4  region     = "eu-central-1"
 5  shared_credentials_files = ["/Users/rahulwagh/.aws/credentials"]
 6}
 7
 8
 9resource "aws_instance" "example" {
10  ami = "ami-0767046d1677be5a0"
11  instance_type = "t2.micro"
12  vpc_security_group_ids = [aws_security_group.main.id]
13  key_name= "aws_key"
14  user_data     =  "${file("install_apache.sh")}"
15}

In the above example, the file function reads the contents of the install_nginx.sh file and passes it to the user_data attribute of the aws_instance resource.

Here is the content of install_nginx.sh:

1#! /bin/bash
2sudo apt-get update
3sudo apt-get install -y apache2
4sudo systemctl start apache2
5sudo systemctl enable apache2
6echo "<h1>Welcome : Apache installed with the help of user_data and file function</h1>" | sudo tee /var/www/html/index.html

Where to place the file? : You should be careful about the placement of the file which will be read by the user_data. Here is the screenshot of my project where I have placed the install_nginx.sh at the same location where my main.tf is present.

In case your file is present at some other location then you should use the relative path to load the file using the ${file("")} function.

Terraform user_data package structure for templatefile

Status of apache after installing it using terraform user_data

Using template_file with user_data

In the next combination, we are going to use template_file resource along with user_data.

But before that let's recap a bit - In the previous section we have hard coded the script inside the user_data block which is perfectly fine and also loaded the large file using the ${file("")}.

But sometimes both options are not sufficient to implement certain tasks with user_data block. You need to have dynamic template where you can update the script at run time and to achieve that we can also use the template_file resource along with user_block in terraform.

Here is an example code on how to do that -

  1. Create Bash file- First create bash script file install_nginx.sh
1#! /bin/bash
2sudo apt-get update
3sudo apt-get install -y apache2
4sudo systemctl start apache2
5sudo systemctl enable apache2
6echo "<h1>Welcome : Apache installed with the help of user_data and file function</h1>" | sudo tee /var/www/html/index.html
  1. Create main.tf and call template_file - Now let's create main.tf and also create template_file block to render the template file .i.e. install_nginx.sh. You can create terraform template with any extension for example - .json, .sh, .tpl
 1#main.tf 
 2
 3# Refer to the template file - install_nginx.sh
 4resource "template_file" "user_data" {
 5  template = "${file("install_nginx.sh")}"
 6
 7  vars {
 8    package = "nginx"
 9    command = "systemctl start nginx"
10  }
11}
12
13resource "aws_instance" "example" {
14  ami           = "ami-12345678"
15  instance_type = "t2.micro"
16  
17  # user_data : render the template
18  user_data     = "${template_file.user_data.rendered}"
19}
  1. Finally you can run the terraform init, terraform plan and terraform apply commands.

Conclusion

If you are reading the conclusion part of this blog post then I would you have pretty much covered all the basic knowledge which is necessary for using the user_data. In an actual production environment, you might see more complex use cases where you will be a lot more complex stuff rather than just creating the folder and installing the package. But this blog post serves you a good start with user_data. For more practical stuff you can follow me on youtube as well.

Posts in this Series