Terraform and Docker

Vanshika SharmaVanshika Sharma
13 min read

In Terraform, specifying the provider is essential to determine which infrastructure platform will be managed. A provider acts as a plugin that allows Terraform to interact with a specific service, such as Docker, AWS, or Kubernetes. To use a provider in Terraform, we need to declare it explicitly along with its source and version. This ensures that Terraform downloads the correct provider plugin and maintains compatibility with our configurations. For Docker, we define the provider in the main.tf file using the required_providers block. Here, we specify kreuzwerker/docker as the source and define the version to ensure stability. Additionally, we configure the provider by specifying the Docker host, which allows Terraform to interact with the Docker daemon. This setup ensures that Terraform can efficiently manage Docker containers and images as part of the automation process.

Blocks and Resources in Terraform:

Terraform configurations are built using blocks, which define the desired infrastructure. Each block serves a specific purpose and consists of arguments and parameters to configure resources.

1. Blocks in Terraform

A block in Terraform is a fundamental unit of configuration that defines settings and infrastructure components. Each block starts with a block type followed by configuration parameters enclosed in {}. Common block types include:

  • Provider Block: Specifies the provider (e.g., AWS, Docker) that Terraform will use.

  • Resource Block: Defines the infrastructure components, such as virtual machines, networks, or storage.

  • Variable Block: Declares input variables that allow configuration customization.

  • Output Block: Displays the results of a Terraform execution.

  • Module Block: Groups multiple resources together for reuse.

2. Resources in Terraform

A resource is the most important block in Terraform, representing an actual infrastructure component. It defines what Terraform will create, modify, or delete. The syntax follows:

resource "<provider>_<type>" "<name>" {
  # Configuration settings
}

For example, to create an AWS EC2 instance:

resource "aws_instance" "example" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"
}

Here:

  • aws_instance is the resource type.

  • example is the resource name.

  • ami and instance_type are arguments specifying the instance details.

Terraform Block:

The Terraform block is the most fundamental in a Terraform configuration. It is used to define the required Terraform version and specify the providers needed for the infrastructure. This block ensures that Terraform works with a compatible version and installs the correct provider plugins.

Syntax of a Terraform Block

The Terraform block is written at the beginning of the main.tf file and follows this structure:

terraform {
  required_version = ">= 1.3.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

Key Components:

  1. required_version – Ensures Terraform uses a specific version or a compatible range.

  2. required_providers – Defines the providers needed for the configuration, specifying the source and version to maintain compatibility.

Task-01

  • Create a Terraform script with Blocks and Resources

terraform {
  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 2.21.0"
}
}
}

In this task, we will create a Terraform script that defines the necessary blocks and resources to manage Docker containers. This script includes:

  • A Terraform block to specify the required provider.

  • A Provider block to configure Docker.

  • A Resource block to create a Docker container.


Terraform Script (main.tf)

# Terraform Block - Specifies provider requirements
terraform {
  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 2.21.0"
    }
  }
}

# Provider Block - Configures the Docker provider
provider "docker" {
  host = "unix:///var/run/docker.sock"
}

# Resource Block - Defines a Docker container
resource "docker_container" "nginx" {
  name  = "nginx_container"
  image = docker_image.nginx.latest
  ports {
    internal = 80
    external = 8080
  }
}

# Resource Block - Pulls the latest Nginx image
resource "docker_image" "nginx" {
  name = "nginx:latest"
}

Explanation of Blocks and Resources:

  1. Terraform Block:

    • Defines the required Docker provider (kreuzwerker/docker) and specifies the version.
  2. Provider Block:

    • Configures Docker to communicate using the Docker daemon socket.
  3. Resource Blocks:

    • docker_image: Pulls the latest Nginx image from Docker Hub.

    • docker_container: Creates a Docker container from the Nginx image and maps port 8080 on the host to port 80 in the container.


Execution Steps:

  1. Initialize Terraform

     terraform init
    
  2. Validate the Configuration

     terraform validate
    
  3. Apply the Configuration

     terraform apply -auto-approve
    
  4. Check the Running Container

     docker ps
    
  5. Destroy Resources (If Needed)

     terraform destroy -auto-approve
    

This script ensures Terraform automates the deployment of a Docker container, making infrastructure management more efficient.

Provider Block:

The Provider block in Terraform is used to configure the provider that Terraform will interact with to manage infrastructure. A provider is a plugin that allows Terraform to communicate with different services like AWS, Azure, GCP, Docker, and many others.


Syntax of a Provider Block

provider "<provider_name>" {
  # Configuration settings
}

Each provider has its required configuration settings, such as authentication details or API endpoints.


Example: Docker Provider Block

provider "docker" {
  host = "unix:///var/run/docker.sock"
}

Explanation:

  • provider "docker" → Specifies that Terraform will use the Docker provider.

  • host → Defines the Docker daemon socket to connect to (used for local Docker installations).


Example: AWS Provider Block

provider "aws" {
  region = "us-east-1"
}

Explanation:

  • provider "aws" → Specifies the AWS provider.

  • region → Sets the AWS region where resources will be deployed.


Why is the Provider Block Important?

  1. Tell Terraform which provider to use.

  2. Configures provider-specific settings.

  3. Ensures Terraform can authenticate and communicate with the provider's API.

Task-02

  • Create a resource Block for an nginx docker image

Hint:

resource "docker_image" "nginx" {
 name         = "nginx:latest"
 keep_locally = false
}
  • Create a resource Block for running a docker container for nginx

resource "docker_container" "nginx" {
 image = docker_image.nginx.latest
 name  = "tutorial"
 ports {
   internal = 80
   external = 80
 }
}

Note: In case Docker is not installed

sudo apt-get install docker.io sudo docker ps sudo chown $USER /var/run/docker.sock

In this task, we will define resource blocks in Terraform to:

  1. Pull the latest Nginx image from Docker Hub.

  2. Create and run a Docker container using the pulled Nginx image.


Terraform Script (main.tf)

# Terraform Block - Defines the required provider
terraform {
  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 2.21.0"
    }
  }
}

# Provider Block - Configures Docker provider
provider "docker" {
  host = "unix:///var/run/docker.sock"
}

# Resource Block - Pulls the latest Nginx image
resource "docker_image" "nginx" {
  name         = "nginx:latest"
  keep_locally = false
}

# Resource Block - Creates and runs an Nginx container
resource "docker_container" "nginx" {
  image = docker_image.nginx.latest
  name  = "tutorial"

  ports {
    internal = 80
    external = 80
  }
}

Explanation of Resource Blocks:

1. docker_image Block:

  • name = "nginx:latest" → Pulls the latest Nginx image.

  • keep_locally = false → Removes the image if not used to free up space.

2. docker_container Block:

  • image = docker_image.nginx.latest → Uses the previously pulled Nginx image.

  • name = "tutorial" → Assign the container a name.

  • ports Mapping:

    • internal = 80 → The Nginx server listens on port 80 inside the container.

    • external = 80 → The container exposes port 80 on the host system, making Nginx accessible via http://localhost:80.


Steps to Execute the Terraform Script

1. Install Docker (If Not Installed)

Run the following commands to install and configure Docker:

sudo apt-get install docker.io
sudo chown $USER /var/run/docker.sock
sudo docker ps

2. Initialize Terraform

terraform init

3. Validate the Configuration

terraform validate

4. Apply the Configuration

terraform apply -auto-approve

5. Verify the Running Container

docker ps

6. Access Nginx in Browser

Open http://localhost:80 in your web browser. You should see the default Nginx welcome page.

7. Destroy the Resources (If Needed)

terraform destroy -auto-approve

This Terraform script automates the deployment of an Nginx container using Docker, making infrastructure provisioning efficient and repeatable.

Terraform Variables

In Terraform, variables are used to make configurations more flexible and reusable. Instead of hardcoding values, variables allow you to pass dynamic inputs into your Terraform scripts.


Types of Variables in Terraform

1. Input Variables (var)

These variables allow you to parameterize configurations, making them more dynamic and reusable.

Defining an Input Variable

You can define a variable using the variable block in a separate file (variables.tf) or directly in main.tf.

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t2.micro"
}
Using an Input Variable in Resources
resource "aws_instance" "example" {
  ami           = "ami-12345678"
  instance_type = var.instance_type
}

2. Environment Variables

Terraform also allows setting variables using environment variables, following the TF_VAR_<variable_name> format.

Example:

