Terraform - Tutorial

Page content

I need to review Terraform sometimes, so I left a tutorial+terminology so that I can recall it quickly.

Install

https://www.terraform.io/downloads https://learn.hashicorp.com/tutorials/terraform/install-cli

On Ubuntu 21:

sudo apt-get update && sudo apt-get install -y gnupg software-properties-common curl
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
sudo apt-get update && sudo apt-get install terraform

Check:

➜ terraform --version
Terraform v1.1.2
on linux_amd64

Configure tab completion.

terraform -install-autocomplete

Quickstart: deploy Docker container on my local machine

Create a project (directory) and files

Just follow below without thinking the lines at first (I left a brief explanation later):

mkdir learn-terraform-docker-container
cd learn-terraform-docker-container

Create main.tf:

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

provider "docker" {}

resource "docker_image" "nginx" {
  name         = "nginx:latest"
  keep_locally = false
}

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

Deploy

Initialize the project (downloading plugins):

terraform init

Here is the result:

➜ tree -a
.
├── main.tf
├── .terraform
│   └── providers
│       └── registry.terraform.io
│           └── kreuzwerker
│               └── docker
│                   └── 2.13.0
│                       └── linux_amd64
│                           ├── CHANGELOG.md
│                           ├── LICENSE
│                           ├── README.md
│                           └── terraform-provider-docker_v2.13.0
└── .terraform.lock.hcl

Deploy (this is the full log):

➜ terraform apply

Terraform used the selected providers to generate the following execution plan. Resource
actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # docker_container.nginx will be created
  + resource "docker_container" "nginx" {
      + attach           = false
      + bridge           = (known after apply)
      + command          = (known after apply)
      + container_logs   = (known after apply)
      + entrypoint       = (known after apply)
      + env              = (known after apply)
      + exit_code        = (known after apply)
      + gateway          = (known after apply)
      + hostname         = (known after apply)
      + id               = (known after apply)
      + image            = (known after apply)
      + init             = (known after apply)
      + ip_address       = (known after apply)
      + ip_prefix_length = (known after apply)
      + ipc_mode         = (known after apply)
      + log_driver       = "json-file"
      + logs             = false
      + must_run         = true
      + name             = "tutorial"
      + network_data     = (known after apply)
      + read_only        = false
      + remove_volumes   = true
      + restart          = "no"
      + rm               = false
      + security_opts    = (known after apply)
      + shm_size         = (known after apply)
      + start            = true
      + stdin_open       = false
      + tty              = false

      + healthcheck {
          + interval     = (known after apply)
          + retries      = (known after apply)
          + start_period = (known after apply)
          + test         = (known after apply)
          + timeout      = (known after apply)
        }

      + labels {
          + label = (known after apply)
          + value = (known after apply)
        }

      + ports {
          + external = 8000
          + internal = 80
          + ip       = "0.0.0.0"
          + protocol = "tcp"
        }
    }

  # docker_image.nginx will be created
  + resource "docker_image" "nginx" {
      + id           = (known after apply)
      + keep_locally = false
      + latest       = (known after apply)
      + name         = "nginx:latest"
      + output       = (known after apply)
      + repo_digest  = (known after apply)
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

docker_image.nginx: Creating...
docker_image.nginx: Still creating... [10s elapsed]
docker_image.nginx: Creation complete after 10s [id=sha256:605c77e624ddb75e6110f997c58876baa13f8754486b461117934b24a9dc3a85nginx:latest]
docker_container.nginx: Creating...
docker_container.nginx: Creation complete after 1s [id=2d0d9fd949df2b17d14256ce47e8b06d8eef2470cad634234f60bd46fb486f44]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Check:

➜ sudo docker ps -a
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                  NAMES
2d0d9fd949df   605c77e624dd   "/docker-entrypoint.…"   7 minutes ago   Up 7 minutes   0.0.0.0:8000->80/tcp   tutorial

You can see Nginx Hello world page at http://localhost:8000.

After the terraform apply the command created the file terraform.tfstate.

Shutdown the container

terraform destroy(Here is the full log):

➜ terraform destroy
docker_image.nginx: Refreshing state... [id=sha256:605c77e624ddb75e6110f997c58876baa13f8754486b461117934b24a9dc3a85nginx:latest]
docker_container.nginx: Refreshing state... [id=2d0d9fd949df2b17d14256ce47e8b06d8eef2470cad634234f60bd46fb486f44]

Terraform used the selected providers to generate the following execution plan. Resource
actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # docker_container.nginx will be destroyed
  - resource "docker_container" "nginx" {
      - attach            = false -> null
      - command           = [
          - "nginx",
          - "-g",
          - "daemon off;",
        ] -> null
      - cpu_shares        = 0 -> null
      - dns               = [] -> null
      - dns_opts          = [] -> null
      - dns_search        = [] -> null
      - entrypoint        = [
          - "/docker-entrypoint.sh",
        ] -> null
      - env               = [] -> null
      - gateway           = "172.17.0.1" -> null
      - group_add         = [] -> null
      - hostname          = "2d0d9fd949df" -> null
      - id                = "2d0d9fd949df2b17d14256ce47e8b06d8eef2470cad634234f60bd46fb486f44" -> null
      - image             = "sha256:605c77e624ddb75e6110f997c58876baa13f8754486b461117934b24a9dc3a85" -> null
      - init              = false -> null
      - ip_address        = "172.17.0.2" -> null
      - ip_prefix_length  = 16 -> null
      - ipc_mode          = "private" -> null
      - links             = [] -> null
      - log_driver        = "json-file" -> null
      - log_opts          = {} -> null
      - logs              = false -> null
      - max_retry_count   = 0 -> null
      - memory            = 0 -> null
      - memory_swap       = 0 -> null
      - must_run          = true -> null
      - name              = "tutorial" -> null
      - network_data      = [
          - {
              - gateway                   = "172.17.0.1"
              - global_ipv6_address       = ""
              - global_ipv6_prefix_length = 0
              - ip_address                = "172.17.0.2"
              - ip_prefix_length          = 16
              - ipv6_gateway              = ""
              - network_name              = "bridge"
            },
        ] -> null
      - network_mode      = "default" -> null
      - privileged        = false -> null
      - publish_all_ports = false -> null
      - read_only         = false -> null
      - remove_volumes    = true -> null
      - restart           = "no" -> null
      - rm                = false -> null
      - security_opts     = [] -> null
      - shm_size          = 64 -> null
      - start             = true -> null
      - stdin_open        = false -> null
      - sysctls           = {} -> null
      - tmpfs             = {} -> null
      - tty               = false -> null

      - ports {
          - external = 8000 -> null
          - internal = 80 -> null
          - ip       = "0.0.0.0" -> null
          - protocol = "tcp" -> null
        }
    }

  # docker_image.nginx will be destroyed
  - resource "docker_image" "nginx" {
      - id           = "sha256:605c77e624ddb75e6110f997c58876baa13f8754486b461117934b24a9dc3a85nginx:latest" -> null
      - keep_locally = false -> null
      - latest       = "sha256:605c77e624ddb75e6110f997c58876baa13f8754486b461117934b24a9dc3a85" -> null
      - name         = "nginx:latest" -> null
      - repo_digest  = "nginx@sha256:0d17b565c37bcbd895e9d92315a05c1c3c9a29f762b011a10c54a66cd53c9b31" -> null
    }

Plan: 0 to add, 0 to change, 2 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

docker_container.nginx: Destroying... [id=2d0d9fd949df2b17d14256ce47e8b06d8eef2470cad634234f60bd46fb486f44]
docker_container.nginx: Destruction complete after 1s
docker_image.nginx: Destroying... [id=sha256:605c77e624ddb75e6110f997c58876baa13f8754486b461117934b24a9dc3a85nginx:latest]
docker_image.nginx: Destruction complete after 0s

Destroy complete! Resources: 2 destroyed.

Basic configuration

https://www.terraform.io/language

Here is the general format of the Terraform file:

<BLOCK TYPE> "<BLOCK LABEL>" "<BLOCK LABEL>" {
  # Block body
  <IDENTIFIER> = <EXPRESSION> # Argument
}

Terraform block

Official document:

Each terraform block can contain a number of settings related to Terraform’s behavior. Within a terraform block, only constant values can be used;

Provider blocks

Official document:

Terraform relies on plugins called “providers” to interact with cloud providers, SaaS providers, and other APIs.

Terraform configurations must declare which providers they require so that Terraform can install and use them. Additionally, some providers require configuration (like endpoint URLs or cloud regions) before they can be used.

Resource blocks

Official document:

Resources are the most important element in the Terraform language. Each resource block describes one or more infrastructure objects, such as virtual networks, compute instances, or higher-level components such as DNS records.

resource "{{ type_of_resource }}" "{{ given_local_name }}"

Each resource type is implemented by a provider, which is a plugin for Terraform that offers a collection of resource types.

In the example above, we use docker_image and docker_container, that are provided by docker provider.

The `{{ given_local_name }} is used when other resource refer to this resource.

Every time you use a new resource type, please refer to the documentation from provider.

(Sideway) How to auto-deploy

An auto-deploy is not Terraform’s responsibility. Instead, CI/CD (GitLab Runner, GitHub Actions, Jenkins) has a role to auto-deploy.