Changing Infrastructure using Terraform
Change Infrastructure
In the previous post on Getting Started with Terraform, we looked at creating and destroying infrastructure using Terraform.
In this post, we will look at changing infra.
pradeep:~$cat 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
}
}
Terraform Init
pradeep:~$terraform init
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of kreuzwerker/docker from the dependency lock file
- Using previously-installed kreuzwerker/docker v2.13.0
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.
pradeep:~$
Terraform Apply
pradeep:~$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 17s [id=sha256:fa5269854a5e615e51a72b17ad3fd1e01268f278a6684c8ed3c5f0cdce3f230bnginx:latest]
docker_container.nginx: Creating...
docker_container.nginx: Creation complete after 1s [id=b887bf2d184ccaffca120e0a70f30977bf06a22e6b07f64c22a04cb7af745ee7]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
pradeep:~$
Now update the external port number of your container. Change the docker_container.nginx
resource under the provider block in main.tf
by replacing the ports.external
value of 8000
with 8080
pradeep:~$cat 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 = 8080
}
}
pradeep:~$
Terraform Plan
pradeep:~$terraform plan
docker_image.nginx: Refreshing state... [id=sha256:fa5269854a5e615e51a72b17ad3fd1e01268f278a6684c8ed3c5f0cdce3f230bnginx:latest]
docker_container.nginx: Refreshing state... [id=b887bf2d184ccaffca120e0a70f30977bf06a22e6b07f64c22a04cb7af745ee7]
Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraform since the last "terraform apply":
# docker_container.nginx has changed
~ resource "docker_container" "nginx" {
+ dns = []
+ dns_opts = []
+ dns_search = []
+ group_add = []
id = "b887bf2d184ccaffca120e0a70f30977bf06a22e6b07f64c22a04cb7af745ee7"
+ links = []
+ log_opts = {}
name = "tutorial"
+ sysctls = {}
+ tmpfs = {}
# (31 unchanged attributes hidden)
# (1 unchanged block hidden)
}
Unless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may include actions to undo or respond to these changes.
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# docker_container.nginx must be replaced
-/+ resource "docker_container" "nginx" {
+ bridge = (known after apply)
~ command = [
- "nginx",
- "-g",
- "daemon off;",
] -> (known after apply)
+ container_logs = (known after apply)
- cpu_shares = 0 -> null
- dns = [] -> null
- dns_opts = [] -> null
- dns_search = [] -> null
~ entrypoint = [
- "/docker-entrypoint.sh",
] -> (known after apply)
~ env = [] -> (known after apply)
+ exit_code = (known after apply)
~ gateway = "172.17.0.1" -> (known after apply)
- group_add = [] -> null
~ hostname = "b887bf2d184c" -> (known after apply)
~ id = "b887bf2d184ccaffca120e0a70f30977bf06a22e6b07f64c22a04cb7af745ee7" -> (known after apply)
~ init = false -> (known after apply)
~ ip_address = "172.17.0.2" -> (known after apply)
~ ip_prefix_length = 16 -> (known after apply)
~ ipc_mode = "private" -> (known after apply)
- links = [] -> null
- log_opts = {} -> null
- max_retry_count = 0 -> null
- memory = 0 -> null
- memory_swap = 0 -> null
name = "tutorial"
~ 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"
},
] -> (known after apply)
- network_mode = "default" -> null
- privileged = false -> null
- publish_all_ports = false -> null
~ security_opts = [] -> (known after apply)
~ shm_size = 64 -> (known after apply)
- sysctls = {} -> null
- tmpfs = {} -> null
# (12 unchanged attributes hidden)
+ 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 -> 8080 # forces replacement
# (3 unchanged attributes hidden)
}
}
Plan: 1 to add, 0 to change, 1 to destroy.
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
pradeep:~$
This update changes the port number your container uses to serve your nginx server. The Docker provider knows that it cannot change the port of a container after it has been created, so Terraform will destroy the old container and create a new one.
docker ps
pradeep:~$docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b887bf2d184c fa5269854a5e "/docker-entrypoint.…" 4 minutes ago Up 4 minutes 0.0.0.0:8000->80/tcp tutorial
pradeep:~$
Terraform Apply
pradeep:~$terraform apply
docker_image.nginx: Refreshing state... [id=sha256:fa5269854a5e615e51a72b17ad3fd1e01268f278a6684c8ed3c5f0cdce3f230bnginx:latest]
docker_container.nginx: Refreshing state... [id=b887bf2d184ccaffca120e0a70f30977bf06a22e6b07f64c22a04cb7af745ee7]
Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraform since the last "terraform apply":
# docker_container.nginx has changed
~ resource "docker_container" "nginx" {
+ dns = []
+ dns_opts = []
+ dns_search = []
+ group_add = []
id = "b887bf2d184ccaffca120e0a70f30977bf06a22e6b07f64c22a04cb7af745ee7"
+ links = []
+ log_opts = {}
name = "tutorial"
+ sysctls = {}
+ tmpfs = {}
# (31 unchanged attributes hidden)
# (1 unchanged block hidden)
}
Unless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may include actions to undo or respond to these changes.
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# docker_container.nginx must be replaced
-/+ resource "docker_container" "nginx" {
+ bridge = (known after apply)
~ command = [
- "nginx",
- "-g",
- "daemon off;",
] -> (known after apply)
+ container_logs = (known after apply)
- cpu_shares = 0 -> null
- dns = [] -> null
- dns_opts = [] -> null
- dns_search = [] -> null
~ entrypoint = [
- "/docker-entrypoint.sh",
] -> (known after apply)
~ env = [] -> (known after apply)
+ exit_code = (known after apply)
~ gateway = "172.17.0.1" -> (known after apply)
- group_add = [] -> null
~ hostname = "b887bf2d184c" -> (known after apply)
~ id = "b887bf2d184ccaffca120e0a70f30977bf06a22e6b07f64c22a04cb7af745ee7" -> (known after apply)
~ init = false -> (known after apply)
~ ip_address = "172.17.0.2" -> (known after apply)
~ ip_prefix_length = 16 -> (known after apply)
~ ipc_mode = "private" -> (known after apply)
- links = [] -> null
- log_opts = {} -> null
- max_retry_count = 0 -> null
- memory = 0 -> null
- memory_swap = 0 -> null
name = "tutorial"
~ 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"
},
] -> (known after apply)
- network_mode = "default" -> null
- privileged = false -> null
- publish_all_ports = false -> null
~ security_opts = [] -> (known after apply)
~ shm_size = 64 -> (known after apply)
- sysctls = {} -> null
- tmpfs = {} -> null
# (12 unchanged attributes hidden)
+ 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 -> 8080 # forces replacement
# (3 unchanged attributes hidden)
}
}
Plan: 1 to add, 0 to change, 1 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_container.nginx: Destroying... [id=b887bf2d184ccaffca120e0a70f30977bf06a22e6b07f64c22a04cb7af745ee7]
docker_container.nginx: Destruction complete after 0s
docker_container.nginx: Creating...
docker_container.nginx: Creation complete after 1s [id=712fa3cb7700e8c1beb057e5535e34dd899ccd59de5fd49a93b2cf65b6423401]
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
pradeep:~$
The prefix -/+
means that Terraform will destroy and recreate the resource, rather than updating it in-place. Terraform can update some attributes in-place (indicated with the ~ prefix), but changing the port for a Docker container requires recreating it. Terraform handles these details for you, and the execution plan displays what Terraform will do.
Additionally, the execution plan shows that the port change is what forces Terraform to replace the container. Using this information, you can adjust your changes to to avoid destructive updates if necessary.
Once again, Terraform prompts for approval of the execution plan before proceeding. Answer yes to execute the planned steps.
docker ps
pradeep:~$docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
712fa3cb7700 fa5269854a5e "/docker-entrypoint.…" 23 seconds ago Up 22 seconds 0.0.0.0:8080->80/tcp tutorial
pradeep:~$
As indicated by the execution plan, Terraform first destroyed the existing container and then created a new one in its place. You can use terraform show
again to have Terraform print out the new values associated with this container.
Terraform Show
pradeep:~$terraform show
# docker_container.nginx:
resource "docker_container" "nginx" {
attach = false
command = [
"nginx",
"-g",
"daemon off;",
]
cpu_shares = 0
entrypoint = [
"/docker-entrypoint.sh",
]
env = []
gateway = "172.17.0.1"
hostname = "712fa3cb7700"
id = "712fa3cb7700e8c1beb057e5535e34dd899ccd59de5fd49a93b2cf65b6423401"
image = "sha256:fa5269854a5e615e51a72b17ad3fd1e01268f278a6684c8ed3c5f0cdce3f230b"
init = false
ip_address = "172.17.0.2"
ip_prefix_length = 16
ipc_mode = "private"
log_driver = "json-file"
logs = false
max_retry_count = 0
memory = 0
memory_swap = 0
must_run = true
name = "tutorial"
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"
},
]
network_mode = "default"
privileged = false
publish_all_ports = false
read_only = false
remove_volumes = true
restart = "no"
rm = false
security_opts = []
shm_size = 64
start = true
stdin_open = false
tty = false
ports {
external = 8080
internal = 80
ip = "0.0.0.0"
protocol = "tcp"
}
}
# docker_image.nginx:
resource "docker_image" "nginx" {
id = "sha256:fa5269854a5e615e51a72b17ad3fd1e01268f278a6684c8ed3c5f0cdce3f230bnginx:latest"
keep_locally = false
latest = "sha256:fa5269854a5e615e51a72b17ad3fd1e01268f278a6684c8ed3c5f0cdce3f230b"
name = "nginx:latest"
repo_digest = "nginx@sha256:859ab6768a6f26a79bc42b231664111317d095a4f04e4b6fe79ce37b3d199097"
}
pradeep:~$
Terraform Plan
Let us run the terraform plan
again
pradeep:~$terraform plan
docker_image.nginx: Refreshing state... [id=sha256:fa5269854a5e615e51a72b17ad3fd1e01268f278a6684c8ed3c5f0cdce3f230bnginx:latest]
docker_container.nginx: Refreshing state... [id=712fa3cb7700e8c1beb057e5535e34dd899ccd59de5fd49a93b2cf65b6423401]
Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraform since the last "terraform apply":
# docker_container.nginx has changed
~ resource "docker_container" "nginx" {
+ dns = []
+ dns_opts = []
+ dns_search = []
+ group_add = []
id = "712fa3cb7700e8c1beb057e5535e34dd899ccd59de5fd49a93b2cf65b6423401"
+ links = []
+ log_opts = {}
name = "tutorial"
+ sysctls = {}
+ tmpfs = {}
# (31 unchanged attributes hidden)
# (1 unchanged block hidden)
}
Unless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may include actions to undo or respond to these changes.
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
No changes. Your infrastructure matches the configuration.
Your configuration already matches the changes detected above. If you'd like to update the Terraform state to match, create and apply a refresh-only plan:
terraform apply -refresh-only
pradeep:~$
It says, No changes. Your infrastructure matches the configuration.
Terraform Graph
pradeep:~$terraform graph
digraph {
compound = "true"
newrank = "true"
subgraph "root" {
"[root] docker_container.nginx (expand)" [label = "docker_container.nginx", shape = "box"]
"[root] docker_image.nginx (expand)" [label = "docker_image.nginx", shape = "box"]
"[root] provider[\"registry.terraform.io/kreuzwerker/docker\"]" [label = "provider[\"registry.terraform.io/kreuzwerker/docker\"]", shape = "diamond"]
"[root] docker_container.nginx (expand)" -> "[root] docker_image.nginx (expand)"
"[root] docker_image.nginx (expand)" -> "[root] provider[\"registry.terraform.io/kreuzwerker/docker\"]"
"[root] provider[\"registry.terraform.io/kreuzwerker/docker\"] (close)" -> "[root] docker_container.nginx (expand)"
"[root] root" -> "[root] provider[\"registry.terraform.io/kreuzwerker/docker\"] (close)"
}
}
pradeep:~$
Terraform Destroy
Let us destroy the resources that we created
pradeep:~$terraform destroy
docker_image.nginx: Refreshing state... [id=sha256:fa5269854a5e615e51a72b17ad3fd1e01268f278a6684c8ed3c5f0cdce3f230bnginx:latest]
docker_container.nginx: Refreshing state... [id=712fa3cb7700e8c1beb057e5535e34dd899ccd59de5fd49a93b2cf65b6423401]
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 = "712fa3cb7700" -> null
- id = "712fa3cb7700e8c1beb057e5535e34dd899ccd59de5fd49a93b2cf65b6423401" -> null
- image = "sha256:fa5269854a5e615e51a72b17ad3fd1e01268f278a6684c8ed3c5f0cdce3f230b" -> 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 = 8080 -> 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:fa5269854a5e615e51a72b17ad3fd1e01268f278a6684c8ed3c5f0cdce3f230bnginx:latest" -> null
- keep_locally = false -> null
- latest = "sha256:fa5269854a5e615e51a72b17ad3fd1e01268f278a6684c8ed3c5f0cdce3f230b" -> null
- name = "nginx:latest" -> null
- repo_digest = "nginx@sha256:859ab6768a6f26a79bc42b231664111317d095a4f04e4b6fe79ce37b3d199097" -> 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=712fa3cb7700e8c1beb057e5535e34dd899ccd59de5fd49a93b2cf65b6423401]
docker_container.nginx: Destruction complete after 0s
docker_image.nginx: Destroying... [id=sha256:fa5269854a5e615e51a72b17ad3fd1e01268f278a6684c8ed3c5f0cdce3f230bnginx:latest]
docker_image.nginx: Destruction complete after 0s
Destroy complete! Resources: 2 destroyed.
pradeep:~$