export TF_VAR_instance_type="t3.medium"

3. Variable Assignment Methods

Variables can be assigned values in multiple ways:

  1. Using terraform.tfvars File

     instance_type = "t3.small"
    
  2. Command-line Flags

     terraform apply -var="instance_type=t3.large"
    

4. Output Variables (output)

These are used to display values after execution.

Example:

output "instance_ip" {
  value = aws_instance.example.public_ip
}

Benefits of Using Variables

  • Increases reusability of Terraform configurations.

  • Enhances flexibility by allowing different values without modifying the code.

  • Improves security by avoiding hardcoded sensitive information.

Task-03

  • Create a local file using Terraform Hint:

resource "local_file" "devops" {
filename = var.filename
content = var.content
}

In this task, we will use Terraform to create a local file dynamically by defining variables for the filename and content.


Terraform Script (main.tf)

# Terraform Block - Defines required providers
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.0"
    }
  }
}

# Provider Block - Uses the local provider
provider "local" {}

# Resource Block - Creates a local file
resource "local_file" "devops" {
  filename = var.filename
  content  = var.content
}

Defining Variables (variables.tf)

variable "filename" {
  description = "Name of the file to be created"
  type        = string
  default     = "devops.txt"
}

variable "content" {
  description = "Content to be written inside the file"
  type        = string
  default     = "Welcome to DevOps with Terraform!"
}

Execution Steps

1. Initialize Terraform

terraform init

2. Validate the Configuration

terraform validate

3. Apply the Configuration

terraform apply -auto-approve

4. Verify the File Creation

cat devops.txt

You should see:

Welcome to DevOps with Terraform!

5. Destroy the Resources (If Needed)

terraform destroy -auto-approve

Explanation of the Terraform Script

  1. Terraform Block

    • Specifies that the local provider is required to create local files.
  2. Provider Block

    • Uses the local provider, which enables Terraform to interact with local system resources.
  3. Resource Block (local_file)

    • filename = var.filename → Uses a variable for the filename.

    • content = var.content → Uses a variable for the file content.

  4. Variables (variables.tf)

    • Allows customization of the filename and content without modifying the script.

This Terraform configuration helps automate file creation dynamically using variables.

Data Types in Terraform:

Terraform supports various data types for defining variables. These data types ensure that inputs are structured correctly and help in writing robust and error-free infrastructure code.


1. Primitive Data Types

These are the basic data types supported in Terraform.

a. String

A sequence of characters enclosed in quotes ("").

variable "instance_type" {
  type    = string
  default = "t2.micro"
}

b. Number

Can be an integer or a floating-point value.

variable "instance_count" {
  type    = number
  default = 2
}

c. Boolean

Represents true or false values.

variable "enable_monitoring" {
  type    = bool
  default = true
}

2. Complex Data Types

Terraform also supports collections and structural types.

a. List (Ordered Collection of Elements)

A list contains multiple values of the same type.

