Press "Enter" to skip to content

Install Terraform on Ubuntu 24.04

What is Terraform?

Terraform is an open-source Infrastructure as Code (IaC) tool used for automating the management and configuration of cloud services and infrastructure. It allows you to define and provision data center infrastructure, such as virtual machines, networks, and storage, using declarative configuration files. Terraform supports multiple cloud providers, including AWS, Azure, and Google Cloud, enabling cross-platform infrastructure deployment and management.

Key features include:

  1. Infrastructure Configuration: Define resources through code.
  2. Cross-Platform Deployment: Manage resources across cloud platforms.
  3. Version Control: Track changes in infrastructure.
  4. Automated Management: Automate the creation, updating, and destruction of resources.

Terraform is widely used in cloud computing, DevOps, and automation scenarios.

In this article, I will guide you to install Terraform on Ubuntu 24.04.

## OS info
## OS version
root@terraform:~# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 24.04 LTS
Release:    24.04
Codename:   noble

## kernel version
root@terraform:~# uname -r
6.8.0-40-generic

## IP
root@terraform:~# ip addr list
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute
       valid_lft forever preferred_lft forever
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:d3:31:9a brd ff:ff:ff:ff:ff:ff
    altname enp2s0
    inet 192.168.70.4/24 brd 192.168.70.255 scope global ens160
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fed3:319a/64 scope link
       valid_lft forever preferred_lft forever

## hostname
root@terraform:~# hostname
terraform

## local DNS resolve
root@terraform:~# cat /etc/hosts
127.0.0.1 localhost
192.168.70.4 terraform

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

Install Terraform

## add apt repo on server
root@terraform:~# wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
--2024-08-12 04:29:23--  https://apt.releases.hashicorp.com/gpg
Resolving apt.releases.hashicorp.com (apt.releases.hashicorp.com)... 13.33.183.115, 13.33.183.48, 13.33.183.121, ...
Connecting to apt.releases.hashicorp.com (apt.releases.hashicorp.com)|13.33.183.115|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3980 (3.9K) [binary/octet-stream]
Saving to: ‘STDOUT’

-                                                                                          100%[========================================================================================================================================================================================================================================>]   3.89K  --.-KB/s    in 0s

2024-08-12 04:29:24 (228 MB/s) - written to stdout [3980/3980]

## add keyrings
root@terraform:~# echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/hashicorp.list
deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com noble main