variable "availability_zones" {
  type    = list(string)
  default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

Accessing List Elements:

availability_zones[0]  # Output: "us-east-1a"

b. Map (Key-Value Pairs)

A map stores key-value pairs for easy retrieval.

variable "instance_tags" {
  type = map(string)
  default = {
    Name        = "Terraform-Instance"
    Environment = "Dev"
  }
}

Accessing Map Elements:

instance_tags["Name"]  # Output: "Terraform-Instance"

c. Set (Unique Unordered Collection)

A set contains unique values but does not maintain order.

variable "allowed_ports" {
  type    = set(number)
  default = [22, 80, 443]
}

3. Structural Data Types

Terraform allows defining nested objects and tuples for more complex configurations.

a. Object (Structured Collection of Key-Value Pairs)

Objects can contain multiple types within them.

variable "server_config" {
  type = object({
    name     = string
    cpu      = number
    is_linux = bool
  })
  default = {
    name     = "web-server"
    cpu      = 4
    is_linux = true
  }
}

b. Tuple (Fixed-Length Collection of Elements)

A tuple allows different types but must follow a fixed structure.

variable "db_config" {
  type    = tuple([string, number, bool])
  default = ["db-server", 8, true]
}

Accessing Tuple Elements:

db_config[0]  # Output: "db-server"

Why Use Data Types in Terraform?

Ensures data consistency and prevents incorrect inputs.
Improves reusability by defining structured configurations.
Reduces errors by validating inputs before execution.

Map:

In Terraform, a map is a key-value pair structure that allows storing multiple values under different keys. This makes configurations more organized, flexible, and reusable.


Example: Defining a Map Variable

variable "file_contents" {
  type = map(string)
  default = {
    "statement1" = "this is cool"
    "statement2" = "this is cooler"
  }
}

Explanation:

  • type = map(string) → Specifies that the variable is a map where both keys and values are strings.

  • Keys"statement1", "statement2"

  • Values"this is cool", "this is cooler"


Using the Map Variable in a Resource

We can use this map variable to create a local file with multiple lines:

resource "local_file" "example" {
  filename = "example.txt"
  content  = "${var.file_contents["statement1"]}\n${var.file_contents["statement2"]}"
}

Accessing Map Values

To retrieve values from a map, use var.map_name["key"]:

output "statement1" {
  value = var.file_contents["statement1"]
}

output "statement2" {
  value = var.file_contents["statement2"]
}

Expected Output:

statement1 = "this is cool"
statement2 = "this is cooler"

Why Use Maps in Terraform?

Organizes data efficiently
Improves reusability of variables
Enables dynamic configurations

Task-04

  • Use terraform to demonstrate usage of List, Set, and Object datatypes

In this task, we will use Terraform to define and use List, Set, and Object data types, apply the configuration, refresh the state, and capture the outputs.


Step 1: Create the Terraform Configuration (main.tf)

# Terraform Block - Defines required providers
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.0"
    }
  }
}

# Provider Block
provider "local" {}

# Variable - List Example
variable "fruits" {
  type    = list(string)
  default = ["Apple", "Banana", "Cherry"]
}

# Variable - Set Example (Ensures Unique Values)
variable "unique_numbers" {
  type    = set(number)
  default = [10, 20, 30, 40, 40]  # Terraform automatically removes duplicates
}

# Variable - Object Example (Complex Data Structure)
variable "server_config" {
  type = object({
    name     = string
    cpu      = number
    is_linux = bool
  })
  default = {
    name     = "web-server"
    cpu      = 4
    is_linux = true
  }
}

# Resource - Creating a Local File using Variables
resource "local_file" "example" {
  filename = "output.txt"
  content  = <<EOT
List Example:
Fruit 1: ${var.fruits[0]}
Fruit 2: ${var.fruits[1]}
Fruit 3: ${var.fruits[2]}

Set Example:
Numbers: ${join(", ", tostring(var.unique_numbers))}

Object Example:
Server Name: ${var.server_config.name}
CPU Cores: ${var.server_config.cpu}
Is Linux: ${var.server_config.is_linux}
EOT
}

# Output Blocks
output "list_example" {
  value = var.fruits
}

output "set_example" {
  value = var.unique_numbers
}

output "object_example" {
  value = var.server_config
}

Step 2: Execute the Terraform Script

1. Initialize Terraform

terraform init

2. Apply the Configuration

terraform apply -auto-approve

Expected Output:

list_example = [
  "Apple",
  "Banana",
  "Cherry"
]
set_example = [
  10,
  20,
  30,
  40
]
object_example = {
  "name" = "web-server"
  "cpu" = 4
  "is_linux" = true
}

Step 3: Refresh the Terraform State

To update the Terraform state based on changes in the configuration file, run:

terraform refresh

This ensures that Terraform reloads the latest values of variables from the configuration.


Step 4: Verify the Output File (output.txt)

Check the contents of the generated file:

cat output.txt

Expected Content:

List Example:
Fruit 1: Apple
Fruit 2: Banana
Fruit 3: Cherry

Set Example:
Numbers: 10, 20, 30, 40

Object Example:
Server Name: web-server
CPU Cores: 4
Is Linux: true

This Terraform setup demonstrates how to work with List, Set, and Object types effectively while managing infrastructure as code.

0
Subscribe to my newsletter

Read articles from Vanshika Sharma directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Vanshika Sharma
Vanshika Sharma

I am currently a B.Tech student pursuing Computer Science with a specialization in Data Science at I.T.S Engineering College. I am always excited to learn and explore new things to increase my knowledge. I have good knowledge of programming languages such as C, Python, Java, and web development.