## make cache
root@terraform:~# apt update
Get:1 https://apt.releases.hashicorp.com noble InRelease [12.9 kB]
Hit:2 http://ports.ubuntu.com/ubuntu-ports noble InRelease
Get:3 https://apt.releases.hashicorp.com noble/main arm64 Packages [78.0 kB]
Hit:4 http://ports.ubuntu.com/ubuntu-ports noble-updates InRelease
Hit:5 http://ports.ubuntu.com/ubuntu-ports noble-backports InRelease
Hit:6 http://ports.ubuntu.com/ubuntu-ports noble-security InRelease
Fetched 90.8 kB in 2s (38.0 kB/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
All packages are up to date.

## install terraform
root@terraform:~# apt install terraform -y
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following NEW packages will be installed:
  terraform
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 25.5 MB of archives.
After this operation, 85.1 MB of additional disk space will be used.
Get:1 https://apt.releases.hashicorp.com noble/main arm64 terraform arm64 1.9.4-1 [25.5 MB]
Fetched 25.5 MB in 30s (851 kB/s)
Selecting previously unselected package terraform.
(Reading database ... 129649 files and directories currently installed.)
Preparing to unpack .../terraform_1.9.4-1_arm64.deb ...
Unpacking terraform (1.9.4-1) ...
Setting up terraform (1.9.4-1) ...
Scanning processes...
Scanning linux images...

Running kernel seems to be up-to-date.

No services need to be restarted.

No containers need to be restarted.

No user sessions are running outdated binaries.

No VM guests are running outdated hypervisor (qemu) binaries on this host.

Evaluate and test

## install docker.io first
root@terraform:~# apt install docker.io -y
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  bridge-utils containerd dns-root-data dnsmasq-base pigz runc ubuntu-fan
Suggested packages:
  ifupdown aufs-tools cgroupfs-mount | cgroup-lite debootstrap docker-buildx docker-compose-v2 docker-doc rinse zfs-fuse | zfsutils
The following NEW packages will be installed:
  bridge-utils containerd dns-root-data dnsmasq-base docker.io pigz runc ubuntu-fan
0 upgraded, 8 newly installed, 0 to remove and 0 not upgraded.
Need to get 57.6 MB of archives.
After this operation, 237 MB of additional disk space will be used.
Get:1 http://ports.ubuntu.com/ubuntu-ports noble/universe arm64 pigz arm64 2.8-1 [60.7 kB]
Get:2 http://ports.ubuntu.com/ubuntu-ports noble/main arm64 bridge-utils arm64 1.7.1-1ubuntu2 [34.2 kB]
Get:3 http://ports.ubuntu.com/ubuntu-ports noble/main arm64 runc arm64 1.1.12-0ubuntu3 [7,913 kB]
Get:4 http://ports.ubuntu.com/ubuntu-ports noble/main arm64 containerd arm64 1.7.12-0ubuntu4 [27.8 MB]
Get:5 http://ports.ubuntu.com/ubuntu-ports noble/main arm64 dns-root-data all 2023112702~willsync1 [4,450 B]
Get:6 http://ports.ubuntu.com/ubuntu-ports noble/main arm64 dnsmasq-base arm64 2.90-2build2 [366 kB]
Get:7 http://ports.ubuntu.com/ubuntu-ports noble/universe arm64 docker.io arm64 24.0.7-0ubuntu4 [21.4 MB]
Get:8 http://ports.ubuntu.com/ubuntu-ports noble/universe arm64 ubuntu-fan all 0.12.16 [35.2 kB]
Fetched 57.6 MB in 3min 43s (258 kB/s)
Preconfiguring packages ...
Selecting previously unselected package pigz.
(Reading database ... 129650 files and directories currently installed.)
Preparing to unpack .../0-pigz_2.8-1_arm64.deb ...
Unpacking pigz (2.8-1) ...
Selecting previously unselected package bridge-utils.
Preparing to unpack .../1-bridge-utils_1.7.1-1ubuntu2_arm64.deb ...
Unpacking bridge-utils (1.7.1-1ubuntu2) ...
Selecting previously unselected package runc.
Preparing to unpack .../2-runc_1.1.12-0ubuntu3_arm64.deb ...
Unpacking runc (1.1.12-0ubuntu3) ...
Selecting previously unselected package containerd.
Preparing to unpack .../3-containerd_1.7.12-0ubuntu4_arm64.deb ...
Unpacking containerd (1.7.12-0ubuntu4) ...
Selecting previously unselected package dns-root-data.
Preparing to unpack .../4-dns-root-data_2023112702~willsync1_all.deb ...
Unpacking dns-root-data (2023112702~willsync1) ...
Selecting previously unselected package dnsmasq-base.
Preparing to unpack .../5-dnsmasq-base_2.90-2build2_arm64.deb ...
Unpacking dnsmasq-base (2.90-2build2) ...
Selecting previously unselected package docker.io.
Preparing to unpack .../6-docker.io_24.0.7-0ubuntu4_arm64.deb ...
Unpacking docker.io (24.0.7-0ubuntu4) ...
Selecting previously unselected package ubuntu-fan.
Preparing to unpack .../7-ubuntu-fan_0.12.16_all.deb ...
Unpacking ubuntu-fan (0.12.16) ...
Setting up dnsmasq-base (2.90-2build2) ...
Setting up runc (1.1.12-0ubuntu3) ...
Setting up dns-root-data (2023112702~willsync1) ...
Setting up bridge-utils (1.7.1-1ubuntu2) ...
Setting up pigz (2.8-1) ...
Setting up containerd (1.7.12-0ubuntu4) ...
Created symlink /etc/systemd/system/multi-user.target.wants/containerd.service → /usr/lib/systemd/system/containerd.service.
Setting up ubuntu-fan (0.12.16) ...
Created symlink /etc/systemd/system/multi-user.target.wants/ubuntu-fan.service → /usr/lib/systemd/system/ubuntu-fan.service.
Setting up docker.io (24.0.7-0ubuntu4) ...
info: Selecting GID from range 100 to 999 ...
info: Adding group `docker' (GID 109) ...
Created symlink /etc/systemd/system/multi-user.target.wants/docker.service → /usr/lib/systemd/system/docker.service.
Created symlink /etc/systemd/system/sockets.target.wants/docker.socket → /usr/lib/systemd/system/docker.socket.
Processing triggers for dbus (1.14.10-4ubuntu4) ...
Processing triggers for man-db (2.12.0-4build2) ...
Scanning processes...
Scanning linux images...

Running kernel seems to be up-to-date.

No services need to be restarted.

No containers need to be restarted.

No user sessions are running outdated binaries.

No VM guests are running outdated hypervisor (qemu) binaries on this host.

## enable & start service
root@terraform:~# systemctl enable docker
root@terraform:~# systemctl restart docker

## check service status
root@terraform:~# systemctl status docker
● docker.service - Docker Application Container Engine
     Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; preset: enabled)
     Active: active (running) since Mon 2024-08-12 04:52:21 UTC; 5s ago
TriggeredBy: ● docker.socket
       Docs: https://docs.docker.com
   Main PID: 2607 (dockerd)
      Tasks: 9
     Memory: 24.0M (peak: 24.3M)
        CPU: 99ms
     CGroup: /system.slice/docker.service
             └─2607 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

Aug 12 04:52:21 terraform dockerd[2607]: time="2024-08-12T04:52:21.709984369Z" level=info msg="Starting up"
Aug 12 04:52:21 terraform dockerd[2607]: time="2024-08-12T04:52:21.710270660Z" level=info msg="detected 127.0.0.53 nameserver, assuming systemd-resolved, so using resolv.conf: /run/systemd/resolve/resolv.conf"
Aug 12 04:52:21 terraform dockerd[2607]: time="2024-08-12T04:52:21.724121351Z" level=info msg="[graphdriver] using prior storage driver: overlay2"
Aug 12 04:52:21 terraform dockerd[2607]: time="2024-08-12T04:52:21.724333017Z" level=info msg="Loading containers: start."
Aug 12 04:52:21 terraform dockerd[2607]: time="2024-08-12T04:52:21.862416504Z" level=info msg="Default bridge (docker0) is assigned with an IP address 172.17.0.0/16. Daemon option --bip can be used to set a preferred IP address"
Aug 12 04:52:21 terraform dockerd[2607]: time="2024-08-12T04:52:21.877264068Z" level=info msg="Loading containers: done."
Aug 12 04:52:21 terraform dockerd[2607]: time="2024-08-12T04:52:21.881790062Z" level=info msg="Docker daemon" commit=24.0.7-0ubuntu4 graphdriver=overlay2 version=24.0.7
Aug 12 04:52:21 terraform dockerd[2607]: time="2024-08-12T04:52:21.881816104Z" level=info msg="Daemon has completed initialization"
Aug 12 04:52:21 terraform dockerd[2607]: time="2024-08-12T04:52:21.889843302Z" level=info msg="API listen on /run/docker.sock"
Aug 12 04:52:21 terraform systemd[1]: Started docker.service - Docker Application Container Engine.

## create a dir for test
root@terraform:~# mkdir /develop/terraform/docker -p
root@terraform:~# cd /develop/terraform/docker/

## write a main.tf to create a container as nginx service
root@terraform:/develop/terraform/docker# vim main.tf
root@terraform:/develop/terraform/docker# cat main.tf
terraform {
  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 3.0.1"
    }
  }
}

provider "docker" {}

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

resource "docker_container" "nginx" {
  image = docker_image.nginx.image_id
  name  = "tutorial"

  ports {
    internal = 80
    external = 8000
  }
}

## Initialize the project, which downloads a plugin called a provider that lets Terraform interact with Docker
root@terraform:/develop/terraform/docker# terraform init
Initializing the backend...
Initializing provider plugins...
- Finding kreuzwerker/docker versions matching "~> 3.0.1"...
- Installing kreuzwerker/docker v3.0.2...
- Installed kreuzwerker/docker v3.0.2 (self-signed, key ID BD080C4571C6104C)
Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.html
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

## Provision the NGINX server container with apply. When Terraform asks you to confirm type yes and press ENTER
root@terraform:/develop/terraform/docker# 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)
      + container_read_refresh_timeout_milliseconds = 15000
      + entrypoint                                  = (known after apply)
      + env                                         = (known after apply)
      + exit_code                                   = (known after apply)
      + hostname                                    = (known after apply)
      + id                                          = (known after apply)
      + image                                       = (known after apply)
      + init                                        = (known after apply)
      + ipc_mode                                    = (known after apply)
      + log_driver                                  = (known after apply)
      + logs                                        = false
      + must_run                                    = true
      + name                                        = "tutorial"
      + network_data                                = (known after apply)
      + read_only                                   = false
      + remove_volumes                              = true
      + restart                                     = "no"
      + rm                                          = false
      + runtime                                     = (known after apply)
      + security_opts                               = (known after apply)
      + shm_size                                    = (known after apply)
      + start                                       = true
      + stdin_open                                  = false
      + stop_signal                                 = (known after apply)
      + stop_timeout                                = (known after apply)
      + tty                                         = false
      + wait                                        = false
      + wait_timeout                                = 60

      + healthcheck (known after apply)

      + labels (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)
      + image_id     = (known after apply)
      + keep_locally = false
      + name         = "nginx"
      + 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: Still creating... [20s elapsed]
docker_image.nginx: Still creating... [30s elapsed]
docker_image.nginx: Still creating... [40s elapsed]
docker_image.nginx: Still creating... [50s elapsed]
docker_image.nginx: Still creating... [1m0s elapsed]
docker_image.nginx: Still creating... [1m10s elapsed]
docker_image.nginx: Still creating... [1m20s elapsed]
docker_image.nginx: Still creating... [1m30s elapsed]
docker_image.nginx: Still creating... [1m40s elapsed]
docker_image.nginx: Creation complete after 1m41s [id=sha256:43b17fe33c4b4cf8de762123d33e02f2ed0c5e1178002f533d4fb5df1e05fb76nginx]
docker_container.nginx: Creating...
docker_container.nginx: Creation complete after 0s [id=f8c48d00497c2769541ee4929cfd69cfc86a436ee971636b6a21ca13e80dea33]

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

  • Can find container already start as well
## check container status
root@terraform:/develop/terraform/docker# docker ps -a
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                  NAMES
f8c48d00497c   43b17fe33c4b   "/docker-entrypoint.…"   4 minutes ago   Up 4 minutes   0.0.0.0:8000->80/tcp   tutorial

## check port listening status
root@terraform:/develop/terraform/docker# netstat -luntp | grep -i 8000
tcp        0      0 0.0.0.0:8000            0.0.0.0:*               LISTEN      2856/docker-proxy

  • For further action, can run ‘terraform destroy’ to stop the container.
root@terraform:/develop/terraform/docker# terraform destroy
docker_image.nginx: Refreshing state... [id=sha256:43b17fe33c4b4cf8de762123d33e02f2ed0c5e1178002f533d4fb5df1e05fb76nginx]
docker_container.nginx: Refreshing state... [id=f8c48d00497c2769541ee4929cfd69cfc86a436ee971636b6a21ca13e80dea33]

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
      - container_read_refresh_timeout_milliseconds = 15000 -> null
      - cpu_shares                                  = 0 -> null
      - dns                                         = [] -> null
      - dns_opts                                    = [] -> null
      - dns_search                                  = [] -> null
      - entrypoint                                  = [
          - "/docker-entrypoint.sh",
        ] -> null
      - env                                         = [] -> null
      - group_add                                   = [] -> null
      - hostname                                    = "f8c48d00497c" -> null
      - id                                          = "f8c48d00497c2769541ee4929cfd69cfc86a436ee971636b6a21ca13e80dea33" -> null
      - image                                       = "sha256:43b17fe33c4b4cf8de762123d33e02f2ed0c5e1178002f533d4fb5df1e05fb76" -> null
      - init                                        = false -> null
      - ipc_mode                                    = "private" -> 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_prefix_length = 0
              - ip_address                = "172.17.0.2"
              - ip_prefix_length          = 16
              - mac_address               = "02:42:ac:11:00:02"
              - network_name              = "bridge"
                # (2 unchanged attributes hidden)
            },
        ] -> 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
      - runtime                                     = "runc" -> null
      - security_opts                               = [] -> null
      - shm_size                                    = 64 -> null
      - start                                       = true -> null
      - stdin_open                                  = false -> null
      - stop_signal                                 = "SIGQUIT" -> null
      - stop_timeout                                = 0 -> null
      - storage_opts                                = {} -> null
      - sysctls                                     = {} -> null
      - tmpfs                                       = {} -> null
      - tty                                         = false -> null
      - wait                                        = false -> null
      - wait_timeout                                = 60 -> null
        # (7 unchanged attributes hidden)

      - 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:43b17fe33c4b4cf8de762123d33e02f2ed0c5e1178002f533d4fb5df1e05fb76nginx" -> null
      - image_id     = "sha256:43b17fe33c4b4cf8de762123d33e02f2ed0c5e1178002f533d4fb5df1e05fb76" -> null
      - keep_locally = false -> null
      - name         = "nginx" -> null
      - repo_digest  = "nginx@sha256:6af79ae5de407283dcea8b00d5c37ace95441fd58a8b1d2aa1ed93f5511bb18c" -> 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=f8c48d00497c2769541ee4929cfd69cfc86a436ee971636b6a21ca13e80dea33]
docker_container.nginx: Still destroying... [id=f8c48d00497c2769541ee4929cfd69cfc86a436ee971636b6a21ca13e80dea33, 10s elapsed]
docker_container.nginx: Destruction complete after 12s
docker_image.nginx: Destroying... [id=sha256:43b17fe33c4b4cf8de762123d33e02f2ed0c5e1178002f533d4fb5df1e05fb76nginx]
docker_image.nginx: Destruction complete after 0s

Destroy complete! Resources: 2 destroyed.

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *